Skip to content

Commit

Permalink
[Rust Server] Handle arrays in forms (#19625)
Browse files Browse the repository at this point in the history
* [Rust Server] Handle arrays in forms correctly

* [Rust Server] Add tests

* Update samples
  • Loading branch information
richardwhiuk authored Sep 22, 2024
1 parent 8821cf0 commit fb36272
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1520,6 +1520,10 @@ private void processParam(CodegenParameter param, CodegenOperation op) {
if (Boolean.TRUE.equals(param.isFreeFormObject)) {
param.vendorExtensions.put("x-format-string", "{:?}");
example = null;
} else if (param.isArray && param.isString) {
// This occurs if the parameter is a form property and is Vec<String>
param.vendorExtensions.put("x-format-string", "{:?}");
example = (param.example != null) ? "&vec![\"" + param.example + "\".to_string()]" : "&Vec::new()";
} else if (param.isString) {
param.vendorExtensions.put("x-format-string", "\\\"{}\\\"");
example = "\"" + ((param.example != null) ? param.example : "") + "\".to_string()";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,36 @@
// Consumes form body
{{#formParams}}
{{#-first}}
let params = &[
let mut params = vec![];
{{/-first}}
("{{{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}}),
{{^required}}
if let Some(param_{{{paramName}}}) = param_{{{paramName}}} {
{{/required}}
{{#isArray}}
// style=form,explode=true
for param_{{{paramName}}} in param_{{{paramName}}} {
{{/isArray}}
params.push(("{{{baseName}}}",
{{^isString}}
format!("{{{vendorExtensions.x-format-string}}}", param_{{{paramName}}})
{{/isString}}
{{#isString}}
{{#isArray}}
param_{{{paramName}}}.to_string()
{{/isArray}}
{{^isArray}}
param_{{{paramName}}}
{{/isArray}}
{{/isString}}
));
{{#isArray}}
}
{{/isArray}}
{{^required}}
}
{{/required}}
{{#-last}}
];

let body = serde_urlencoded::to_string(params).expect("impossible to fail to serialize");

*request.body_mut() = Body::from(body.into_bytes());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,24 @@ paths:
responses:
200:
description: OK
/form-test:
post:
summary: Test a Form Post
operationId: FormTest
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
properties:
requiredArray:
type: array
items:
type: string
required: true
responses:
'200':
description: OK

components:
securitySchemes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ edition = "2018"
[features]
default = ["client", "server"]
client = [
"serde_urlencoded",
"serde_ignored", "regex", "percent-encoding", "lazy_static",
"hyper", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url"
]
Expand Down Expand Up @@ -55,6 +56,7 @@ serde_ignored = {version = "0.1.1", optional = true}
url = {version = "2.1", optional = true}

# Client-specific
serde_urlencoded = {version = "0.6.1", optional = true}

# Server, and client callback-specific
lazy_static = { version = "1.4", optional = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ To run a client, follow one of the following simple steps:
cargo run --example client AnyOfGet
cargo run --example client CallbackWithHeaderPost
cargo run --example client ComplexQueryParamGet
cargo run --example client FormTest
cargo run --example client GetWithBooleanParameter
cargo run --example client JsonComplexQueryParamGet
cargo run --example client MandatoryRequestHeaderGet
Expand Down Expand Up @@ -152,6 +153,7 @@ Method | HTTP request | Description
[****](docs/default_api.md#) | **GET** /any-of |
[****](docs/default_api.md#) | **POST** /callback-with-header |
[****](docs/default_api.md#) | **GET** /complex-query-param |
[**FormTest**](docs/default_api.md#FormTest) | **POST** /form-test | Test a Form Post
[**GetWithBooleanParameter**](docs/default_api.md#GetWithBooleanParameter) | **GET** /get-with-bool |
[****](docs/default_api.md#) | **GET** /json-complex-query-param |
[****](docs/default_api.md#) | **GET** /mandatory-request-header |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,19 @@ paths:
responses:
"200":
description: OK
/form-test:
post:
operationId: FormTest
requestBody:
content:
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/FormTest_request'
required: true
responses:
"200":
description: OK
summary: Test a Form Post
components:
schemas:
AnyOfProperty:
Expand Down Expand Up @@ -780,6 +793,13 @@ components:
anyOf:
- $ref: '#/components/schemas/StringObject'
- $ref: '#/components/schemas/UuidObject'
FormTest_request:
properties:
requiredArray:
items:
type: string
type: array
type: object
AnyOfObject_anyOf:
enum:
- FOO
Expand Down
22 changes: 22 additions & 0 deletions samples/server/petstore/rust-server/output/openapi-v3/bin/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use openapi_v3::{
AnyOfGetResponse,
CallbackWithHeaderPostResponse,
ComplexQueryParamGetResponse,
FormTestResponse,
GetWithBooleanParameterResponse,
JsonComplexQueryParamGetResponse,
MandatoryRequestHeaderGetResponse,
Expand Down Expand Up @@ -101,6 +102,11 @@ enum Operation {
#[structopt(parse(try_from_str = parse_json), long)]
list_of_strings: Option<Vec<models::StringObject>>,
},
/// Test a Form Post
FormTest {
#[structopt(parse(try_from_str = parse_json), long)]
required_array: Option<Vec<String>>,
},
GetWithBooleanParameter {
/// Let's check apostrophes get encoded properly!
#[structopt(short, long)]
Expand Down Expand Up @@ -319,6 +325,22 @@ async fn main() -> Result<()> {
,
}
}
Operation::FormTest {
required_array,
} => {
info!("Performing a FormTest request");

let result = client.form_test(
required_array.as_ref(),
).await?;
debug!("Result: {:?}", result);

match result {
FormTestResponse::OK
=> "OK\n".to_string()
,
}
}
Operation::GetWithBooleanParameter {
iambool,
} => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Method | HTTP request | Description
****](default_api.md#) | **GET** /any-of |
****](default_api.md#) | **POST** /callback-with-header |
****](default_api.md#) | **GET** /complex-query-param |
**FormTest**](default_api.md#FormTest) | **POST** /form-test | Test a Form Post
**GetWithBooleanParameter**](default_api.md#GetWithBooleanParameter) | **GET** /get-with-bool |
****](default_api.md#) | **GET** /json-complex-query-param |
****](default_api.md#) | **GET** /mandatory-request-header |
Expand Down Expand Up @@ -122,6 +123,38 @@ No authorization required

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

# **FormTest**
> FormTest(optional)
Test a Form Post

### Required Parameters

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**optional** | **map[string]interface{}** | optional parameters | nil if no parameters

### Optional Parameters
Optional parameters are passed through a map[string]interface{}.

Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**required_array** | [**String**](String.md)| |

### Return type

(empty response body)

### Authorization

No authorization required

### HTTP request headers

- **Content-Type**: application/x-www-form-urlencoded
- **Accept**: Not defined

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

# **GetWithBooleanParameter**
> GetWithBooleanParameter(iambool)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use openapi_v3::{Api, ApiNoContext, Claims, Client, ContextWrapperExt, models,
AnyOfGetResponse,
CallbackWithHeaderPostResponse,
ComplexQueryParamGetResponse,
FormTestResponse,
GetWithBooleanParameterResponse,
JsonComplexQueryParamGetResponse,
MandatoryRequestHeaderGetResponse,
Expand Down Expand Up @@ -66,6 +67,7 @@ fn main() {
"AnyOfGet",
"CallbackWithHeaderPost",
"ComplexQueryParamGet",
"FormTest",
"GetWithBooleanParameter",
"JsonComplexQueryParamGet",
"MandatoryRequestHeaderGet",
Expand Down Expand Up @@ -185,6 +187,12 @@ fn main() {
));
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
},
Some("FormTest") => {
let result = rt.block_on(client.form_test(
Some(&Vec::new())
));
info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has<XSpanIdString>).get().clone());
},
Some("GetWithBooleanParameter") => {
let result = rt.block_on(client.get_with_boolean_parameter(
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ use openapi_v3::{
AnyOfGetResponse,
CallbackWithHeaderPostResponse,
ComplexQueryParamGetResponse,
FormTestResponse,
GetWithBooleanParameterResponse,
JsonComplexQueryParamGetResponse,
MandatoryRequestHeaderGetResponse,
Expand Down Expand Up @@ -166,6 +167,16 @@ impl<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
Err(ApiError("Api-Error: Operation is NOT implemented".into()))
}

/// Test a Form Post
async fn form_test(
&self,
required_array: Option<&Vec<String>>,
context: &C) -> Result<FormTestResponse, ApiError>
{
info!("form_test({:?}) - X-Span-ID: {:?}", required_array, context.get().0.clone());
Err(ApiError("Api-Error: Operation is NOT implemented".into()))
}

async fn get_with_boolean_parameter(
&self,
iambool: bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use crate::{Api,
AnyOfGetResponse,
CallbackWithHeaderPostResponse,
ComplexQueryParamGetResponse,
FormTestResponse,
GetWithBooleanParameterResponse,
JsonComplexQueryParamGetResponse,
MandatoryRequestHeaderGetResponse,
Expand Down Expand Up @@ -670,6 +671,96 @@ impl<S, C> Api<C> for Client<S, C> where
}
}

async fn form_test(
&self,
param_required_array: Option<&Vec<String>>,
context: &C) -> Result<FormTestResponse, ApiError>
{
let mut client_service = self.client_service.clone();
let mut uri = format!(
"{}/form-test",
self.base_path
);

// Query parameters
let query_string = {
let mut query_string = form_urlencoded::Serializer::new("".to_owned());
query_string.finish()
};
if !query_string.is_empty() {
uri += "?";
uri += &query_string;
}

let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))),
};

let mut request = match Request::builder()
.method("POST")
.uri(uri)
.body(Body::empty()) {
Ok(req) => req,
Err(e) => return Err(ApiError(format!("Unable to create request: {}", e)))
};

// Consumes form body
let mut params = vec![];
if let Some(param_required_array) = param_required_array {
// style=form,explode=true
for param_required_array in param_required_array {
params.push(("requiredArray",
format!("{:?}", param_required_array)
));
}
}

let body = serde_urlencoded::to_string(params).expect("impossible to fail to serialize");

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

let header = "application/x-www-form-urlencoded";
request.headers_mut().insert(CONTENT_TYPE, match HeaderValue::from_str(header) {
Ok(h) => h,
Err(e) => return Err(ApiError(format!("Unable to create header: {} - {}", header, e)))
});

let header = HeaderValue::from_str(Has::<XSpanIdString>::get(context).0.as_str());
request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header {
Ok(h) => h,
Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e)))
});

let response = client_service.call((request, context.clone()))
.map_err(|e| ApiError(format!("No response received: {}", e))).await?;

match response.status().as_u16() {
200 => {
Ok(
FormTestResponse::OK
)
}
code => {
let headers = response.headers().clone();
let body = response.into_body()
.take(100)
.into_raw().await;
Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}",
code,
headers,
match body {
Ok(body) => match String::from_utf8(body) {
Ok(body) => body,
Err(e) => format!("<Body was not UTF8: {:?}>", e),
},
Err(e) => format!("<Failed to read body: {}>", e),
}
)))
}
}
}

async fn get_with_boolean_parameter(
&self,
param_iambool: bool,
Expand Down
Loading

0 comments on commit fb36272

Please sign in to comment.