Skip to content

Commit c094eff

Browse files
authored
[Core, Rust Server] Support JSON query parameters (#6577)
* [Core] Record content type for parameters * [Rust Server] Support query parameters in JSON * [Rust Server] Add test for JSON query parameter * Update samples
1 parent 45655c2 commit c094eff

File tree

16 files changed

+572
-120
lines changed

16 files changed

+572
-120
lines changed

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4096,22 +4096,23 @@ public CodegenParameter fromParameter(Parameter parameter, Set<String> imports)
40964096
codegenParameter.vendorExtensions.putAll(parameter.getExtensions());
40974097
}
40984098

4099-
Schema s;
4099+
Schema parameterSchema;
41004100
if (parameter.getSchema() != null) {
4101-
s = parameter.getSchema();
4101+
parameterSchema = parameter.getSchema();
41024102
} else if (parameter.getContent() != null) {
41034103
Content content = parameter.getContent();
41044104
if (content.size() > 1) {
41054105
once(LOGGER).warn("Multiple schemas found in content, returning only the first one");
41064106
}
4107-
MediaType mediaType = content.values().iterator().next();
4108-
s = mediaType.getSchema();
4107+
Map.Entry<String, MediaType> entry = content.entrySet().iterator().next();
4108+
codegenParameter.contentType = entry.getKey();
4109+
parameterSchema = entry.getValue().getSchema();
41094110
} else {
4110-
s = null;
4111+
parameterSchema = null;
41114112
}
41124113

