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/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java index 1efb600d2785..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 @@ -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. + final 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..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; @@ -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}} 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 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