Skip to content

Feat/rust reqwest shallow object query param #21321

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

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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
9 changes: 9 additions & 0 deletions bin/configs/rust-reqwest-object-query-param.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
generatorName: rust
outputDir: samples/client/others/rust/reqwest/object-query-param
library: reqwest
inputSpec: modules/openapi-generator/src/test/resources/3_0/objectQueryParam.yaml
templateDir: modules/openapi-generator/src/main/resources/rust
validateSpec: "true"
additionalProperties:
supportAsync: "false"
packageName: object-query-param-reqwest
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,18 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration:
{{/isArray}}
{{^isArray}}
{{^isNullable}}
{{#isModel}}
{{#isDeepObject}}
let params = crate::apis::parse_deep_object("{{{baseName}}}", &serde_json::to_value(param_value)?);
{{/isDeepObject}}
{{^isDeepObject}}
let params = crate::apis::parse_flat_object(&serde_json::to_value({{{vendorExtensions.x-rust-param-identifier}}})?);
{{/isDeepObject}}
req_builder = req_builder.query(&params);
{{/isModel}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct me If I am wrong @wing328: isModel here does not guarantee non-nested?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isModel can have nested properties (properties that are models)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.

There's an issue though, it seems like the isDeepObject flag is not correctly set for nested objects. I've added a couple test cases to the PR.

@wing328 am I doing something wrong here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wing328 ping!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think isDeepObject also does not guarantee non-nested.

Generated code from @whirm integration test:

pub fn list(configuration: &configuration::Configuration, page_query: models::ListPageQueryParameter, page_query_schema: models::Page, deep: models::ListDeepParameter, not_required: Option<models::ListNotRequiredParameter>, not_required_deep: Option<models::ListNotRequiredDeepParameter>)
...
if let Some(ref param_value) = p_not_required_deep {
              let params = crate::apis::parse_flat_object(&serde_json::to_value(param_value)?);
              req_builder = req_builder.query(&params);
    }
...

However, regarding specs, it's nested object:

- in: query
          name: notRequiredDeep
          required: false
          schema:
            type: object
            properties:
              foo:
                type: object
                properties:
                  baz:
                    type: integer
                    format: int32
              bar:
                type: integer
                format: int32

{{^isModel}}
req_builder = req_builder.query(&[("{{{baseName}}}", &{{{vendorExtensions.x-rust-param-identifier}}}.to_string())]);
{{/isModel}}
{{/isNullable}}
{{#isNullable}}
{{#isDeepObject}}
Expand Down Expand Up @@ -232,7 +243,8 @@ pub {{#supportAsync}}async {{/supportAsync}}fn {{{operationId}}}(configuration:
req_builder = req_builder.query(&[("{{{baseName}}}", &serde_json::to_string(param_value)?)]);
{{/isObject}}
{{#isModel}}
req_builder = req_builder.query(&[("{{{baseName}}}", &serde_json::to_string(param_value)?)]);
let params = crate::apis::parse_flat_object(&serde_json::to_value(param_value)?);
req_builder = req_builder.query(&params);
{{/isModel}}
{{^isObject}}
{{^isModel}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,33 @@ pub fn urlencode<T: AsRef<str>>(s: T) -> String {
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}

pub fn parse_flat_object(value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];

for (key, value) in object {
match value {
serde_json::Value::Object(_) => {
unreachable!(
"This should never be reached, there's a bug on the codegen or the templates"
)
}
serde_json::Value::Array(array) => {
for (i, value) in array.iter().enumerate() {
params.push((format!("{key}[{i}]"), value.to_string()));
}
}
serde_json::Value::String(s) => params.push((key.to_string(), s.clone())),
_ => params.push((key.to_string(), value.to_string())),
}
}

return params;
}

unimplemented!("Only objects are supported")
}

pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ paths:
parameters:
- in: query
name: pageQuery
required: true
schema:
type: object
properties:
Expand All @@ -22,4 +23,68 @@ paths:
format: int32
limit:
type: integer
format: int32
format: int32
- in: query
name: notRequired
required: false
schema:
type: object
properties:
foobar:
type: integer
format: int32

- in: query
name: pageQuerySchema
required: true
schema:
$ref: '#/components/schemas/Page'
- in: query
name: deep
required: true
schema:
type: object
required:
- foo
- bar
properties:
foo:
type: object
required:
- baz
properties:
baz:
type: integer
format: int32
bar:
type: integer
format: int32
- in: query
name: notRequiredDeep
required: false
schema:
type: object
properties:
foo:
type: object
properties:
baz:
type: integer
format: int32
bar:
type: integer
format: int32
responses:
'200':
description: OK
components:
schemas:
Page:
type: object
properties:
spage:
type: integer
format: int32
sperPage:
type: integer
format: int32
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ pub fn urlencode<T: AsRef<str>>(s: T) -> String {
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}

pub fn parse_flat_object(value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];

for (key, value) in object {
match value {
serde_json::Value::Object(_) => {
unreachable!(
"This should never be reached, there's a bug on the codegen or the templates"
)
}
serde_json::Value::Array(array) => {
for (i, value) in array.iter().enumerate() {
params.push((format!("{key}[{i}]"), value.to_string()));
}
}
serde_json::Value::String(s) => params.push((key.to_string(), s.clone())),
_ => params.push((key.to_string(), value.to_string())),
}
}

return params;
}

unimplemented!("Only objects are supported")
}

pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ pub fn urlencode<T: AsRef<str>>(s: T) -> String {
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}

pub fn parse_flat_object(value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];

for (key, value) in object {
match value {
serde_json::Value::Object(_) => {
unreachable!(
"This should never be reached, there's a bug on the codegen or the templates"
)
}
serde_json::Value::Array(array) => {
for (i, value) in array.iter().enumerate() {
params.push((format!("{key}[{i}]"), value.to_string()));
}
}
serde_json::Value::String(s) => params.push((key.to_string(), s.clone())),
_ => params.push((key.to_string(), value.to_string())),
}
}

return params;
}

unimplemented!("Only objects are supported")
}

pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];
Expand Down
27 changes: 27 additions & 0 deletions samples/client/others/rust/reqwest/composed-oneof/src/apis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ pub fn urlencode<T: AsRef<str>>(s: T) -> String {
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}

pub fn parse_flat_object(value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];

for (key, value) in object {
match value {
serde_json::Value::Object(_) => {
unreachable!(
"This should never be reached, there's a bug on the codegen or the templates"
)
}
serde_json::Value::Array(array) => {
for (i, value) in array.iter().enumerate() {
params.push((format!("{key}[{i}]"), value.to_string()));
}
}
serde_json::Value::String(s) => params.push((key.to_string(), s.clone())),
_ => params.push((key.to_string(), value.to_string())),
}
}

return params;
}

unimplemented!("Only objects are supported")
}

pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];
Expand Down
27 changes: 27 additions & 0 deletions samples/client/others/rust/reqwest/emptyObject/src/apis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ pub fn urlencode<T: AsRef<str>>(s: T) -> String {
::url::form_urlencoded::byte_serialize(s.as_ref().as_bytes()).collect()
}

pub fn parse_flat_object(value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];

for (key, value) in object {
match value {
serde_json::Value::Object(_) => {
unreachable!(
"This should never be reached, there's a bug on the codegen or the templates"
)
}
serde_json::Value::Array(array) => {
for (i, value) in array.iter().enumerate() {
params.push((format!("{key}[{i}]"), value.to_string()));
}
}
serde_json::Value::String(s) => params.push((key.to_string(), s.clone())),
_ => params.push((key.to_string(), value.to_string())),
}
}

return params;
}

unimplemented!("Only objects are supported")
}

pub fn parse_deep_object(prefix: &str, value: &serde_json::Value) -> Vec<(String, String)> {
if let serde_json::Value::Object(object) = value {
let mut params = vec![];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target/
**/*.rs.bk
Cargo.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator

# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.

# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs

# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux

# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux

# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.gitignore
.travis.yml
Cargo.toml
README.md
docs/DefaultApi.md
docs/ListDeepParameter.md
docs/ListDeepParameterFoo.md
docs/ListNotRequiredDeepParameter.md
docs/ListNotRequiredDeepParameterFoo.md
docs/ListNotRequiredParameter.md
docs/ListPageQueryParameter.md
docs/Page.md
git_push.sh
src/apis/configuration.rs
src/apis/default_api.rs
src/apis/mod.rs
src/lib.rs
src/models/list_deep_parameter.rs
src/models/list_deep_parameter_foo.rs
src/models/list_not_required_deep_parameter.rs
src/models/list_not_required_deep_parameter_foo.rs
src/models/list_not_required_parameter.rs
src/models/list_page_query_parameter.rs
src/models/mod.rs
src/models/page.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
7.14.0-SNAPSHOT
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
language: rust
14 changes: 14 additions & 0 deletions samples/client/others/rust/reqwest/object-query-param/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "object-query-param-reqwest"
version = "1.0.0"
authors = ["OpenAPI Generator team and contributors"]
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
license = "Apache-2.0"
edition = "2021"

[dependencies]
serde = { version = "^1.0", features = ["derive"] }
serde_json = "^1.0"
serde_repr = "^0.1"
url = "^2.5"
reqwest = { version = "^0.12", default-features = false, features = ["json", "blocking", "multipart"] }
Loading
Loading