[rust-server] Changed query parameters to be url encoded (#2136)

Based on this [issue](https://github.com/servo/rust-url/issues/416) the `utf8_percent_encode` doesn't percent encode `+` instead treats it as a space which is may cause a problem if the query parameters contain one. For example if we wanted to use a DateTime range in query parameters:

`{base_path}{path}?start=2019-02-12T00:00:00+00:00&end=2019-02-13T15:00:00+00:00`

These parameters may be decoded as:

```
start = 2019-02-12T00:00:00 00:00
end = 2019-02-13T15:00:00 00:00
```

To solve this I changed query parameters to be form-urlencoded using `url::form_urlencoded` based on what's done in [`rust` client generator](ea08106c80/modules/openapi-generator/src/main/resources/rust/request.rs (L106)).
This commit is contained in:
Luis Alejandro Herrera León 2019-02-15 05:18:08 -08:00 committed by Benjamin Gill
parent 7cb6a38150
commit cefd29b6ff
3 changed files with 129 additions and 160 deletions

View File

@ -25,6 +25,7 @@ use std::path::Path;
use std::sync::Arc;
use std::str;
use std::str::FromStr;
use std::string::ToString;
use mimetypes;
@ -246,18 +247,24 @@ impl<F, C> Api<C> for Client<F> where
C: Has<XSpanIdString> {{#hasAuthMethods}}+ Has<Option<AuthData>>{{/hasAuthMethods}}{
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
fn {{#vendorExtensions}}{{{operation_id}}}{{/vendorExtensions}}(&self{{#allParams}}, param_{{{paramName}}}: {{^required}}Option<{{/required}}{{#isListContainer}}&{{/isListContainer}}{{{dataType}}}{{^required}}>{{/required}}{{/allParams}}, context: &C) -> Box<Future<Item={{{operationId}}}Response, Error=ApiError>> {
{{#queryParams}}{{#-first}}
// Query parameters
{{/-first}}{{#required}} let query_{{{paramName}}} = format!("{{{baseName}}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{{paramName}}}=param_{{{paramName}}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
{{/required}}{{^required}} let query_{{{paramName}}} = param_{{{paramName}}}.map_or_else(String::new, |query| format!("{{{baseName}}}={{=<% %>=}}{<% paramName %>}<%={{ }}=%>&", {{{paramName}}}=query{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}}));
{{/required}}{{/queryParams}}
let uri = format!(
"{}{{{basePathWithoutHost}}}{{path}}{{#queryParams}}{{#-first}}?{{/-first}}{{=<% %>=}}{<% paramName %>}<%={{ }}=%>{{/queryParams}}",
self.base_path{{#pathParams}}, {{{baseName}}}=utf8_percent_encode(&param_{{{paramName}}}.to_string(), ID_ENCODE_SET){{/pathParams}}{{#queryParams}},
{{{paramName}}}=utf8_percent_encode(&query_{{{paramName}}}, QUERY_ENCODE_SET){{/queryParams}}
let mut uri = format!(
"{}{{{basePathWithoutHost}}}{{path}}",
self.base_path{{#pathParams}}, {{{baseName}}}=utf8_percent_encode(&param_{{{paramName}}}.to_string(), ID_ENCODE_SET){{/pathParams}}
);
{{#queryParams}}{{#-first}}
// Query parameters
let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
{{/-first}}{{#required}} query_string.append_pair("{{{baseName}}}", &param_{{{paramName}}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
{{/required}}{{^required}} if let Some({{{paramName}}}) = param_{{{paramName}}} {
query_string.append_pair("{{{baseName}}}", &{{{paramName}}}{{#isListContainer}}.join(","){{/isListContainer}}{{^isListContainer}}.to_string(){{/isListContainer}});
}
{{/required}}{{#-last}}
let query_string_str = query_string.finish();
if !query_string_str.is_empty() {
uri += "?";
uri += &query_string_str;
}
{{/-last}}{{/queryParams}}
let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
Err(err) => return Box::new(futures::done(Err(ApiError(format!("Unable to build URI: {}", err))))),

View File

@ -25,6 +25,7 @@ use std::path::Path;
use std::sync::Arc;
use std::str;
use std::str::FromStr;
use std::string::ToString;
use mimetypes;
@ -277,9 +278,7 @@ impl<F, C> Api<C> for Client<F> where
C: Has<XSpanIdString> + Has<Option<AuthData>>{
fn test_special_tags(&self, param_body: models::Client, context: &C) -> Box<Future<Item=TestSpecialTagsResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/another-fake/dummy",
self.base_path
);
@ -352,9 +351,7 @@ impl<F, C> Api<C> for Client<F> where
}
fn fake_outer_boolean_serialize(&self, param_body: Option<bool>, context: &C) -> Box<Future<Item=FakeOuterBooleanSerializeResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake/outer/boolean",
self.base_path
);
@ -429,9 +426,7 @@ if let Some(body) = body {
}
fn fake_outer_composite_serialize(&self, param_body: Option<models::OuterComposite>, context: &C) -> Box<Future<Item=FakeOuterCompositeSerializeResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake/outer/composite",
self.base_path
);
@ -504,9 +499,7 @@ if let Some(body) = body {
}
fn fake_outer_number_serialize(&self, param_body: Option<f64>, context: &C) -> Box<Future<Item=FakeOuterNumberSerializeResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake/outer/number",
self.base_path
);
@ -579,9 +572,7 @@ if let Some(body) = body {
}
fn fake_outer_string_serialize(&self, param_body: Option<String>, context: &C) -> Box<Future<Item=FakeOuterStringSerializeResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake/outer/string",
self.base_path
);
@ -654,16 +645,20 @@ if let Some(body) = body {
}
fn test_body_with_query_params(&self, param_query: String, param_body: models::User, context: &C) -> Box<Future<Item=TestBodyWithQueryParamsResponse, Error=ApiError>> {
// Query parameters
let query_query = format!("query={query}&", query=param_query.to_string());
let uri = format!(
"{}/v2/fake/body-with-query-params?{query}",
self.base_path,
query=utf8_percent_encode(&query_query, QUERY_ENCODE_SET)
let mut uri = format!(
"{}/v2/fake/body-with-query-params",
self.base_path
);
// Query parameters
let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
query_string.append_pair("query", &param_query.to_string());
let query_string_str = query_string.finish();
if !query_string_str.is_empty() {
uri += "?";
uri += &query_string_str;
}
let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
@ -721,9 +716,7 @@ if let Some(body) = body {
}
fn test_client_model(&self, param_body: models::Client, context: &C) -> Box<Future<Item=TestClientModelResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake",
self.base_path
);
@ -794,9 +787,7 @@ if let Some(body) = body {
}
fn test_endpoint_parameters(&self, param_number: f64, param_double: f64, param_pattern_without_delimiter: String, param_byte: swagger::ByteArray, param_integer: Option<i32>, param_int32: Option<i32>, param_int64: Option<i64>, param_float: Option<f32>, param_string: Option<String>, param_binary: Option<swagger::ByteArray>, param_date: Option<chrono::DateTime<chrono::Utc>>, param_date_time: Option<chrono::DateTime<chrono::Utc>>, param_password: Option<String>, param_callback: Option<String>, context: &C) -> Box<Future<Item=TestEndpointParametersResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake",
self.base_path
);
@ -885,22 +876,31 @@ if let Some(body) = body {
}
fn test_enum_parameters(&self, param_enum_header_string_array: Option<&Vec<String>>, param_enum_header_string: Option<String>, param_enum_query_string_array: Option<&Vec<String>>, param_enum_query_string: Option<String>, param_enum_query_integer: Option<i32>, param_enum_query_double: Option<f64>, param_enum_form_string: Option<String>, context: &C) -> Box<Future<Item=TestEnumParametersResponse, Error=ApiError>> {
// Query parameters
let query_enum_query_string_array = param_enum_query_string_array.map_or_else(String::new, |query| format!("enum_query_string_array={enum_query_string_array}&", enum_query_string_array=query.join(",")));
let query_enum_query_string = param_enum_query_string.map_or_else(String::new, |query| format!("enum_query_string={enum_query_string}&", enum_query_string=query.to_string()));
let query_enum_query_integer = param_enum_query_integer.map_or_else(String::new, |query| format!("enum_query_integer={enum_query_integer}&", enum_query_integer=query.to_string()));
let query_enum_query_double = param_enum_query_double.map_or_else(String::new, |query| format!("enum_query_double={enum_query_double}&", enum_query_double=query.to_string()));
let uri = format!(
"{}/v2/fake?{enum_query_string_array}{enum_query_string}{enum_query_integer}{enum_query_double}",
self.base_path,
enum_query_string_array=utf8_percent_encode(&query_enum_query_string_array, QUERY_ENCODE_SET),
enum_query_string=utf8_percent_encode(&query_enum_query_string, QUERY_ENCODE_SET),
enum_query_integer=utf8_percent_encode(&query_enum_query_integer, QUERY_ENCODE_SET),
enum_query_double=utf8_percent_encode(&query_enum_query_double, QUERY_ENCODE_SET)
let mut uri = format!(
"{}/v2/fake",
self.base_path
);
// Query parameters
let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
if let Some(enum_query_string_array) = param_enum_query_string_array {
query_string.append_pair("enum_query_string_array", &enum_query_string_array.join(","));
}
if let Some(enum_query_string) = param_enum_query_string {
query_string.append_pair("enum_query_string", &enum_query_string.to_string());
}
if let Some(enum_query_integer) = param_enum_query_integer {
query_string.append_pair("enum_query_integer", &enum_query_integer.to_string());
}
if let Some(enum_query_double) = param_enum_query_double {
query_string.append_pair("enum_query_double", &enum_query_double.to_string());
}
let query_string_str = query_string.finish();
if !query_string_str.is_empty() {
uri += "?";
uri += &query_string_str;
}
let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
@ -973,9 +973,7 @@ if let Some(body) = body {
}
fn test_inline_additional_properties(&self, param_param: HashMap<String, String>, context: &C) -> Box<Future<Item=TestInlineAdditionalPropertiesResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake/inline-additionalProperties",
self.base_path
);
@ -1036,9 +1034,7 @@ if let Some(body) = body {
}
fn test_json_form_data(&self, param_param: String, param_param2: String, context: &C) -> Box<Future<Item=TestJsonFormDataResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake/jsonFormData",
self.base_path
);
@ -1100,9 +1096,7 @@ if let Some(body) = body {
}
fn test_classname(&self, param_body: models::Client, context: &C) -> Box<Future<Item=TestClassnameResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/fake_classname_test",
self.base_path
);
@ -1175,9 +1169,7 @@ if let Some(body) = body {
}
fn add_pet(&self, param_body: models::Pet, context: &C) -> Box<Future<Item=AddPetResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/pet",
self.base_path
);
@ -1240,9 +1232,7 @@ if let Some(body) = body {
}
fn delete_pet(&self, param_pet_id: i64, param_api_key: Option<String>, context: &C) -> Box<Future<Item=DeletePetResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/pet/{petId}",
self.base_path, petId=utf8_percent_encode(&param_pet_id.to_string(), ID_ENCODE_SET)
);
@ -1301,16 +1291,20 @@ if let Some(body) = body {
}
fn find_pets_by_status(&self, param_status: &Vec<String>, context: &C) -> Box<Future<Item=FindPetsByStatusResponse, Error=ApiError>> {
// Query parameters
let query_status = format!("status={status}&", status=param_status.join(","));
let uri = format!(
"{}/v2/pet/findByStatus?{status}",
self.base_path,
status=utf8_percent_encode(&query_status, QUERY_ENCODE_SET)
let mut uri = format!(
"{}/v2/pet/findByStatus",
self.base_path
);
// Query parameters
let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
query_string.append_pair("status", &param_status.join(","));
let query_string_str = query_string.finish();
if !query_string_str.is_empty() {
uri += "?";
uri += &query_string_str;
}
let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
@ -1383,16 +1377,20 @@ if let Some(body) = body {
}
fn find_pets_by_tags(&self, param_tags: &Vec<String>, context: &C) -> Box<Future<Item=FindPetsByTagsResponse, Error=ApiError>> {
// Query parameters
let query_tags = format!("tags={tags}&", tags=param_tags.join(","));
let uri = format!(
"{}/v2/pet/findByTags?{tags}",
self.base_path,
tags=utf8_percent_encode(&query_tags, QUERY_ENCODE_SET)
let mut uri = format!(
"{}/v2/pet/findByTags",
self.base_path
);
// Query parameters
let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
query_string.append_pair("tags", &param_tags.join(","));
let query_string_str = query_string.finish();
if !query_string_str.is_empty() {
uri += "?";
uri += &query_string_str;
}
let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
@ -1465,9 +1463,7 @@ if let Some(body) = body {
}
fn get_pet_by_id(&self, param_pet_id: i64, context: &C) -> Box<Future<Item=GetPetByIdResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/pet/{petId}",
self.base_path, petId=utf8_percent_encode(&param_pet_id.to_string(), ID_ENCODE_SET)
);
@ -1552,9 +1548,7 @@ if let Some(body) = body {
}
fn update_pet(&self, param_body: models::Pet, context: &C) -> Box<Future<Item=UpdatePetResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/pet",
self.base_path
);
@ -1633,9 +1627,7 @@ if let Some(body) = body {
}
fn update_pet_with_form(&self, param_pet_id: i64, param_name: Option<String>, param_status: Option<String>, context: &C) -> Box<Future<Item=UpdatePetWithFormResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/pet/{petId}",
self.base_path, petId=utf8_percent_encode(&param_pet_id.to_string(), ID_ENCODE_SET)
);
@ -1697,9 +1689,7 @@ if let Some(body) = body {
}
fn upload_file(&self, param_pet_id: i64, param_additional_metadata: Option<String>, param_file: Option<swagger::ByteArray>, context: &C) -> Box<Future<Item=UploadFileResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/pet/{petId}/uploadImage",
self.base_path, petId=utf8_percent_encode(&param_pet_id.to_string(), ID_ENCODE_SET)
);
@ -1771,9 +1761,7 @@ if let Some(body) = body {
}
fn delete_order(&self, param_order_id: String, context: &C) -> Box<Future<Item=DeleteOrderResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/store/order/{order_id}",
self.base_path, order_id=utf8_percent_encode(&param_order_id.to_string(), ID_ENCODE_SET)
);
@ -1837,9 +1825,7 @@ if let Some(body) = body {
}
fn get_inventory(&self, context: &C) -> Box<Future<Item=GetInventoryResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/store/inventory",
self.base_path
);
@ -1904,9 +1890,7 @@ if let Some(body) = body {
}
fn get_order_by_id(&self, param_order_id: i64, context: &C) -> Box<Future<Item=GetOrderByIdResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/store/order/{order_id}",
self.base_path, order_id=utf8_percent_encode(&param_order_id.to_string(), ID_ENCODE_SET)
);
@ -1991,9 +1975,7 @@ if let Some(body) = body {
}
fn place_order(&self, param_body: models::Order, context: &C) -> Box<Future<Item=PlaceOrderResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/store/order",
self.base_path
);
@ -2075,9 +2057,7 @@ if let Some(body) = body {
}
fn create_user(&self, param_body: models::User, context: &C) -> Box<Future<Item=CreateUserResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/user",
self.base_path
);
@ -2140,9 +2120,7 @@ if let Some(body) = body {
}
fn create_users_with_array_input(&self, param_body: &Vec<models::User>, context: &C) -> Box<Future<Item=CreateUsersWithArrayInputResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/user/createWithArray",
self.base_path
);
@ -2203,9 +2181,7 @@ if let Some(body) = body {
}
fn create_users_with_list_input(&self, param_body: &Vec<models::User>, context: &C) -> Box<Future<Item=CreateUsersWithListInputResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/user/createWithList",
self.base_path
);
@ -2266,9 +2242,7 @@ if let Some(body) = body {
}
fn delete_user(&self, param_username: String, context: &C) -> Box<Future<Item=DeleteUserResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/user/{username}",
self.base_path, username=utf8_percent_encode(&param_username.to_string(), ID_ENCODE_SET)
);
@ -2332,9 +2306,7 @@ if let Some(body) = body {
}
fn get_user_by_name(&self, param_username: String, context: &C) -> Box<Future<Item=GetUserByNameResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/user/{username}",
self.base_path, username=utf8_percent_encode(&param_username.to_string(), ID_ENCODE_SET)
);
@ -2419,18 +2391,21 @@ if let Some(body) = body {
}
fn login_user(&self, param_username: String, param_password: String, context: &C) -> Box<Future<Item=LoginUserResponse, Error=ApiError>> {
// Query parameters
let query_username = format!("username={username}&", username=param_username.to_string());
let query_password = format!("password={password}&", password=param_password.to_string());
let uri = format!(
"{}/v2/user/login?{username}{password}",
self.base_path,
username=utf8_percent_encode(&query_username, QUERY_ENCODE_SET),
password=utf8_percent_encode(&query_password, QUERY_ENCODE_SET)
let mut uri = format!(
"{}/v2/user/login",
self.base_path
);
// Query parameters
let mut query_string = self::url::form_urlencoded::Serializer::new("".to_owned());
query_string.append_pair("username", &param_username.to_string());
query_string.append_pair("password", &param_password.to_string());
let query_string_str = query_string.finish();
if !query_string_str.is_empty() {
uri += "?";
uri += &query_string_str;
}
let uri = match Uri::from_str(&uri) {
Ok(uri) => uri,
@ -2513,9 +2488,7 @@ if let Some(body) = body {
}
fn logout_user(&self, context: &C) -> Box<Future<Item=LogoutUserResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/user/logout",
self.base_path
);
@ -2570,9 +2543,7 @@ if let Some(body) = body {
}
fn update_user(&self, param_username: String, param_body: models::User, context: &C) -> Box<Future<Item=UpdateUserResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/v2/user/{username}",
self.base_path, username=utf8_percent_encode(&param_username.to_string(), ID_ENCODE_SET)
);

View File

@ -25,6 +25,7 @@ use std::path::Path;
use std::sync::Arc;
use std::str;
use std::str::FromStr;
use std::string::ToString;
use mimetypes;
@ -250,9 +251,7 @@ impl<F, C> Api<C> for Client<F> where
C: Has<XSpanIdString> {
fn dummy_get(&self, context: &C) -> Box<Future<Item=DummyGetResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/dummy",
self.base_path
);
@ -307,9 +306,7 @@ impl<F, C> Api<C> for Client<F> where
}
fn dummy_put(&self, param_nested_response: models::InlineObject, context: &C) -> Box<Future<Item=DummyPutResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/dummy",
self.base_path
);
@ -370,9 +367,7 @@ impl<F, C> Api<C> for Client<F> where
}
fn file_response_get(&self, context: &C) -> Box<Future<Item=FileResponseGetResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/file_response",
self.base_path
);
@ -437,9 +432,7 @@ impl<F, C> Api<C> for Client<F> where
}
fn html_post(&self, param_body: String, context: &C) -> Box<Future<Item=HtmlPostResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/html",
self.base_path
);
@ -508,9 +501,7 @@ impl<F, C> Api<C> for Client<F> where
}
fn raw_json_get(&self, context: &C) -> Box<Future<Item=RawJsonGetResponse, Error=ApiError>> {
let uri = format!(
let mut uri = format!(
"{}/raw_json",
self.base_path
);