From c5963b6f34e0d7fa0a853b70d0679cd6e99165d5 Mon Sep 17 00:00:00 2001 From: Mert Yildiz Date: Fri, 3 May 2024 14:13:06 +0200 Subject: [PATCH 1/5] fix: Fix uuid in header params causing errors Routes with header parameters with a `format` of `uuid` in the openAPI specification used to cause a compilation error in the resulting Rust Axum code. This commit fixes the issue by including the correct conversion trait implementation on the condition that at least one header parameter of `format` `uuid` is included in the specification. --- .../languages/RustAxumServerCodegen.java | 7 ++++ .../main/resources/rust-axum/header.mustache | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java index 1efb600d2785..3944e7e0f175 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java @@ -45,6 +45,7 @@ import java.io.File; import java.io.IOException; +import java.io.ObjectInputFilter.Config; import java.math.BigInteger; import java.nio.file.Path; import java.util.*; @@ -552,6 +553,12 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation } } + // Include renderUuidConversionImpl exactly once in the vendorExtensions map when + // at least one `uuid::Uuid` converted from a header value in the resulting Rust code. + Boolean renderUuidConversionImpl = op.headerParams.stream().anyMatch(h -> h.getDataType().equals(uuidType)); + if (renderUuidConversionImpl) { + additionalProperties.put("renderUuidConversionImpl", "true"); + } return op; } diff --git a/modules/openapi-generator/src/main/resources/rust-axum/header.mustache b/modules/openapi-generator/src/main/resources/rust-axum/header.mustache index 4d1cc4c6dccd..453281aea734 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/header.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/header.mustache @@ -178,3 +178,39 @@ impl TryFrom>> for HeaderValue { } } } + +{{#renderUuidConversionImpl}} +// uuid::Uuid + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match uuid::Uuid::from_str(hdr_value) { + Ok(uuid) => Ok(IntoHeaderValue(uuid)), + Err(e) => Err(format!("Unable to parse: {} as uuid - {}", hdr_value, e)), + }, + Err(e) => Err(format!( + "Unable to convert header {:?} to string {}", + hdr_value, e + )), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_bytes(hdr_value.0.as_bytes()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} to a header: {}", + hdr_value, e + )), + } + } +} + +{{/renderUuidConversionImpl}} From 7a83cbb6c23a0d29f263bbbad9126490efbf9ba9 Mon Sep 17 00:00:00 2001 From: Mert Yildiz Date: Sat, 4 May 2024 15:43:26 +0200 Subject: [PATCH 2/5] refactor: Add final to boolean --- .../openapitools/codegen/languages/RustAxumServerCodegen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java index 3944e7e0f175..7767de8b6234 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java @@ -555,7 +555,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation // Include renderUuidConversionImpl exactly once in the vendorExtensions map when // at least one `uuid::Uuid` converted from a header value in the resulting Rust code. - Boolean renderUuidConversionImpl = op.headerParams.stream().anyMatch(h -> h.getDataType().equals(uuidType)); + final Boolean renderUuidConversionImpl = op.headerParams.stream().anyMatch(h -> h.getDataType().equals(uuidType)); if (renderUuidConversionImpl) { additionalProperties.put("renderUuidConversionImpl", "true"); } From 6bee82e85a54a55b4ad4aa5905617e518ce952aa Mon Sep 17 00:00:00 2001 From: Mert Yildiz Date: Sat, 4 May 2024 15:44:20 +0200 Subject: [PATCH 3/5] fix: Bring str::FromStr optionally into scope The trait needs to be in scope for the TryFrom implementation: `TryFrom for IntoHeaderValue ` It will only be brought into scope when the implementation is rendered. --- .../src/main/resources/rust-axum/header.mustache | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/resources/rust-axum/header.mustache b/modules/openapi-generator/src/main/resources/rust-axum/header.mustache index 453281aea734..36bfcf45f7b7 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/header.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/header.mustache @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, fmt, ops::Deref}; +use std::{convert::TryFrom, fmt, ops::Deref{{#renderUuidConversionImpl}}, str::FromStr{{/renderUuidConversionImpl}}}; use chrono::{DateTime, Utc}; use http::HeaderValue; From 97337f2a1c7831b7779c452ceebb6aa9216043cd Mon Sep 17 00:00:00 2001 From: Mert Yildiz Date: Sun, 5 May 2024 14:26:18 +0200 Subject: [PATCH 4/5] test: Add integration test and its specification This commit adds an integration test that tests the bug fix for #18554. A header parameter of `format: uuid` is included in one route. This makes the example create a route handler that tries to extract a Rust `uuid::Uuid` type from the header. The integration test will check that the generated code compiles. --- bin/configs/manual/rust-axum-header-uuid.yaml | 9 ++++++ .../3_0/rust-axum/rust-axum-header-uuid.yaml | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 bin/configs/manual/rust-axum-header-uuid.yaml create mode 100644 modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-header-uuid.yaml diff --git a/bin/configs/manual/rust-axum-header-uuid.yaml b/bin/configs/manual/rust-axum-header-uuid.yaml new file mode 100644 index 000000000000..7e04339b464d --- /dev/null +++ b/bin/configs/manual/rust-axum-header-uuid.yaml @@ -0,0 +1,9 @@ +generatorName: rust-axum +outputDir: samples/server/petstore/rust-axum/output/rust-axum-header-uuid +inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-header-uuid.yaml +templateDir: modules/openapi-generator/src/main/resources/rust-axum +generateAliasAsModel: true +additionalProperties: + hideGenerationTimestamp: "true" + packageName: rust-axum-header-uui +enablePostProcessFile: true diff --git a/modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-header-uuid.yaml b/modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-header-uuid.yaml new file mode 100644 index 000000000000..e2aee6440aa2 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-header-uuid.yaml @@ -0,0 +1,32 @@ +# Test deserialization of uuid objects in headers +openapi: 3.0.0 +info: + title: Sample API + version: 0.1.9 +paths: + /users: + post: + description: Adds a user to the users database table. + parameters: + - $ref: "#/components/parameters/UuidHeaderParam" + responses: + "201": # status code + description: Added row to table! + content: + application/json: + schema: + type: string +components: + schemas: + HeaderUuid: + type: string + format: uuid + example: a9f5a638-728c-479d-af9b-016eb8049ab6 + parameters: + UuidHeaderParam: + name: some_uid + in: header + required: true + description: A uuid transmitted in the header. + schema: + $ref: "#/components/schemas/HeaderUuid" \ No newline at end of file From a8e06cd5f86090c6d9c9c10e46535a3a740cd782 Mon Sep 17 00:00:00 2001 From: Mert Yildiz Date: Sun, 5 May 2024 14:28:48 +0200 Subject: [PATCH 5/5] test: Update examples and run integration test The generated samples are updated with: `./bin/generate-samples.sh ./bin/configs/manual/*.yaml` Most example projects have their version numbers bumped. Some changes show, that there are some other unrelated changes to the files, which indicates that some prior commit did not update the samples accordingly. The relevant integration test `mvn integration-test -f samples/server/petstore/rust-axum/pom.xml` passes. --- .../multipart-v3/.openapi-generator/VERSION | 2 +- .../rust-axum/output/multipart-v3/README.md | 2 +- .../openapi-v3/.openapi-generator/VERSION | 2 +- .../rust-axum/output/openapi-v3/README.md | 2 +- .../output/ops-v3/.openapi-generator/VERSION | 2 +- .../rust-axum/output/ops-v3/README.md | 2 +- .../.openapi-generator/VERSION | 2 +- .../README.md | 2 +- .../src/lib.rs | 4 + .../src/models.rs | 930 ++++++++++++++++++ .../src/server/mod.rs | 97 +- .../petstore/.openapi-generator/VERSION | 2 +- .../rust-axum/output/petstore/README.md | 2 +- .../rust-axum/output/petstore/src/lib.rs | 1 + .../rust-axum/output/petstore/src/models.rs | 311 ++++++ .../output/petstore/src/server/mod.rs | 29 +- .../.openapi-generator/VERSION | 2 +- .../output/ping-bearer-auth/README.md | 2 +- .../output/rust-axum-header-uuid/.gitignore | 2 + .../.openapi-generator-ignore | 23 + .../.openapi-generator/FILES | 9 + .../.openapi-generator/VERSION | 1 + .../output/rust-axum-header-uuid/Cargo.toml | 46 + .../output/rust-axum-header-uuid/README.md | 91 ++ .../rust-axum-header-uuid/src/header.rs | 230 +++++ .../output/rust-axum-header-uuid/src/lib.rs | 58 ++ .../rust-axum-header-uuid/src/models.rs | 49 + .../rust-axum-header-uuid/src/server/mod.rs | 139 +++ .../output/rust-axum-header-uuid/src/types.rs | 665 +++++++++++++ .../rust-axum-test/.openapi-generator/VERSION | 2 +- .../rust-axum/output/rust-axum-test/README.md | 2 +- 31 files changed, 2675 insertions(+), 38 deletions(-) create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.gitignore create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator-ignore create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/FILES create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/Cargo.toml create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/header.rs create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/lib.rs create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/models.rs create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/server/mod.rs create mode 100644 samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/types.rs diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION index 08bfd0643b8c..ecb21862b1ee 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION @@ -1 +1 @@ -7.5.0-SNAPSHOT +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/README.md b/samples/server/petstore/rust-axum/output/multipart-v3/README.md index f4f456c04bd6..68f1c75bf6ca 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/README.md +++ b/samples/server/petstore/rust-axum/output/multipart-v3/README.md @@ -12,7 +12,7 @@ server, you can easily generate a server stub. To see how to make this your own, look here: [README]((https://openapi-generator.tech)) - API version: 1.0.7 -- Generator version: 7.5.0-SNAPSHOT +- Generator version: 7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION index 08bfd0643b8c..ecb21862b1ee 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION @@ -1 +1 @@ -7.5.0-SNAPSHOT +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/README.md b/samples/server/petstore/rust-axum/output/openapi-v3/README.md index cce3ce70309e..20b9a33bcac4 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/README.md +++ b/samples/server/petstore/rust-axum/output/openapi-v3/README.md @@ -12,7 +12,7 @@ server, you can easily generate a server stub. To see how to make this your own, look here: [README]((https://openapi-generator.tech)) - API version: 1.0.7 -- Generator version: 7.5.0-SNAPSHOT +- Generator version: 7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION index 08bfd0643b8c..ecb21862b1ee 100644 --- a/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION @@ -1 +1 @@ -7.5.0-SNAPSHOT +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/ops-v3/README.md b/samples/server/petstore/rust-axum/output/ops-v3/README.md index 3cf44f6b0230..9be673f94408 100644 --- a/samples/server/petstore/rust-axum/output/ops-v3/README.md +++ b/samples/server/petstore/rust-axum/output/ops-v3/README.md @@ -12,7 +12,7 @@ server, you can easily generate a server stub. To see how to make this your own, look here: [README]((https://openapi-generator.tech)) - API version: 0.0.1 -- Generator version: 7.5.0-SNAPSHOT +- Generator version: 7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION index 08bfd0643b8c..ecb21862b1ee 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION @@ -1 +1 @@ -7.5.0-SNAPSHOT +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md index e48c2df3d016..69a87cb8bb12 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md @@ -12,7 +12,7 @@ server, you can easily generate a server stub. To see how to make this your own, look here: [README]((https://openapi-generator.tech)) - API version: 1.0.0 -- Generator version: 7.5.0-SNAPSHOT +- Generator version: 7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs index 5cc0e76a1a53..4d017097f1ae 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/lib.rs @@ -445,6 +445,7 @@ pub trait Api { method: Method, host: Host, cookies: CookieJar, + body: models::TestEndpointParametersRequest, ) -> Result; /// To test enum parameters. @@ -457,6 +458,7 @@ pub trait Api { cookies: CookieJar, header_params: models::TestEnumParametersHeaderParams, query_params: models::TestEnumParametersQueryParams, + body: Option, ) -> Result; /// test inline additionalProperties. @@ -478,6 +480,7 @@ pub trait Api { method: Method, host: Host, cookies: CookieJar, + body: models::TestJsonFormDataRequest, ) -> Result; /// To test class name in snake case. @@ -567,6 +570,7 @@ pub trait Api { host: Host, cookies: CookieJar, path_params: models::UpdatePetWithFormPathParams, + body: Option, ) -> Result; /// uploads an image. diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/models.rs b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/models.rs index ccd8f72ef78c..591189ae1f1d 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/models.rs +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/models.rs @@ -5521,6 +5521,936 @@ impl std::convert::TryFrom for header::IntoHeaderValue { } } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct TestEndpointParametersRequest { + /// None + #[serde(rename = "integer")] + #[validate(range(min = 10, max = 100))] + #[serde(skip_serializing_if = "Option::is_none")] + pub integer: Option, + + /// None + #[serde(rename = "int32")] + #[validate(range(min = 20, max = 200))] + #[serde(skip_serializing_if = "Option::is_none")] + pub int32: Option, + + /// None + #[serde(rename = "int64")] + #[serde(skip_serializing_if = "Option::is_none")] + pub int64: Option, + + /// None + #[serde(rename = "number")] + #[validate(range(min = 32.1, max = 543.2))] + pub number: f64, + + /// None + #[serde(rename = "float")] + #[validate(range(max = 987.6))] + #[serde(skip_serializing_if = "Option::is_none")] + pub float: Option, + + /// None + #[serde(rename = "double")] + #[validate(range(min = 67.8, max = 123.4))] + pub double: f64, + + /// None + #[serde(rename = "string")] + #[validate( + regex(path = *RE_TESTENDPOINTPARAMETERSREQUEST_STRING), + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub string: Option, + + /// None + #[serde(rename = "pattern_without_delimiter")] + #[validate( + regex(path = *RE_TESTENDPOINTPARAMETERSREQUEST_PATTERN_WITHOUT_DELIMITER), + )] + pub pattern_without_delimiter: String, + + /// None + #[serde(rename = "byte")] + pub byte: ByteArray, + + /// None + #[serde(rename = "binary")] + #[serde(skip_serializing_if = "Option::is_none")] + pub binary: Option, + + /// None + #[serde(rename = "date")] + #[serde(skip_serializing_if = "Option::is_none")] + pub date: Option, + + /// None + #[serde(rename = "dateTime")] + #[serde(skip_serializing_if = "Option::is_none")] + pub date_time: Option>, + + /// None + #[serde(rename = "password")] + #[validate(length(min = 10, max = 64))] + #[serde(skip_serializing_if = "Option::is_none")] + pub password: Option, + + /// None + #[serde(rename = "callback")] + #[serde(skip_serializing_if = "Option::is_none")] + pub callback: Option, +} + +lazy_static::lazy_static! { + static ref RE_TESTENDPOINTPARAMETERSREQUEST_STRING: regex::Regex = regex::Regex::new(r"/[a-z]/i").unwrap(); +} +lazy_static::lazy_static! { + static ref RE_TESTENDPOINTPARAMETERSREQUEST_PATTERN_WITHOUT_DELIMITER: regex::Regex = regex::Regex::new(r"^[A-Z].*").unwrap(); +} + +impl TestEndpointParametersRequest { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new( + number: f64, + double: f64, + pattern_without_delimiter: String, + byte: ByteArray, + ) -> TestEndpointParametersRequest { + TestEndpointParametersRequest { + integer: None, + int32: None, + int64: None, + number, + float: None, + double, + string: None, + pattern_without_delimiter, + byte, + binary: None, + date: None, + date_time: None, + password: None, + callback: None, + } + } +} + +/// Converts the TestEndpointParametersRequest value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::fmt::Display for TestEndpointParametersRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + self.integer + .as_ref() + .map(|integer| ["integer".to_string(), integer.to_string()].join(",")), + self.int32 + .as_ref() + .map(|int32| ["int32".to_string(), int32.to_string()].join(",")), + self.int64 + .as_ref() + .map(|int64| ["int64".to_string(), int64.to_string()].join(",")), + Some("number".to_string()), + Some(self.number.to_string()), + self.float + .as_ref() + .map(|float| ["float".to_string(), float.to_string()].join(",")), + Some("double".to_string()), + Some(self.double.to_string()), + self.string + .as_ref() + .map(|string| ["string".to_string(), string.to_string()].join(",")), + Some("pattern_without_delimiter".to_string()), + Some(self.pattern_without_delimiter.to_string()), + // Skipping byte in query parameter serialization + // Skipping byte in query parameter serialization + + // Skipping binary in query parameter serialization + // Skipping binary in query parameter serialization + + // Skipping date in query parameter serialization + + // Skipping dateTime in query parameter serialization + self.password + .as_ref() + .map(|password| ["password".to_string(), password.to_string()].join(",")), + self.callback + .as_ref() + .map(|callback| ["callback".to_string(), callback.to_string()].join(",")), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a TestEndpointParametersRequest value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for TestEndpointParametersRequest { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub integer: Vec, + pub int32: Vec, + pub int64: Vec, + pub number: Vec, + pub float: Vec, + pub double: Vec, + pub string: Vec, + pub pattern_without_delimiter: Vec, + pub byte: Vec, + pub binary: Vec, + pub date: Vec, + pub date_time: Vec>, + pub password: Vec, + pub callback: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => { + return std::result::Result::Err( + "Missing value while parsing TestEndpointParametersRequest".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "integer" => intermediate_rep.integer.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "int32" => intermediate_rep.int32.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "int64" => intermediate_rep.int64.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "number" => intermediate_rep.number.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "float" => intermediate_rep.float.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "double" => intermediate_rep.double.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "string" => intermediate_rep.string.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "pattern_without_delimiter" => intermediate_rep.pattern_without_delimiter.push(::from_str(val).map_err(|x| x.to_string())?), + "byte" => return std::result::Result::Err("Parsing binary data in this style is not supported in TestEndpointParametersRequest".to_string()), + "binary" => return std::result::Result::Err("Parsing binary data in this style is not supported in TestEndpointParametersRequest".to_string()), + #[allow(clippy::redundant_clone)] + "date" => intermediate_rep.date.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "dateTime" => intermediate_rep.date_time.push( as std::str::FromStr>::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "password" => intermediate_rep.password.push(::from_str(val).map_err(|x| x.to_string())?), + #[allow(clippy::redundant_clone)] + "callback" => intermediate_rep.callback.push(::from_str(val).map_err(|x| x.to_string())?), + _ => return std::result::Result::Err("Unexpected key while parsing TestEndpointParametersRequest".to_string()) + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(TestEndpointParametersRequest { + integer: intermediate_rep.integer.into_iter().next(), + int32: intermediate_rep.int32.into_iter().next(), + int64: intermediate_rep.int64.into_iter().next(), + number: intermediate_rep + .number + .into_iter() + .next() + .ok_or_else(|| "number missing in TestEndpointParametersRequest".to_string())?, + float: intermediate_rep.float.into_iter().next(), + double: intermediate_rep + .double + .into_iter() + .next() + .ok_or_else(|| "double missing in TestEndpointParametersRequest".to_string())?, + string: intermediate_rep.string.into_iter().next(), + pattern_without_delimiter: intermediate_rep + .pattern_without_delimiter + .into_iter() + .next() + .ok_or_else(|| { + "pattern_without_delimiter missing in TestEndpointParametersRequest".to_string() + })?, + byte: intermediate_rep + .byte + .into_iter() + .next() + .ok_or_else(|| "byte missing in TestEndpointParametersRequest".to_string())?, + binary: intermediate_rep.binary.into_iter().next(), + date: intermediate_rep.date.into_iter().next(), + date_time: intermediate_rep.date_time.into_iter().next(), + password: intermediate_rep.password.into_iter().next(), + callback: intermediate_rep.callback.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for TestEndpointParametersRequest - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => std::result::Result::Ok(header::IntoHeaderValue(value)), + std::result::Result::Err(err) => std::result::Result::Err( + format!("Unable to convert header value '{}' into TestEndpointParametersRequest - {}", + value, err)) + } + }, + std::result::Result::Err(e) => std::result::Result::Err( + format!("Unable to convert header: {:?} to string: {}", + hdr_value, e)) + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct TestEnumParametersRequest { + /// Form parameter enum test (string) + /// Note: inline enums are not fully supported by openapi-generator + #[serde(rename = "enum_form_string")] + #[serde(skip_serializing_if = "Option::is_none")] + pub enum_form_string: Option, +} + +impl TestEnumParametersRequest { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> TestEnumParametersRequest { + TestEnumParametersRequest { + enum_form_string: Some("-efg".to_string()), + } + } +} + +/// Converts the TestEnumParametersRequest value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::fmt::Display for TestEnumParametersRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = + vec![self.enum_form_string.as_ref().map(|enum_form_string| { + ["enum_form_string".to_string(), enum_form_string.to_string()].join(",") + })]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a TestEnumParametersRequest value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for TestEnumParametersRequest { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub enum_form_string: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => { + return std::result::Result::Err( + "Missing value while parsing TestEnumParametersRequest".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "enum_form_string" => intermediate_rep.enum_form_string.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing TestEnumParametersRequest".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(TestEnumParametersRequest { + enum_form_string: intermediate_rep.enum_form_string.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for TestEnumParametersRequest - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into TestEnumParametersRequest - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct TestJsonFormDataRequest { + /// field1 + #[serde(rename = "param")] + pub param: String, + + /// field2 + #[serde(rename = "param2")] + pub param2: String, +} + +impl TestJsonFormDataRequest { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(param: String, param2: String) -> TestJsonFormDataRequest { + TestJsonFormDataRequest { param, param2 } + } +} + +/// Converts the TestJsonFormDataRequest value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::fmt::Display for TestJsonFormDataRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("param".to_string()), + Some(self.param.to_string()), + Some("param2".to_string()), + Some(self.param2.to_string()), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a TestJsonFormDataRequest value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for TestJsonFormDataRequest { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub param: Vec, + pub param2: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => { + return std::result::Result::Err( + "Missing value while parsing TestJsonFormDataRequest".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "param" => intermediate_rep.param.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "param2" => intermediate_rep.param2.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing TestJsonFormDataRequest".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(TestJsonFormDataRequest { + param: intermediate_rep + .param + .into_iter() + .next() + .ok_or_else(|| "param missing in TestJsonFormDataRequest".to_string())?, + param2: intermediate_rep + .param2 + .into_iter() + .next() + .ok_or_else(|| "param2 missing in TestJsonFormDataRequest".to_string())?, + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for TestJsonFormDataRequest - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into TestJsonFormDataRequest - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UpdatePetWithFormRequest { + /// Updated name of the pet + #[serde(rename = "name")] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + /// Updated status of the pet + #[serde(rename = "status")] + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, +} + +impl UpdatePetWithFormRequest { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> UpdatePetWithFormRequest { + UpdatePetWithFormRequest { + name: None, + status: None, + } + } +} + +/// Converts the UpdatePetWithFormRequest value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::fmt::Display for UpdatePetWithFormRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + self.name + .as_ref() + .map(|name| ["name".to_string(), name.to_string()].join(",")), + self.status + .as_ref() + .map(|status| ["status".to_string(), status.to_string()].join(",")), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UpdatePetWithFormRequest value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for UpdatePetWithFormRequest { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub name: Vec, + pub status: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => { + return std::result::Result::Err( + "Missing value while parsing UpdatePetWithFormRequest".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "name" => intermediate_rep.name.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "status" => intermediate_rep.status.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing UpdatePetWithFormRequest".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(UpdatePetWithFormRequest { + name: intermediate_rep.name.into_iter().next(), + status: intermediate_rep.status.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for UpdatePetWithFormRequest - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into UpdatePetWithFormRequest - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UploadFileRequest { + /// Additional data to pass to server + #[serde(rename = "additionalMetadata")] + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_metadata: Option, + + /// file to upload + #[serde(rename = "file")] + #[serde(skip_serializing_if = "Option::is_none")] + pub file: Option, +} + +impl UploadFileRequest { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> UploadFileRequest { + UploadFileRequest { + additional_metadata: None, + file: None, + } + } +} + +/// Converts the UploadFileRequest value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::fmt::Display for UploadFileRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + self.additional_metadata + .as_ref() + .map(|additional_metadata| { + [ + "additionalMetadata".to_string(), + additional_metadata.to_string(), + ] + .join(",") + }), + // Skipping file in query parameter serialization + // Skipping file in query parameter serialization + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UploadFileRequest value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for UploadFileRequest { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub additional_metadata: Vec, + pub file: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => { + return std::result::Result::Err( + "Missing value while parsing UploadFileRequest".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "additionalMetadata" => intermediate_rep.additional_metadata.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + "file" => return std::result::Result::Err( + "Parsing binary data in this style is not supported in UploadFileRequest" + .to_string(), + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing UploadFileRequest".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(UploadFileRequest { + additional_metadata: intermediate_rep.additional_metadata.into_iter().next(), + file: intermediate_rep.file.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for UploadFileRequest - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into UploadFileRequest - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct User { diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs index cf0fe31c8130..e566756227e4 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs @@ -895,9 +895,21 @@ where }) } +#[derive(validator::Validate)] +#[allow(dead_code)] +struct TestEndpointParametersBodyValidator<'a> { + #[validate(nested)] + body: &'a models::TestEndpointParametersRequest, +} + #[tracing::instrument(skip_all)] -fn test_endpoint_parameters_validation() -> std::result::Result<(), ValidationErrors> { - Ok(()) +fn test_endpoint_parameters_validation( + body: models::TestEndpointParametersRequest, +) -> std::result::Result<(models::TestEndpointParametersRequest,), ValidationErrors> { + let b = TestEndpointParametersBodyValidator { body: &body }; + b.validate()?; + + Ok((body,)) } /// TestEndpointParameters - POST /v2/fake #[tracing::instrument(skip_all)] @@ -906,17 +918,18 @@ async fn test_endpoint_parameters( host: Host, cookies: CookieJar, State(api_impl): State, + Form(body): Form, ) -> Result where I: AsRef + Send + Sync, A: Api, { #[allow(clippy::redundant_closure)] - let validation = tokio::task::spawn_blocking(move || test_endpoint_parameters_validation()) + let validation = tokio::task::spawn_blocking(move || test_endpoint_parameters_validation(body)) .await .unwrap(); - let Ok(()) = validation else { + let Ok((body,)) = validation else { return Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::from(validation.unwrap_err().to_string())) @@ -925,7 +938,7 @@ where let result = api_impl .as_ref() - .test_endpoint_parameters(method, host, cookies) + .test_endpoint_parameters(method, host, cookies, body) .await; let mut response = Response::builder(); @@ -954,21 +967,34 @@ where }) } +#[derive(validator::Validate)] +#[allow(dead_code)] +struct TestEnumParametersBodyValidator<'a> { + #[validate(nested)] + body: &'a models::TestEnumParametersRequest, +} + #[tracing::instrument(skip_all)] fn test_enum_parameters_validation( header_params: models::TestEnumParametersHeaderParams, query_params: models::TestEnumParametersQueryParams, + body: Option, ) -> std::result::Result< ( models::TestEnumParametersHeaderParams, models::TestEnumParametersQueryParams, + Option, ), ValidationErrors, > { header_params.validate()?; query_params.validate()?; + if let Some(body) = &body { + let b = TestEnumParametersBodyValidator { body }; + b.validate()?; + } - Ok((header_params, query_params)) + Ok((header_params, query_params, body)) } /// TestEnumParameters - GET /v2/fake #[tracing::instrument(skip_all)] @@ -979,6 +1005,7 @@ async fn test_enum_parameters( headers: HeaderMap, Query(query_params): Query, State(api_impl): State, + Form(body): Form>, ) -> Result where I: AsRef + Send + Sync, @@ -1036,12 +1063,12 @@ where #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || { - test_enum_parameters_validation(header_params, query_params) + test_enum_parameters_validation(header_params, query_params, body) }) .await .unwrap(); - let Ok((header_params, query_params)) = validation else { + let Ok((header_params, query_params, body)) = validation else { return Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::from(validation.unwrap_err().to_string())) @@ -1050,7 +1077,7 @@ where let result = api_impl .as_ref() - .test_enum_parameters(method, host, cookies, header_params, query_params) + .test_enum_parameters(method, host, cookies, header_params, query_params, body) .await; let mut response = Response::builder(); @@ -1147,9 +1174,21 @@ where }) } +#[derive(validator::Validate)] +#[allow(dead_code)] +struct TestJsonFormDataBodyValidator<'a> { + #[validate(nested)] + body: &'a models::TestJsonFormDataRequest, +} + #[tracing::instrument(skip_all)] -fn test_json_form_data_validation() -> std::result::Result<(), ValidationErrors> { - Ok(()) +fn test_json_form_data_validation( + body: models::TestJsonFormDataRequest, +) -> std::result::Result<(models::TestJsonFormDataRequest,), ValidationErrors> { + let b = TestJsonFormDataBodyValidator { body: &body }; + b.validate()?; + + Ok((body,)) } /// TestJsonFormData - GET /v2/fake/jsonFormData #[tracing::instrument(skip_all)] @@ -1158,17 +1197,18 @@ async fn test_json_form_data( host: Host, cookies: CookieJar, State(api_impl): State, + Form(body): Form, ) -> Result where I: AsRef + Send + Sync, A: Api, { #[allow(clippy::redundant_closure)] - let validation = tokio::task::spawn_blocking(move || test_json_form_data_validation()) + let validation = tokio::task::spawn_blocking(move || test_json_form_data_validation(body)) .await .unwrap(); - let Ok(()) = validation else { + let Ok((body,)) = validation else { return Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::from(validation.unwrap_err().to_string())) @@ -1177,7 +1217,7 @@ where let result = api_impl .as_ref() - .test_json_form_data(method, host, cookies) + .test_json_form_data(method, host, cookies, body) .await; let mut response = Response::builder(); @@ -1754,13 +1794,31 @@ where }) } +#[derive(validator::Validate)] +#[allow(dead_code)] +struct UpdatePetWithFormBodyValidator<'a> { + #[validate(nested)] + body: &'a models::UpdatePetWithFormRequest, +} + #[tracing::instrument(skip_all)] fn update_pet_with_form_validation( path_params: models::UpdatePetWithFormPathParams, -) -> std::result::Result<(models::UpdatePetWithFormPathParams,), ValidationErrors> { + body: Option, +) -> std::result::Result< + ( + models::UpdatePetWithFormPathParams, + Option, + ), + ValidationErrors, +> { path_params.validate()?; + if let Some(body) = &body { + let b = UpdatePetWithFormBodyValidator { body }; + b.validate()?; + } - Ok((path_params,)) + Ok((path_params, body)) } /// UpdatePetWithForm - POST /v2/pet/{petId} #[tracing::instrument(skip_all)] @@ -1770,6 +1828,7 @@ async fn update_pet_with_form( cookies: CookieJar, Path(path_params): Path, State(api_impl): State, + Form(body): Form>, ) -> Result where I: AsRef + Send + Sync, @@ -1777,11 +1836,11 @@ where { #[allow(clippy::redundant_closure)] let validation = - tokio::task::spawn_blocking(move || update_pet_with_form_validation(path_params)) + tokio::task::spawn_blocking(move || update_pet_with_form_validation(path_params, body)) .await .unwrap(); - let Ok((path_params,)) = validation else { + let Ok((path_params, body)) = validation else { return Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::from(validation.unwrap_err().to_string())) @@ -1790,7 +1849,7 @@ where let result = api_impl .as_ref() - .update_pet_with_form(method, host, cookies, path_params) + .update_pet_with_form(method, host, cookies, path_params, body) .await; let mut response = Response::builder(); diff --git a/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION index 08bfd0643b8c..ecb21862b1ee 100644 --- a/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION @@ -1 +1 @@ -7.5.0-SNAPSHOT +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore/README.md b/samples/server/petstore/rust-axum/output/petstore/README.md index 795a6f427e32..a6f7c5cb19af 100644 --- a/samples/server/petstore/rust-axum/output/petstore/README.md +++ b/samples/server/petstore/rust-axum/output/petstore/README.md @@ -12,7 +12,7 @@ server, you can easily generate a server stub. To see how to make this your own, look here: [README]((https://openapi-generator.tech)) - API version: 1.0.0 -- Generator version: 7.5.0-SNAPSHOT +- Generator version: 7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore/src/lib.rs b/samples/server/petstore/rust-axum/output/petstore/src/lib.rs index 50d589234ee9..9cf4c904abaa 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/lib.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/lib.rs @@ -301,6 +301,7 @@ pub trait Api { host: Host, cookies: CookieJar, path_params: models::UpdatePetWithFormPathParams, + body: Option, ) -> Result; /// uploads an image. diff --git a/samples/server/petstore/rust-axum/output/petstore/src/models.rs b/samples/server/petstore/rust-axum/output/petstore/src/models.rs index 3bc961a8430b..b3e7b9da0cba 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/models.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/models.rs @@ -1008,6 +1008,317 @@ impl std::convert::TryFrom for header::IntoHeaderValue { } } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UpdatePetWithFormRequest { + /// Updated name of the pet + #[serde(rename = "name")] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + /// Updated status of the pet + #[serde(rename = "status")] + #[serde(skip_serializing_if = "Option::is_none")] + pub status: Option, +} + +impl UpdatePetWithFormRequest { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> UpdatePetWithFormRequest { + UpdatePetWithFormRequest { + name: None, + status: None, + } + } +} + +/// Converts the UpdatePetWithFormRequest value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::fmt::Display for UpdatePetWithFormRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + self.name + .as_ref() + .map(|name| ["name".to_string(), name.to_string()].join(",")), + self.status + .as_ref() + .map(|status| ["status".to_string(), status.to_string()].join(",")), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UpdatePetWithFormRequest value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for UpdatePetWithFormRequest { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub name: Vec, + pub status: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => { + return std::result::Result::Err( + "Missing value while parsing UpdatePetWithFormRequest".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "name" => intermediate_rep.name.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "status" => intermediate_rep.status.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing UpdatePetWithFormRequest".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(UpdatePetWithFormRequest { + name: intermediate_rep.name.into_iter().next(), + status: intermediate_rep.status.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for UpdatePetWithFormRequest - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into UpdatePetWithFormRequest - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UploadFileRequest { + /// Additional data to pass to server + #[serde(rename = "additionalMetadata")] + #[serde(skip_serializing_if = "Option::is_none")] + pub additional_metadata: Option, + + /// file to upload + #[serde(rename = "file")] + #[serde(skip_serializing_if = "Option::is_none")] + pub file: Option, +} + +impl UploadFileRequest { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> UploadFileRequest { + UploadFileRequest { + additional_metadata: None, + file: None, + } + } +} + +/// Converts the UploadFileRequest value to the Query Parameters representation (style=form, explode=false) +/// specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde serializer +impl std::fmt::Display for UploadFileRequest { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + self.additional_metadata + .as_ref() + .map(|additional_metadata| { + [ + "additionalMetadata".to_string(), + additional_metadata.to_string(), + ] + .join(",") + }), + // Skipping file in query parameter serialization + // Skipping file in query parameter serialization + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a UploadFileRequest value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for UploadFileRequest { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub additional_metadata: Vec, + pub file: Vec, + } + + let mut intermediate_rep = IntermediateRep::default(); + + // Parse into intermediate representation + let mut string_iter = s.split(','); + let mut key_result = string_iter.next(); + + while key_result.is_some() { + let val = match string_iter.next() { + Some(x) => x, + None => { + return std::result::Result::Err( + "Missing value while parsing UploadFileRequest".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "additionalMetadata" => intermediate_rep.additional_metadata.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + "file" => return std::result::Result::Err( + "Parsing binary data in this style is not supported in UploadFileRequest" + .to_string(), + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing UploadFileRequest".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(UploadFileRequest { + additional_metadata: intermediate_rep.additional_metadata.into_iter().next(), + file: intermediate_rep.file.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for UploadFileRequest - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into UploadFileRequest - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + /// A User who is purchasing from the pet store #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] diff --git a/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs b/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs index 51fb05bcfa24..2abdd54e9c40 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs @@ -562,13 +562,31 @@ where }) } +#[derive(validator::Validate)] +#[allow(dead_code)] +struct UpdatePetWithFormBodyValidator<'a> { + #[validate(nested)] + body: &'a models::UpdatePetWithFormRequest, +} + #[tracing::instrument(skip_all)] fn update_pet_with_form_validation( path_params: models::UpdatePetWithFormPathParams, -) -> std::result::Result<(models::UpdatePetWithFormPathParams,), ValidationErrors> { + body: Option, +) -> std::result::Result< + ( + models::UpdatePetWithFormPathParams, + Option, + ), + ValidationErrors, +> { path_params.validate()?; + if let Some(body) = &body { + let b = UpdatePetWithFormBodyValidator { body }; + b.validate()?; + } - Ok((path_params,)) + Ok((path_params, body)) } /// UpdatePetWithForm - POST /v2/pet/{petId} #[tracing::instrument(skip_all)] @@ -578,6 +596,7 @@ async fn update_pet_with_form( cookies: CookieJar, Path(path_params): Path, State(api_impl): State, + Form(body): Form>, ) -> Result where I: AsRef + Send + Sync, @@ -585,11 +604,11 @@ where { #[allow(clippy::redundant_closure)] let validation = - tokio::task::spawn_blocking(move || update_pet_with_form_validation(path_params)) + tokio::task::spawn_blocking(move || update_pet_with_form_validation(path_params, body)) .await .unwrap(); - let Ok((path_params,)) = validation else { + let Ok((path_params, body)) = validation else { return Response::builder() .status(StatusCode::BAD_REQUEST) .body(Body::from(validation.unwrap_err().to_string())) @@ -598,7 +617,7 @@ where let result = api_impl .as_ref() - .update_pet_with_form(method, host, cookies, path_params) + .update_pet_with_form(method, host, cookies, path_params, body) .await; let mut response = Response::builder(); diff --git a/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION index 08bfd0643b8c..ecb21862b1ee 100644 --- a/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION @@ -1 +1 @@ -7.5.0-SNAPSHOT +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md b/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md index 56b88a40475c..49a2490640c4 100644 --- a/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md +++ b/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md @@ -12,7 +12,7 @@ server, you can easily generate a server stub. To see how to make this your own, look here: [README]((https://openapi-generator.tech)) - API version: 1.0 -- Generator version: 7.5.0-SNAPSHOT +- Generator version: 7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.gitignore b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.gitignore new file mode 100644 index 000000000000..a9d37c560c6a --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator-ignore b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator-ignore new file mode 100644 index 000000000000..7484ee590a38 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator-ignore @@ -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 diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/FILES b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/FILES new file mode 100644 index 000000000000..129313d13b57 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/FILES @@ -0,0 +1,9 @@ +.gitignore +.openapi-generator-ignore +Cargo.toml +README.md +src/header.rs +src/lib.rs +src/models.rs +src/server/mod.rs +src/types.rs diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION new file mode 100644 index 000000000000..ecb21862b1ee --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/Cargo.toml new file mode 100644 index 000000000000..48c393fd4b0a --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "rust-axum-header-uui" +version = "0.1.9" +authors = ["OpenAPI Generator team and contributors"] +description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" +edition = "2021" + +[features] +default = ["server"] +server = [] +conversion = [ + "frunk", + "frunk_derives", + "frunk_core", + "frunk-enum-core", + "frunk-enum-derive", +] + +[dependencies] +async-trait = "0.1" +axum = { version = "0.7" } +axum-extra = { version = "0.9", features = ["cookie", "multipart"] } +base64 = "0.22" +bytes = "1" +chrono = { version = "0.4", features = ["serde"] } +frunk = { version = "0.4", optional = true } +frunk-enum-core = { version = "0.3", optional = true } +frunk-enum-derive = { version = "0.3", optional = true } +frunk_core = { version = "0.4", optional = true } +frunk_derives = { version = "0.4", optional = true } +http = "1" +lazy_static = "1" +regex = "1" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["raw_value"] } +serde_urlencoded = "0.7" +tokio = { version = "1", default-features = false, features = [ + "signal", + "rt-multi-thread", +] } +tracing = { version = "0.1", features = ["attributes"] } +uuid = { version = "1", features = ["serde"] } +validator = { version = "0.18", features = ["derive"] } + +[dev-dependencies] +tracing-subscriber = "0.3" diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md new file mode 100644 index 000000000000..e99c9e7847f7 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md @@ -0,0 +1,91 @@ +# Rust API for rust-axum-header-uui + +No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + +## Overview + +This server was generated by the [openapi-generator] +(https://openapi-generator.tech) project. By using the +[OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote +server, you can easily generate a server stub. + +To see how to make this your own, look here: [README]((https://openapi-generator.tech)) + +- API version: 0.1.9 +- Generator version: 7.6.0-SNAPSHOT + + + +This autogenerated project defines an API crate `rust-axum-header-uui` which contains: +* An `Api` trait defining the API in Rust. +* Data types representing the underlying data model. +* Axum router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. + * Request validations (path, query, body params) are included. + +## Using the generated library + +The generated library has a few optional features that can be activated through Cargo. + +* `server` + * This defaults to enabled and creates the basic skeleton of a server implementation based on Axum. + * To create the server stack you'll need to provide an implementation of the API trait to provide the server function. +* `conversions` + * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. + +See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. + +### Example + +```rust +struct ServerImpl { + // database: sea_orm::DbConn, +} + +#[allow(unused_variables)] +#[async_trait] +impl rust-axum-header-uui::Api for ServerImpl { + // API implementation goes here +} + +pub async fn start_server(addr: &str) { + // initialize tracing + tracing_subscriber::fmt::init(); + + // Init Axum router + let app = rust-axum-header-uui::server::new(Arc::new(ServerImpl)); + + // Add layers to the router + let app = app.layer(...); + + // Run the server with graceful shutdown + let listener = TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await + .unwrap(); +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} +``` diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/header.rs b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/header.rs new file mode 100644 index 000000000000..656550c13244 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/header.rs @@ -0,0 +1,230 @@ +use std::{convert::TryFrom, fmt, ops::Deref, str::FromStr}; + +use chrono::{DateTime, Utc}; +use http::HeaderValue; + +/// A struct to allow homogeneous conversion into a HeaderValue. We can't +/// implement the From/Into trait on HeaderValue because we don't own +/// either of the types. +#[derive(Debug, Clone)] +pub(crate) struct IntoHeaderValue(pub T); + +// Generic implementations + +impl Deref for IntoHeaderValue { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +// Derive for each TryFrom in http::HeaderValue + +macro_rules! ihv_generate { + ($t:ident) => { + impl TryFrom for IntoHeaderValue<$t> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match hdr_value.parse::<$t>() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value)), + Err(e) => Err(format!( + "Unable to parse {} as a string: {}", + stringify!($t), + e + )), + }, + Err(e) => Err(format!( + "Unable to parse header {:?} as a string - {}", + hdr_value, e + )), + } + } + } + + impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue<$t>) -> Result { + Ok(hdr_value.0.into()) + } + } + }; +} + +ihv_generate!(u64); +ihv_generate!(i64); +ihv_generate!(i16); +ihv_generate!(u16); +ihv_generate!(u32); +ihv_generate!(usize); +ihv_generate!(isize); +ihv_generate!(i32); + +// Custom derivations + +// Vec + +impl TryFrom for IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => Ok(IntoHeaderValue( + hdr_value + .split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y.to_string()), + }) + .collect(), + )), + Err(e) => Err(format!( + "Unable to parse header: {:?} as a string - {}", + hdr_value, e + )), + } + } +} + +impl TryFrom>> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue>) -> Result { + match HeaderValue::from_str(&hdr_value.0.join(", ")) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} into a header - {}", + hdr_value, e + )), + } + } +} + +// String + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value.to_string())), + Err(e) => Err(format!("Unable to convert header {:?} to {}", hdr_value, e)), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_str(&hdr_value.0) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} from a header {}", + hdr_value, e + )), + } + } +} + +// Bool + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match hdr_value.parse() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value)), + Err(e) => Err(format!("Unable to parse bool from {} - {}", hdr_value, e)), + }, + Err(e) => Err(format!( + "Unable to convert {:?} from a header {}", + hdr_value, e + )), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_str(&hdr_value.0.to_string()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert: {:?} into a header: {}", + hdr_value, e + )), + } + } +} + +// DateTime + +impl TryFrom for IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match DateTime::parse_from_rfc3339(hdr_value) { + Ok(date) => Ok(IntoHeaderValue(date.with_timezone(&Utc))), + Err(e) => Err(format!("Unable to parse: {} as date - {}", hdr_value, e)), + }, + Err(e) => Err(format!( + "Unable to convert header {:?} to string {}", + hdr_value, e + )), + } + } +} + +impl TryFrom>> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue>) -> Result { + match HeaderValue::from_str(hdr_value.0.to_rfc3339().as_str()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} to a header: {}", + hdr_value, e + )), + } + } +} + +// uuid::Uuid + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match uuid::Uuid::from_str(hdr_value) { + Ok(uuid) => Ok(IntoHeaderValue(uuid)), + Err(e) => Err(format!("Unable to parse: {} as uuid - {}", hdr_value, e)), + }, + Err(e) => Err(format!( + "Unable to convert header {:?} to string {}", + hdr_value, e + )), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_bytes(hdr_value.0.as_bytes()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} to a header: {}", + hdr_value, e + )), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/lib.rs b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/lib.rs new file mode 100644 index 000000000000..cc68ed2682b2 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/lib.rs @@ -0,0 +1,58 @@ +#![allow( + missing_docs, + trivial_casts, + unused_variables, + unused_mut, + unused_extern_crates, + non_camel_case_types, + unused_imports, + unused_attributes +)] +#![allow(clippy::derive_partial_eq_without_eq, clippy::disallowed_names)] + +use async_trait::async_trait; +use axum::extract::*; +use axum_extra::extract::{CookieJar, Multipart}; +use bytes::Bytes; +use http::Method; +use serde::{Deserialize, Serialize}; + +use types::*; + +pub const BASE_PATH: &str = ""; +pub const API_VERSION: &str = "0.1.9"; + + #[derive(Debug, PartialEq, Serialize, Deserialize)] +#[must_use] +#[allow(clippy::large_enum_variant)] +pub enum UsersPostResponse { + /// Added row to table! + Status201_AddedRowToTable + (String) +} + + +/// API +#[async_trait] +#[allow(clippy::ptr_arg)] +pub trait Api { + + /// UsersPost - POST /users + async fn users_post( + &self, + method: Method, + host: Host, + cookies: CookieJar, + header_params: models::UsersPostHeaderParams, + ) -> Result; + +} + +#[cfg(feature = "server")] +pub mod server; + +pub mod models; +pub mod types; + +#[cfg(feature = "server")] +pub(crate) mod header; diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/models.rs b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/models.rs new file mode 100644 index 000000000000..22862b9b8b37 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/models.rs @@ -0,0 +1,49 @@ +#![allow(unused_qualifications)] + +use http::HeaderValue; +use validator::Validate; + +#[cfg(feature = "server")] +use crate::header; +use crate::{models, types::*}; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct UsersPostHeaderParams { + pub some_uid: uuid::Uuid, +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct HeaderUuid(uuid::Uuid); + +impl validator::Validate for HeaderUuid { + fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { + std::result::Result::Ok(()) + } +} + +impl std::convert::From for HeaderUuid { + fn from(x: uuid::Uuid) -> Self { + HeaderUuid(x) + } +} + +impl std::convert::From for uuid::Uuid { + fn from(x: HeaderUuid) -> Self { + x.0 + } +} + +impl std::ops::Deref for HeaderUuid { + type Target = uuid::Uuid; + fn deref(&self) -> &uuid::Uuid { + &self.0 + } +} + +impl std::ops::DerefMut for HeaderUuid { + fn deref_mut(&mut self) -> &mut uuid::Uuid { + &mut self.0 + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/server/mod.rs b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/server/mod.rs new file mode 100644 index 000000000000..2a0fa5663243 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/server/mod.rs @@ -0,0 +1,139 @@ +use std::collections::HashMap; + +use axum::{body::Body, extract::*, response::Response, routing::*}; +use axum_extra::extract::{CookieJar, Multipart}; +use bytes::Bytes; +use http::{header::CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue, Method, StatusCode}; +use tracing::error; +use validator::{Validate, ValidationErrors}; + +use crate::{header, types::*}; + +#[allow(unused_imports)] +use crate::models; + +use crate::{Api, UsersPostResponse}; + +/// Setup API Server. +pub fn new(api_impl: I) -> Router +where + I: AsRef + Clone + Send + Sync + 'static, + A: Api + 'static, +{ + // build our application with a route + Router::new() + .route("/users", post(users_post::)) + .with_state(api_impl) +} + +#[tracing::instrument(skip_all)] +fn users_post_validation( + header_params: models::UsersPostHeaderParams, +) -> std::result::Result<(models::UsersPostHeaderParams,), ValidationErrors> { + header_params.validate()?; + + Ok((header_params,)) +} +/// UsersPost - POST /users +#[tracing::instrument(skip_all)] +async fn users_post( + method: Method, + host: Host, + cookies: CookieJar, + headers: HeaderMap, + State(api_impl): State, +) -> Result +where + I: AsRef + Send + Sync, + A: Api, +{ + // Header parameters + let header_params = { + let header_some_uid = headers.get(HeaderName::from_static("some_uid")); + + let header_some_uid = match header_some_uid { + Some(v) => match header::IntoHeaderValue::::try_from((*v).clone()) { + Ok(result) => result.0, + Err(err) => { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(format!("Invalid header some_uid - {}", err))) + .map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }); + } + }, + None => { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from("Missing required header some_uid")) + .map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }); + } + }; + + models::UsersPostHeaderParams { + some_uid: header_some_uid, + } + }; + + #[allow(clippy::redundant_closure)] + let validation = tokio::task::spawn_blocking(move || users_post_validation(header_params)) + .await + .unwrap(); + + let Ok((header_params,)) = validation else { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(validation.unwrap_err().to_string())) + .map_err(|_| StatusCode::BAD_REQUEST); + }; + + let result = api_impl + .as_ref() + .users_post(method, host, cookies, header_params) + .await; + + let mut response = Response::builder(); + + let resp = match result { + Ok(rsp) => match rsp { + UsersPostResponse::Status201_AddedRowToTable(body) => { + let mut response = response.status(201); + { + let mut response_headers = response.headers_mut().unwrap(); + response_headers.insert( + CONTENT_TYPE, + HeaderValue::from_str("application/json").map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + })?, + ); + } + + let body_content = tokio::task::spawn_blocking(move || { + serde_json::to_vec(&body).map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) + }) + .await + .unwrap()?; + response.body(Body::from(body_content)) + } + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + response.status(500).body(Body::empty()) + } + }; + + resp.map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/types.rs b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/types.rs new file mode 100644 index 000000000000..aac11a86f4d6 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/src/types.rs @@ -0,0 +1,665 @@ +use std::{mem, str::FromStr}; + +use base64::{engine::general_purpose, Engine}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[allow(dead_code)] +pub struct Object(serde_json::Value); + +impl validator::Validate for Object { + fn validate(&self) -> Result<(), validator::ValidationErrors> { + Ok(()) + } +} + +impl FromStr for Object { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(Self(serde_json::Value::String(s.to_owned()))) + } +} + +/// Serde helper function to create a default `Option>` while +/// deserializing +pub fn default_optional_nullable() -> Option> { + None +} + +/// Serde helper function to deserialize into an `Option>` +pub fn deserialize_optional_nullable<'de, D, T>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de>, +{ + Option::::deserialize(deserializer).map(|val| match val { + Some(inner) => Some(Nullable::Present(inner)), + None => Some(Nullable::Null), + }) +} + +/// The Nullable type. Represents a value which may be specified as null on an API. +/// Note that this is distinct from a value that is optional and not present! +/// +/// Nullable implements many of the same methods as the Option type (map, unwrap, etc). +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub enum Nullable { + /// Null value + Null, + /// Value is present + Present(T), +} + +impl Nullable { + ///////////////////////////////////////////////////////////////////////// + // Querying the contained values + ///////////////////////////////////////////////////////////////////////// + + /// Returns `true` if the Nullable is a `Present` value. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x: Nullable = Nullable::Present(2); + /// assert_eq!(x.is_present(), true); + /// + /// let x: Nullable = Nullable::Null; + /// assert_eq!(x.is_present(), false); + /// ``` + #[inline] + pub fn is_present(&self) -> bool { + match *self { + Nullable::Present(_) => true, + Nullable::Null => false, + } + } + + /// Returns `true` if the Nullable is a `Null` value. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x: Nullable = Nullable::Present(2); + /// assert_eq!(x.is_null(), false); + /// + /// let x: Nullable = Nullable::Null; + /// assert_eq!(x.is_null(), true); + /// ``` + #[inline] + pub fn is_null(&self) -> bool { + !self.is_present() + } + + ///////////////////////////////////////////////////////////////////////// + // Adapter for working with references + ///////////////////////////////////////////////////////////////////////// + + /// Converts from `Nullable` to `Nullable<&T>`. + /// + /// # Examples + /// + /// Convert an `Nullable<`[`String`]`>` into a `Nullable<`[`usize`]`>`, preserving the original. + /// The [`map`] method takes the `self` argument by value, consuming the original, + /// so this technique uses `as_ref` to first take a `Nullable` to a reference + /// to the value inside the original. + /// + /// [`map`]: enum.Nullable.html#method.map + /// [`String`]: ../../std/string/struct.String.html + /// [`usize`]: ../../std/primitive.usize.html + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let num_as_str: Nullable = Nullable::Present("10".to_string()); + /// // First, cast `Nullable` to `Nullable<&String>` with `as_ref`, + /// // then consume *that* with `map`, leaving `num_as_str` on the stack. + /// let num_as_int: Nullable = num_as_str.as_ref().map(|n| n.len()); + /// println!("still can print num_as_str: {:?}", num_as_str); + /// ``` + #[inline] + pub fn as_ref(&self) -> Nullable<&T> { + match *self { + Nullable::Present(ref x) => Nullable::Present(x), + Nullable::Null => Nullable::Null, + } + } + + /// Converts from `Nullable` to `Nullable<&mut T>`. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let mut x = Nullable::Present(2); + /// match x.as_mut() { + /// Nullable::Present(v) => *v = 42, + /// Nullable::Null => {}, + /// } + /// assert_eq!(x, Nullable::Present(42)); + /// ``` + #[inline] + pub fn as_mut(&mut self) -> Nullable<&mut T> { + match *self { + Nullable::Present(ref mut x) => Nullable::Present(x), + Nullable::Null => Nullable::Null, + } + } + + ///////////////////////////////////////////////////////////////////////// + // Getting to contained values + ///////////////////////////////////////////////////////////////////////// + + /// Unwraps a Nullable, yielding the content of a `Nullable::Present`. + /// + /// # Panics + /// + /// Panics if the value is a [`Nullable::Null`] with a custom panic message provided by + /// `msg`. + /// + /// [`Nullable::Null`]: #variant.Null + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present("value"); + /// assert_eq!(x.expect("the world is ending"), "value"); + /// ``` + /// + /// ```{.should_panic} + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x: Nullable<&str> = Nullable::Null; + /// x.expect("the world is ending"); // panics with `the world is ending` + /// ``` + #[inline] + pub fn expect(self, msg: &str) -> T { + match self { + Nullable::Present(val) => val, + Nullable::Null => expect_failed(msg), + } + } + + /// Moves the value `v` out of the `Nullable` if it is `Nullable::Present(v)`. + /// + /// In general, because this function may panic, its use is discouraged. + /// Instead, prefer to use pattern matching and handle the `Nullable::Null` + /// case explicitly. + /// + /// # Panics + /// + /// Panics if the self value equals [`Nullable::Null`]. + /// + /// [`Nullable::Null`]: #variant.Null + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present("air"); + /// assert_eq!(x.unwrap(), "air"); + /// ``` + /// + /// ```{.should_panic} + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.unwrap(), "air"); // fails + /// ``` + #[inline] + pub fn unwrap(self) -> T { + match self { + Nullable::Present(val) => val, + Nullable::Null => panic!("called `Nullable::unwrap()` on a `Nullable::Null` value"), + } + } + + /// Returns the contained value or a default. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// assert_eq!(Nullable::Present("car").unwrap_or("bike"), "car"); + /// assert_eq!(Nullable::Null.unwrap_or("bike"), "bike"); + /// ``` + #[inline] + pub fn unwrap_or(self, def: T) -> T { + match self { + Nullable::Present(x) => x, + Nullable::Null => def, + } + } + + /// Returns the contained value or computes it from a closure. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let k = 10; + /// assert_eq!(Nullable::Present(4).unwrap_or_else(|| 2 * k), 4); + /// assert_eq!(Nullable::Null.unwrap_or_else(|| 2 * k), 20); + /// ``` + #[inline] + pub fn unwrap_or_else T>(self, f: F) -> T { + match self { + Nullable::Present(x) => x, + Nullable::Null => f(), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Transforming contained values + ///////////////////////////////////////////////////////////////////////// + + /// Maps a `Nullable` to `Nullable` by applying a function to a contained value. + /// + /// # Examples + /// + /// Convert a `Nullable<`[`String`]`>` into a `Nullable<`[`usize`]`>`, consuming the original: + /// + /// [`String`]: ../../std/string/struct.String.html + /// [`usize`]: ../../std/primitive.usize.html + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let maybe_some_string = Nullable::Present(String::from("Hello, World!")); + /// // `Nullable::map` takes self *by value*, consuming `maybe_some_string` + /// let maybe_some_len = maybe_some_string.map(|s| s.len()); + /// + /// assert_eq!(maybe_some_len, Nullable::Present(13)); + /// ``` + #[inline] + pub fn map U>(self, f: F) -> Nullable { + match self { + Nullable::Present(x) => Nullable::Present(f(x)), + Nullable::Null => Nullable::Null, + } + } + + /// Applies a function to the contained value (if any), + /// or returns a `default` (if not). + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.map_or(42, |v| v.len()), 3); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.map_or(42, |v| v.len()), 42); + /// ``` + #[inline] + pub fn map_or U>(self, default: U, f: F) -> U { + match self { + Nullable::Present(t) => f(t), + Nullable::Null => default, + } + } + + /// Applies a function to the contained value (if any), + /// or computes a `default` (if not). + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let k = 21; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42); + /// ``` + #[inline] + pub fn map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { + match self { + Nullable::Present(t) => f(t), + Nullable::Null => default(), + } + } + + /// Transforms the `Nullable` into a [`Result`], mapping `Nullable::Present(v)` to + /// [`Ok(v)`] and `Nullable::Null` to [`Err(err)`][Err]. + /// + /// [`Result`]: ../../std/result/enum.Result.html + /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok + /// [Err]: ../../std/result/enum.Result.html#variant.Err + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.ok_or(0), Ok("foo")); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.ok_or(0), Err(0)); + /// ``` + #[inline] + pub fn ok_or(self, err: E) -> Result { + match self { + Nullable::Present(v) => Ok(v), + Nullable::Null => Err(err), + } + } + + /// Transforms the `Nullable` into a [`Result`], mapping `Nullable::Present(v)` to + /// [`Ok(v)`] and `Nullable::Null` to [`Err(err())`][Err]. + /// + /// [`Result`]: ../../std/result/enum.Result.html + /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok + /// [Err]: ../../std/result/enum.Result.html#variant.Err + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.ok_or_else(|| 0), Ok("foo")); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.ok_or_else(|| 0), Err(0)); + /// ``` + #[inline] + pub fn ok_or_else E>(self, err: F) -> Result { + match self { + Nullable::Present(v) => Ok(v), + Nullable::Null => Err(err()), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Boolean operations on the values, eager and lazy + ///////////////////////////////////////////////////////////////////////// + + /// Returns `Nullable::Null` if the Nullable is `Nullable::Null`, otherwise returns `optb`. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present(2); + /// let y: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.and(y), Nullable::Null); + /// + /// let x: Nullable = Nullable::Null; + /// let y = Nullable::Present("foo"); + /// assert_eq!(x.and(y), Nullable::Null); + /// + /// let x = Nullable::Present(2); + /// let y = Nullable::Present("foo"); + /// assert_eq!(x.and(y), Nullable::Present("foo")); + /// + /// let x: Nullable = Nullable::Null; + /// let y: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.and(y), Nullable::Null); + /// ``` + #[inline] + pub fn and(self, optb: Nullable) -> Nullable { + match self { + Nullable::Present(_) => optb, + Nullable::Null => Nullable::Null, + } + } + + /// Returns `Nullable::Null` if the Nullable is `Nullable::Null`, otherwise calls `f` with the + /// wrapped value and returns the result. + /// + /// Some languages call this operation flatmap. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// fn sq(x: u32) -> Nullable { Nullable::Present(x * x) } + /// fn nope(_: u32) -> Nullable { Nullable::Null } + /// + /// assert_eq!(Nullable::Present(2).and_then(sq).and_then(sq), Nullable::Present(16)); + /// assert_eq!(Nullable::Present(2).and_then(sq).and_then(nope), Nullable::Null); + /// assert_eq!(Nullable::Present(2).and_then(nope).and_then(sq), Nullable::Null); + /// assert_eq!(Nullable::Null.and_then(sq).and_then(sq), Nullable::Null); + /// ``` + #[inline] + pub fn and_then Nullable>(self, f: F) -> Nullable { + match self { + Nullable::Present(x) => f(x), + Nullable::Null => Nullable::Null, + } + } + + /// Returns the Nullable if it contains a value, otherwise returns `optb`. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present(2); + /// let y = Nullable::Null; + /// assert_eq!(x.or(y), Nullable::Present(2)); + /// + /// let x = Nullable::Null; + /// let y = Nullable::Present(100); + /// assert_eq!(x.or(y), Nullable::Present(100)); + /// + /// let x = Nullable::Present(2); + /// let y = Nullable::Present(100); + /// assert_eq!(x.or(y), Nullable::Present(2)); + /// + /// let x: Nullable = Nullable::Null; + /// let y = Nullable::Null; + /// assert_eq!(x.or(y), Nullable::Null); + /// ``` + #[inline] + pub fn or(self, optb: Nullable) -> Nullable { + match self { + Nullable::Present(_) => self, + Nullable::Null => optb, + } + } + + /// Returns the Nullable if it contains a value, otherwise calls `f` and + /// returns the result. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// fn nobody() -> Nullable<&'static str> { Nullable::Null } + /// fn vikings() -> Nullable<&'static str> { Nullable::Present("vikings") } + /// + /// assert_eq!(Nullable::Present("barbarians").or_else(vikings), + /// Nullable::Present("barbarians")); + /// assert_eq!(Nullable::Null.or_else(vikings), Nullable::Present("vikings")); + /// assert_eq!(Nullable::Null.or_else(nobody), Nullable::Null); + /// ``` + #[inline] + pub fn or_else Nullable>(self, f: F) -> Nullable { + match self { + Nullable::Present(_) => self, + Nullable::Null => f(), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Misc + ///////////////////////////////////////////////////////////////////////// + + /// Takes the value out of the Nullable, leaving a `Nullable::Null` in its place. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let mut x = Nullable::Present(2); + /// x.take(); + /// assert_eq!(x, Nullable::Null); + /// + /// let mut x: Nullable = Nullable::Null; + /// x.take(); + /// assert_eq!(x, Nullable::Null); + /// ``` + #[inline] + pub fn take(&mut self) -> Nullable { + mem::replace(self, Nullable::Null) + } +} + +impl<'a, T: Clone> Nullable<&'a T> { + /// Maps an `Nullable<&T>` to an `Nullable` by cloning the contents of the + /// Nullable. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = 12; + /// let opt_x = Nullable::Present(&x); + /// assert_eq!(opt_x, Nullable::Present(&12)); + /// let cloned = opt_x.cloned(); + /// assert_eq!(cloned, Nullable::Present(12)); + /// ``` + pub fn cloned(self) -> Nullable { + self.map(Clone::clone) + } +} + +impl Nullable { + /// Returns the contained value or a default + /// + /// Consumes the `self` argument then, if `Nullable::Present`, returns the contained + /// value, otherwise if `Nullable::Null`, returns the default value for that + /// type. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_header_uui::types::Nullable; + /// + /// let x = Nullable::Present(42); + /// assert_eq!(42, x.unwrap_or_default()); + /// + /// let y: Nullable = Nullable::Null; + /// assert_eq!(0, y.unwrap_or_default()); + /// ``` + #[inline] + pub fn unwrap_or_default(self) -> T { + match self { + Nullable::Present(x) => x, + Nullable::Null => Default::default(), + } + } +} + +impl Default for Nullable { + /// Returns None. + #[inline] + fn default() -> Nullable { + Nullable::Null + } +} + +impl From for Nullable { + fn from(val: T) -> Nullable { + Nullable::Present(val) + } +} + +impl Serialize for Nullable +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + Nullable::Present(ref inner) => serializer.serialize_some(&inner), + Nullable::Null => serializer.serialize_none(), + } + } +} + +impl<'de, T> Deserialize<'de> for Nullable +where + T: serde::de::DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + // In order to deserialize a required, but nullable, value, we first have to check whether + // the value is present at all. To do this, we deserialize to a serde_json::Value, which + // fails if the value is missing, or gives serde_json::Value::Null if the value is present. + // If that succeeds as null, we can easily return a Null. + // If that succeeds as some value, we deserialize that value and return a Present. + // If that errors, we return the error. + let presence: Result<::serde_json::Value, _> = + serde::Deserialize::deserialize(deserializer); + match presence { + Ok(serde_json::Value::Null) => Ok(Nullable::Null), + Ok(some_value) => serde_json::from_value(some_value) + .map(Nullable::Present) + .map_err(serde::de::Error::custom), + Err(x) => Err(x), + } + } +} + +#[inline(never)] +#[cold] +fn expect_failed(msg: &str) -> ! { + panic!("{}", msg) +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +/// Base64-encoded byte array +pub struct ByteArray(pub Vec); + +impl Serialize for ByteArray { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&general_purpose::STANDARD.encode(&self.0)) + } +} + +impl<'de> Deserialize<'de> for ByteArray { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match general_purpose::STANDARD.decode(s) { + Ok(bin) => Ok(ByteArray(bin)), + _ => Err(serde::de::Error::custom("invalid base64")), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION index 08bfd0643b8c..ecb21862b1ee 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION @@ -1 +1 @@ -7.5.0-SNAPSHOT +7.6.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-test/README.md b/samples/server/petstore/rust-axum/output/rust-axum-test/README.md index d1f027f128d6..615ce4f4c740 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-test/README.md +++ b/samples/server/petstore/rust-axum/output/rust-axum-test/README.md @@ -12,7 +12,7 @@ server, you can easily generate a server stub. To see how to make this your own, look here: [README]((https://openapi-generator.tech)) - API version: 2.3.4 -- Generator version: 7.5.0-SNAPSHOT +- Generator version: 7.6.0-SNAPSHOT