Skip to content

Commit fb36272

Browse files
authored
[Rust Server] Handle arrays in forms (#19625)
* [Rust Server] Handle arrays in forms correctly * [Rust Server] Add tests * Update samples
1 parent 8821cf0 commit fb36272

File tree

14 files changed

+435
-53
lines changed

14 files changed

+435
-53
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -1520,6 +1520,10 @@ private void processParam(CodegenParameter param, CodegenOperation op) {
15201520
if (Boolean.TRUE.equals(param.isFreeFormObject)) {
15211521
param.vendorExtensions.put("x-format-string", "{:?}");
15221522
example = null;
1523+
} else if (param.isArray && param.isString) {
1524+
// This occurs if the parameter is a form property and is Vec<String>
1525+
param.vendorExtensions.put("x-format-string", "{:?}");
1526+
example = (param.example != null) ? "&vec![\"" + param.example + "\".to_string()]" : "&Vec::new()";
15231527
} else if (param.isString) {
15241528
param.vendorExtensions.put("x-format-string", "\\\"{}\\\"");
15251529
example = "\"" + ((param.example != null) ? param.example : "") + "\".to_string()";

modules/openapi-generator/src/main/resources/rust-server/client-request-body-instance.mustache

+28-3
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,36 @@
2828
// Consumes form body
2929
{{#formParams}}
3030
{{#-first}}
31-
let params = &[
31+
let mut params = vec![];
3232
{{/-first}}
33-
("{{{baseName}}}", {{#vendorExtensions}}{{#required}}Some({{#isString}}param_{{{paramName}}}{{/isString}}{{^isString}}format!("{:?}", param_{{{paramName}}}){{/isString}}){{/required}}{{^required}}{{#isString}}param_{{{paramName}}}{{/isString}}{{^isString}}param_{{{paramName}}}.map(|param| format!("{:?}", param)){{/isString}}{{/required}}{{/vendorExtensions}}),
33+
{{^required}}
34+
if let Some(param_{{{paramName}}}) = param_{{{paramName}}} {
35+
{{/required}}
36+
{{#isArray}}
37+
// style=form,explode=true
38+
for param_{{{paramName}}} in param_{{{paramName}}} {
39+
{{/isArray}}
40+
params.push(("{{{baseName}}}",
41+
{{^isString}}
42+
format!("{{{vendorExtensions.x-format-string}}}", param_{{{paramName}}})
43+
{{/isString}}
44+
{{#isString}}
45+
{{#isArray}}
46+
param_{{{paramName}}}.to_string()
47+
{{/isArray}}
48+
{{^isArray}}
49+
param_{{{paramName}}}
50+
{{/isArray}}
51+
{{/isString}}
52+
));
53+
{{#isArray}}
54+
}
55+
{{/isArray}}
56+
{{^required}}
57+
}
58+
{{/required}}
3459
{{#-last}}
35-
];
60+
3661
let body = serde_urlencoded::to_string(params).expect("impossible to fail to serialize");
3762

3863
*request.body_mut() = Body::from(body.into_bytes());

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

+18
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,24 @@ paths:
495495
responses:
496496
200:
497497
description: OK
498+
/form-test:
499+
post:
500+
summary: Test a Form Post
501+
operationId: FormTest
502+
requestBody:
503+
content:
504+
application/x-www-form-urlencoded:
505+
schema:
506+
type: object
507+
properties:
508+
requiredArray:
509+
type: array
510+
items:
511+
type: string
512+
required: true
513+
responses:
514+
'200':
515+
description: OK
498516

499517
components:
500518
securitySchemes:

samples/server/petstore/rust-server/output/openapi-v3/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ edition = "2018"
1010
[features]
1111
default = ["client", "server"]
1212
client = [
13+
"serde_urlencoded",
1314
"serde_ignored", "regex", "percent-encoding", "lazy_static",
1415
"hyper", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url"
1516
]
@@ -55,6 +56,7 @@ serde_ignored = {version = "0.1.1", optional = true}
5556
url = {version = "2.1", optional = true}
5657

5758
# Client-specific
59+
serde_urlencoded = {version = "0.6.1", optional = true}
5860

5961
# Server, and client callback-specific
6062
lazy_static = { version = "1.4", optional = true }

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

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ To run a client, follow one of the following simple steps:
8989
cargo run --example client AnyOfGet
9090
cargo run --example client CallbackWithHeaderPost
9191
cargo run --example client ComplexQueryParamGet
92+
cargo run --example client FormTest
9293
cargo run --example client GetWithBooleanParameter
9394
cargo run --example client JsonComplexQueryParamGet
9495
cargo run --example client MandatoryRequestHeaderGet
@@ -152,6 +153,7 @@ Method | HTTP request | Description
152153
[****](docs/default_api.md#) | **GET** /any-of |
153154
[****](docs/default_api.md#) | **POST** /callback-with-header |
154155
[****](docs/default_api.md#) | **GET** /complex-query-param |
156+
[**FormTest**](docs/default_api.md#FormTest) | **POST** /form-test | Test a Form Post
155157
[**GetWithBooleanParameter**](docs/default_api.md#GetWithBooleanParameter) | **GET** /get-with-bool |
156158
[****](docs/default_api.md#) | **GET** /json-complex-query-param |
157159
[****](docs/default_api.md#) | **GET** /mandatory-request-header |

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

+20
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,19 @@ paths:
525525
responses:
526526
"200":
527527
description: OK
528+
/form-test:
529+
post:
530+
operationId: FormTest
531+
requestBody:
532+
content:
533+
application/x-www-form-urlencoded:
534+
schema:
535+
$ref: '#/components/schemas/FormTest_request'
536+
required: true
537+
responses:
538+
"200":
539+
description: OK
540+
summary: Test a Form Post
528541
components:
529542
schemas:
530543
AnyOfProperty:
@@ -780,6 +793,13 @@ components:
780793
anyOf:
781794
- $ref: '#/components/schemas/StringObject'
782795
- $ref: '#/components/schemas/UuidObject'
796+
FormTest_request:
797+
properties:
798+
requiredArray:
799+
items:
800+
type: string
801+
type: array
802+
type: object
783803
AnyOfObject_anyOf:
784804
enum:
785805
- FOO

samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs

+22
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use openapi_v3::{
88
AnyOfGetResponse,
99
CallbackWithHeaderPostResponse,
1010
ComplexQueryParamGetResponse,
11+
FormTestResponse,
1112
GetWithBooleanParameterResponse,
1213
JsonComplexQueryParamGetResponse,
1314
MandatoryRequestHeaderGetResponse,
@@ -101,6 +102,11 @@ enum Operation {
101102
#[structopt(parse(try_from_str = parse_json), long)]
102103
list_of_strings: Option<Vec<models::StringObject>>,
103104
},
105+
/// Test a Form Post
106+
FormTest {
107+
#[structopt(parse(try_from_str = parse_json), long)]
108+
required_array: Option<Vec<String>>,
109+
},
104110
GetWithBooleanParameter {
105111
/// Let's check apostrophes get encoded properly!
106112
#[structopt(short, long)]
@@ -319,6 +325,22 @@ async fn main() -> Result<()> {
319325
,
320326
}
321327
}
328+
Operation::FormTest {
329+
required_array,
330+
} => {
331+
info!("Performing a FormTest request");
332+
333+
let result = client.form_test(
334+
required_array.as_ref(),
335+
).await?;
336+
debug!("Result: {:?}", result);
337+
338+
match result {
339+
FormTestResponse::OK
340+
=> "OK\n".to_string()
341+
,
342+
}
343+
}
322344
Operation::GetWithBooleanParameter {
323345
iambool,
324346
} => {

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

+33
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Method | HTTP request | Description
77
****](default_api.md#) | **GET** /any-of |
88
****](default_api.md#) | **POST** /callback-with-header |
99
****](default_api.md#) | **GET** /complex-query-param |
10+
**FormTest**](default_api.md#FormTest) | **POST** /form-test | Test a Form Post
1011
**GetWithBooleanParameter**](default_api.md#GetWithBooleanParameter) | **GET** /get-with-bool |
1112
****](default_api.md#) | **GET** /json-complex-query-param |
1213
****](default_api.md#) | **GET** /mandatory-request-header |
@@ -122,6 +123,38 @@ No authorization required
122123

123124
[[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)
124125

126+
# **FormTest**
127+
> FormTest(optional)
128+
Test a Form Post
129+
130+
### Required Parameters
131+
132+
Name | Type | Description | Notes
133+
------------- | ------------- | ------------- | -------------
134+
**optional** | **map[string]interface{}** | optional parameters | nil if no parameters
135+
136+
### Optional Parameters
137+
Optional parameters are passed through a map[string]interface{}.
138+
139+
Name | Type | Description | Notes
140+
------------- | ------------- | ------------- | -------------
141+
**required_array** | [**String**](String.md)| |
142+
143+
### Return type
144+
145+
(empty response body)
146+
147+
### Authorization
148+
149+
No authorization required
150+
151+
### HTTP request headers
152+
153+
- **Content-Type**: application/x-www-form-urlencoded
154+
- **Accept**: Not defined
155+
156+
[[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)
157+
125158
# **GetWithBooleanParameter**
126159
> GetWithBooleanParameter(iambool)
127160

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

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use openapi_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
99
AnyOfGetResponse,
1010
CallbackWithHeaderPostResponse,
1111
ComplexQueryParamGetResponse,
12+
FormTestResponse,
1213
GetWithBooleanParameterResponse,
1314
JsonComplexQueryParamGetResponse,
1415
MandatoryRequestHeaderGetResponse,
@@ -66,6 +67,7 @@ fn main() {
6667
"AnyOfGet",
6768
"CallbackWithHeaderPost",
6869
"ComplexQueryParamGet",
70+
"FormTest",
6971
"GetWithBooleanParameter",
7072
"JsonComplexQueryParamGet",
7173
"MandatoryRequestHeaderGet",
@@ -185,6 +187,12 @@ fn main() {
185187
));
186188
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
187189
},
190+
Some("FormTest") => {
191+
let result = rt.block_on(client.form_test(
192+
Some(&Vec::new())
193+
));
194+
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
195+
},
188196
Some("GetWithBooleanParameter") => {
189197
let result = rt.block_on(client.get_with_boolean_parameter(
190198
true

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

+11
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ use openapi_v3::{
105105
AnyOfGetResponse,
106106
CallbackWithHeaderPostResponse,
107107
ComplexQueryParamGetResponse,
108+
FormTestResponse,
108109
GetWithBooleanParameterResponse,
109110
JsonComplexQueryParamGetResponse,
110111
MandatoryRequestHeaderGetResponse,
@@ -166,6 +167,16 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
166167
Err(ApiError("Api-Error: Operation is NOT implemented".into()))
167168
}
168169

170+
/// Test a Form Post
171+
async fn form_test(
172+
&self,
173+
required_array: Option<&Vec<String>>,
174+
context: &C) -> Result<FormTestResponse, ApiError>
175+
{
176+
info!("form_test({:?}) - X-Span-ID: {:?}", required_array, context.get().0.clone());
177+
Err(ApiError("Api-Error: Operation is NOT implemented".into()))
178+
}
179+
169180
async fn get_with_boolean_parameter(
170181
&self,
171182
iambool: bool,

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

+91
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ use crate::{Api,
3939
AnyOfGetResponse,
4040
CallbackWithHeaderPostResponse,
4141
ComplexQueryParamGetResponse,
42+
FormTestResponse,
4243
GetWithBooleanParameterResponse,
4344
JsonComplexQueryParamGetResponse,
4445
MandatoryRequestHeaderGetResponse,
@@ -670,6 +671,96 @@ impl<S, C> Api<C> for Client<S, C> where
670671
}
671672
}
672673

674+
async fn form_test(
675+
&self,
676+
param_required_array: Option<&Vec<String>>,
677+
context: &C) -> Result<FormTestResponse, ApiError>
678+
{
679+
let mut client_service = self.client_service.clone();
680+
let mut uri = format!(
681+
"{}/form-test",
682+
self.base_path
683+
);
684+
685+
// Query parameters
686+
let query_string = {
687+
let mut query_string = form_urlencoded::Serializer::new("".to_owned());
688+
query_string.finish()
689+
};
690+
if !query_string.is_empty() {
691+
uri += "?";
692+
uri += &query_string;
693+
}
694+
695+
let uri = match Uri::from_str(&uri) {
696+
Ok(uri) => uri,
697+
Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
698+
};
699+
700+
let mut request = match Request::builder()
701+
.method("POST")
702+
.uri(uri)
703+
.body(Body::empty()) {
704+
Ok(req) => req,
705+
Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
706+
};
707+
708+
// Consumes form body
709+
let mut params = vec![];
710+
if let Some(param_required_array) = param_required_array {
711+
// style=form,explode=true
712+
for param_required_array in param_required_array {
713+
params.push(("requiredArray",
714+
format!("{:?}", param_required_array)
715+
));
716+
}
717+
}
718+
719+
let body = serde_urlencoded::to_string(params).expect("impossible to fail to serialize");
720+
721+
*request.body_mut() = Body::from(body.into_bytes());
722+
723+
let header = "application/x-www-form-urlencoded";
724+
request.headers_mut().insert(CONTENT_TYPE, match HeaderValue::from_str(header) {
725+
Ok(h) => h,
726+
Err(e) => return Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
727+
});
728+
729+
let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.as_str());
730+
request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
731+
Ok(h) => h,
732+
Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
733+
});
734+
735+
let response = client_service.call((request, context.clone()))
736+
.map_err(|e| ApiError(format!("No response received: {}", e))).await?;
737+
738+
match response.status().as_u16() {
739+
200 => {
740+
Ok(
741+
FormTestResponse::OK
742+
)
743+
}
744+
code => {
745+
let headers = response.headers().clone();
746+
let body = response.into_body()
747+
.take(100)
748+
.into_raw().await;
749+
Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
750+
code,
751+
headers,
752+
match body {
753+
Ok(body) => match String::from_utf8(body) {
754+
Ok(body) => body,
755+
Err(e) => format!("<Body was not UTF8: {:?}>", e),
756+
},
757+
Err(e) => format!("<Failed to read body: {}>", e),
758+
}
759+
)))
760+
}
761+
}
762+
}
763+
673764
async fn get_with_boolean_parameter(
674765
&self,
675766
param_iambool: bool,

0 commit comments

Comments
 (0)