4113-
if (s != null) {
4114-
Schema parameterSchema = ModelUtils.unaliasSchema(this.openAPI, s, importMapping);
4114+
if (parameterSchema != null) {
4115+
parameterSchema = ModelUtils.unaliasSchema(this.openAPI, parameterSchema);
41154116
if (parameterSchema == null) {
41164117
LOGGER.warn("warning! Schema not found for parameter \"" + parameter.getName() + "\", using String");
41174118
parameterSchema = new StringSchema().description("//TODO automatically added by openapi-generator due to missing type definition.");

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,13 @@ private void postProcessOperationWithModels(CodegenOperation op, List<Object> al
10271027
}
10281028
}
10291029

1030+
for (CodegenParameter param : op.queryParams) {
1031+
// If the MIME type is JSON, mark it. We don't currently support any other MIME types.
1032+
if (param.contentType != null && isMimetypeJson(param.contentType)) {
1033+
param.vendorExtensions.put("x-consumes-json", true);
1034+
}
1035+
}
1036+
10301037
for (CodegenParameter param : op.formParams) {
10311038
processParam(param, op);
10321039
}

modules/openapi-generator/src/main/resources/rust-server/client-operation.mustache

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,23 @@
3636
{{^required}}
3737
if let Some(param_{{{paramName}}}) = param_{{{paramName}}} {
3838
{{/required}}
39-
query_string.append_pair("{{{baseName}}}", &param_{{{paramName}}}{{#isListContainer}}.iter().map(ToString::to_string).collect::<Vec<String>>().join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
39+
query_string.append_pair("{{{baseName}}}",
40+
{{#vendorExtensions}}
41+
{{#x-consumes-json}}
42+
&match serde_json::to_string(&param_{{{paramName}}}) {
43+
Ok(str) => str,
44+
Err(e) => return Err(ApiError(format!("Unable to serialize {{{paramName}}} to string: {}", e))),
45+
});
46+
{{/x-consumes-json}}
47+
{{^x-consumes-json}}
48+
{{#isListContainer}}
49+
&param_{{{paramName}}}.iter().map(ToString::to_string).collect::<Vec<String>>().join(","));
50+
{{/isListContainer}}
51+
{{^isListContainer}}
52+
&param_{{{paramName}}}.to_string());
53+
{{/isListContainer}}
54+
{{/x-consumes-json}}
55+
{{/vendorExtensions}}
4056
{{^required}}
4157
}
4258
{{/required}}
@@ -87,7 +103,7 @@
87103
{{#jsonSchema}}
88104
let {{{paramName}}}_str = match serde_json::to_string(&param_{{{paramName}}}) {
89105
Ok(str) => str,
90-
Err(e) => return Err(ApiError(format!("Unable to parse {{{paramName}}} to string: {}", e))),
106+
Err(e) => return Err(ApiError(format!("Unable to serialize {{{paramName}}} to string: {}", e))),
91107
};
92108

93109
let {{{paramName}}}_vec = {{{paramName}}}_str.as_bytes().to_vec();

modules/openapi-generator/src/main/resources/rust-server/server-operation.mustache

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -121,43 +121,83 @@
121121
{{/-last}}
122122
{{/headerParams}}
123123
{{#queryParams}}
124-
{{#-first}}
124+
{{#-first}}
125125
// Query parameters (note that non-required or collection query parameters will ignore garbage values, rather than causing a 400 response)
126126
let query_params = form_urlencoded::parse(uri.query().unwrap_or_default().as_bytes()).collect::<Vec<_>>();
127-
{{/-first}}
127+
{{/-first}}
128128
let param_{{{paramName}}} = query_params.iter().filter(|e| e.0 == "{{{baseName}}}").map(|e| e.1.to_owned())
129-
{{#isListContainer}}
129+
{{#isListContainer}}
130+
{{^vendorExtensions.x-consumes-json}}
130131
.filter_map(|param_{{{paramName}}}| param_{{{paramName}}}.parse().ok())
131132
.collect::<Vec<_>>();
132-
{{^required}}
133+
{{^required}}
133134
let param_{{{paramName}}} = if !param_{{{paramName}}}.is_empty() {
134135
Some(param_{{{paramName}}})
135136
} else {
136137
None
137138
};
138-
{{/required}}
139-
{{/isListContainer}}
140-
{{^isListContainer}}
139+
{{/required}}
140+
{{/vendorExtensions.x-consumes-json}}
141+
{{#vendorExtensions.x-consumes-json}}
141142
.nth(0);
142-
{{#required}}
143143
let param_{{{paramName}}} = match param_{{{paramName}}} {
144-
Some(param_{{{paramName}}}) => match param_{{{paramName}}}.parse::<{{{dataType}}}>() {
145-
Ok(param_{{{paramName}}}) => param_{{{paramName}}},
146-
Err(e) => return Ok(Response::builder()
147-
.status(StatusCode::BAD_REQUEST)
148-
.body(Body::from(format!("Couldn't parse query parameter {{{baseName}}} - doesn't match schema: {}", e)))
149-
.expect("Unable to create Bad Request response for invalid query parameter {{{baseName}}}")),
144+
Some(param_{{{paramName}}}) => {
145+
let param_{{{paramName}}} =
146+
serde_json::from_str::<{{{dataType}}}>
147+
(&param_{{{paramName}}});
148+
match param_{{{paramName}}} {
149+
Ok(param_{{{paramName}}}) => Some(param_{{{paramName}}}),
150+
Err(e) => return Ok(Response::builder()
151+
.status(StatusCode::BAD_REQUEST)
152+
.body(Body::from(format!("Couldn't parse query parameter {{{baseName}}} - doesn't match schema: {}", e)))
153+
.expect("Unable to create Bad Request response for invalid query parameter {{{baseName}}}")),
154+
}
150155
},
156+
None => None,
157+
};
158+
{{#required}}
159+
let param_{{{paramName}}} = match param_{{{paramName}}} {
160+
Some(param_{{{paramName}}}) => param_{{{paramName}}},
151161
None => return Ok(Response::builder()
152-
.status(StatusCode::BAD_REQUEST)
153-
.body(Body::from("Missing required query parameter {{{baseName}}}"))
154-
.expect("Unable to create Bad Request response for missing qeury parameter {{{baseName}}}")),
162+
.status(StatusCode::BAD_REQUEST)
163+
.body(Body::from("Missing required query parameter {{{baseName}}}"))
164+
.expect("Unable to create Bad Request response for missing query parameter {{{baseName}}}")),
155165
};
156-
{{/required}}
157-
{{^required}}
158-
let param_{{{paramName}}} = param_{{{paramName}}}.and_then(|param_{{{paramName}}}| param_{{{paramName}}}.parse::<{{{baseType}}}>().ok());
159-
{{/required}}
160-
{{/isListContainer}}
166+
{{/required}}
167+
{{/vendorExtensions.x-consumes-json}}
168+
{{/isListContainer}}
169+
{{^isListContainer}}
170+
.nth(0);
171+
let param_{{{paramName}}} = match param_{{{paramName}}} {
172+
Some(param_{{{paramName}}}) => {
173+
let param_{{{paramName}}} =
174+
{{#vendorExtensions.x-consumes-json}}
175+
serde_json::from_str::<{{{dataType}}}>
176+
{{/vendorExtensions.x-consumes-json}}
177+
{{^vendorExtensions.x-consumes-json}}
178+
<{{{dataType}}} as std::str::FromStr>::from_str
179+
{{/vendorExtensions.x-consumes-json}}
180+
(&param_{{{paramName}}});
181+
match param_{{{paramName}}} {
182+
Ok(param_{{{paramName}}}) => Some(param_{{{paramName}}}),
183+
Err(e) => return Ok(Response::builder()
184+
.status(StatusCode::BAD_REQUEST)
185+
.body(Body::from(format!("Couldn't parse query parameter {{{baseName}}} - doesn't match schema: {}", e)))
186+
.expect("Unable to create Bad Request response for invalid query parameter {{{baseName}}}")),
187+
}
188+
},
189+
None => None,
190+
};
191+
{{#required}}
192+
let param_{{{paramName}}} = match param_{{{paramName}}} {
193+
Some(param_{{{paramName}}}) => param_{{{paramName}}},
194+
None => return Ok(Response::builder()
195+
.status(StatusCode::BAD_REQUEST)
196+
.body(Body::from("Missing required query parameter {{{baseName}}}"))
197+
.expect("Unable to create Bad Request response for missing query parameter {{{baseName}}}")),
198+
};
199+
{{/required}}
200+
{{/isListContainer}}
161201
{{#-last}}
162202

163203
{{/-last}}

modules/openapi-generator/src/test/resources/3_0/rust-server/openapi-v3.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,20 @@ paths:
391391
responses:
392392
'200':
393393
description: Success
394+
/json-complex-query-param:
395+
get:
396+
parameters:
397+
- name: list-of-strings
398+
in: query
399+
content:
400+
application/json:
401+
schema:
402+
type: array
403+
items:
404+
$ref: '#/components/schemas/StringObject'
405+
responses:
406+
'200':
407+
description: Success
394408

395409
components:
396410
securitySchemes:

samples/server/petstore/rust-server/output/multipart-v3/src/client/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ impl<S, C> Api<C> for Client<S, C> where
574574

575575
let string_field_str = match serde_json::to_string(&param_string_field) {
576576
Ok(str) => str,
577-
Err(e) => return Err(ApiError(format!("Unable to parse string_field to string: {}", e))),
577+
Err(e) => return Err(ApiError(format!("Unable to serialize string_field to string: {}", e))),
578578
};
579579

580580
let string_field_vec = string_field_str.as_bytes().to_vec();
@@ -586,7 +586,7 @@ impl<S, C> Api<C> for Client<S, C> where
586586

587587
let optional_string_field_str = match serde_json::to_string(&param_optional_string_field) {
588588
Ok(str) => str,
589-
Err(e) => return Err(ApiError(format!("Unable to parse optional_string_field to string: {}", e))),
589+
Err(e) => return Err(ApiError(format!("Unable to serialize optional_string_field to string: {}", e))),
590590
};
591591

592592
let optional_string_field_vec = optional_string_field_str.as_bytes().to_vec();
@@ -598,7 +598,7 @@ impl<S, C> Api<C> for Client<S, C> where
598598

599599
let object_field_str = match serde_json::to_string(&param_object_field) {
600600
Ok(str) => str,
601-
Err(e) => return Err(ApiError(format!("Unable to parse object_field to string: {}", e))),
601+
Err(e) => return Err(ApiError(format!("Unable to serialize object_field to string: {}", e))),
602602
};
603603

604604
let object_field_vec = object_field_str.as_bytes().to_vec();

samples/server/petstore/rust-server/output/openapi-v3/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ To run a client, follow one of the following simple steps:
6363
```
6464
cargo run --example client CallbackWithHeaderPost
6565
cargo run --example client ComplexQueryParamGet
66+
cargo run --example client JsonComplexQueryParamGet
6667
cargo run --example client MandatoryRequestHeaderGet
6768
cargo run --example client MergePatchJsonGet
6869
cargo run --example client MultigetGet
@@ -119,6 +120,7 @@ Method | HTTP request | Description
119120
[****](docs/default_api.md#) | **POST** /callback-with-header |
120121
[****](docs/default_api.md#) | **GET** /complex-query-param |
121122
[****](docs/default_api.md#) | **GET** /enum_in_path/{path_param} |
123+
[****](docs/default_api.md#) | **GET** /json-complex-query-param |
122124
[****](docs/default_api.md#) | **GET** /mandatory-request-header |
123125
[****](docs/default_api.md#) | **GET** /merge-patch-json |
124126
[****](docs/default_api.md#) | **GET** /multiget | Get some stuff.

samples/server/petstore/rust-server/output/openapi-v3/api/openapi.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,23 @@ paths:
407407
description: Success
408408
tags:
409409
- Repo
410+
/json-complex-query-param:
411+
get:
412+
parameters:
413+
- content:
414+
application/json:
415+
schema:
416+
items:
417+
$ref: '#/components/schemas/StringObject'
418+
type: array
419+
explode: true
420+
in: query
421+
name: list-of-strings
422+
required: false
423+
style: form
424+
responses:
425+
"200":
426+
description: Success
410427
components:
411428
schemas:
412429
EnumWithStarObject:

samples/server/petstore/rust-server/output/openapi-v3/docs/default_api.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Method | HTTP request | Description
77
****](default_api.md#) | **POST** /callback-with-header |
88
****](default_api.md#) | **GET** /complex-query-param |
99
****](default_api.md#) | **GET** /enum_in_path/{path_param} |
10+
****](default_api.md#) | **GET** /json-complex-query-param |
1011
****](default_api.md#) | **GET** /mandatory-request-header |
1112
****](default_api.md#) | **GET** /merge-patch-json |
1213
****](default_api.md#) | **GET** /multiget | Get some stuff.
@@ -109,6 +110,38 @@ No authorization required
109110

110111
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
111112

113+
# ****
114+
> (optional)
115+
116+
117+
### Required Parameters
118+
119+
Name | Type | Description | Notes
120+
------------- | ------------- | ------------- | -------------
121+
**optional** | **map[string]interface{}** | optional parameters | nil if no parameters
122+
123+
### Optional Parameters
124+
Optional parameters are passed through a map[string]interface{}.
125+
126+
Name | Type | Description | Notes
127+
------------- | ------------- | ------------- | -------------
128+
**list_of_strings** | [**String**](String.md)| |
129+
130+
### Return type
131+
132+
(empty response body)
133+
134+
### Authorization
135+
136+
No authorization required
137+
138+
### HTTP request headers
139+
140+
- **Content-Type**: Not defined
141+
- **Accept**: Not defined
142+
143+
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
144+
112145
# ****
113146
> (x_header)
114147

samples/server/petstore/rust-server/output/openapi-v3/examples/client/main.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use openapi_v3::{Api, ApiNoContext, Client, ContextWrapperExt, models,
99
CallbackWithHeaderPostResponse,
1010
ComplexQueryParamGetResponse,
1111
EnumInPathPathParamGetResponse,
12+
JsonComplexQueryParamGetResponse,
1213
MandatoryRequestHeaderGetResponse,
1314
MergePatchJsonGetResponse,
1415
MultigetGetResponse,
@@ -52,6 +53,7 @@ fn main() {
5253
.possible_values(&[
5354
"CallbackWithHeaderPost",
5455
"ComplexQueryParamGet",
56+
"JsonComplexQueryParamGet",
5557
"MandatoryRequestHeaderGet",
5658
"MergePatchJsonGet",
5759
"MultigetGet",
@@ -138,6 +140,12 @@ fn main() {
138140
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
139141
},
140142
*/
143+
Some("JsonComplexQueryParamGet") => {
144+
let result = rt.block_on(client.json_complex_query_param_get(
145+
Some(&Vec::new())
146+
));
147+
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
148+
},
141149
Some("MandatoryRequestHeaderGet") => {
142150
let result = rt.block_on(client.mandatory_request_header_get(
143151
"x_header_example".to_string()

0 commit comments

Comments
 (0)