Skip to content

[Rust Server] Handle arrays in forms #19625

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -774,6 +787,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
Loading