diff --git a/bin/utils/test_file_list.yaml b/bin/utils/test_file_list.yaml index d9f0ef85e8dc..68bf0874cceb 100644 --- a/bin/utils/test_file_list.yaml +++ b/bin/utils/test_file_list.yaml @@ -59,6 +59,6 @@ sha256: 45cdaba3d2adc212cd4f0184ad475419a95e2326254c2ef84175e210c922b2f3 # rust axum test files - filename: "samples/server/petstore/rust-axum/output/rust-axum-oneof/tests/oneof_with_discriminator.rs" - sha256: 2d4f5a069fdcb3057bb078d5e75b3de63cd477b97725e457079df24bd2c30600 + sha256: b2093528aac971193f2863a70f46eea45cf8bda79120b133a614599e80d8b46d - filename: "samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs" sha256: 1d3fb01f65e98290b1d3eece28014c7d3e3f2fdf18e7110249d3c591cc4642ab 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 4633d6df0a95..a6450d6f3f52 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 @@ -648,7 +648,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation } private void postProcessPolymorphism(final List allModels) { - final HashMap> discriminatorsForModel = new HashMap<>(); + final HashMap> discriminatorsForModel = new HashMap<>(); for (final ModelMap mo : allModels) { final CodegenModel cm = mo.getModel(); @@ -656,15 +656,17 @@ private void postProcessPolymorphism(final List allModels) { final CodegenComposedSchemas cs = cm.getComposedSchemas(); if (cs != null) { final List csOneOf = cs.getOneOf(); + CodegenDiscriminator discriminator = cm.getDiscriminator(); + if (csOneOf != null) { - processPolymorphismDataType(csOneOf); + processPolymorphismDataType(csOneOf, discriminator); cs.setOneOf(csOneOf); cm.setComposedSchemas(cs); } final List csAnyOf = cs.getAnyOf(); if (csAnyOf != null) { - processPolymorphismDataType(csAnyOf); + processPolymorphismDataType(csAnyOf, discriminator); cs.setAnyOf(csAnyOf); cm.setComposedSchemas(cs); } @@ -672,14 +674,14 @@ private void postProcessPolymorphism(final List allModels) { if (cm.discriminator != null) { for (final String model : cm.oneOf) { - final List discriminators = discriminatorsForModel.getOrDefault(model, new ArrayList<>()); - discriminators.add(cm.discriminator.getPropertyName()); + final List discriminators = discriminatorsForModel.getOrDefault(model, new ArrayList<>()); + discriminators.add(cm.discriminator); discriminatorsForModel.put(model, discriminators); } for (final String model : cm.anyOf) { - final List discriminators = discriminatorsForModel.getOrDefault(model, new ArrayList<>()); - discriminators.add(cm.discriminator.getPropertyName()); + final List discriminators = discriminatorsForModel.getOrDefault(model, new ArrayList<>()); + discriminators.add(cm.discriminator); discriminatorsForModel.put(model, discriminators); } } @@ -689,11 +691,11 @@ private void postProcessPolymorphism(final List allModels) { for (ModelMap mo : allModels) { final CodegenModel cm = mo.getModel(); - final List discriminators = discriminatorsForModel.get(cm.getSchemaName()); + final List discriminators = discriminatorsForModel.get(cm.getSchemaName()); if (discriminators != null) { // If the discriminator field is not a defined attribute in the variant structure, create it. if (!discriminating(discriminators, cm)) { - final String discriminator = discriminators.get(0); + final CodegenDiscriminator discriminator = discriminators.get(0); CodegenProperty property = new CodegenProperty(); @@ -710,17 +712,18 @@ private void postProcessPolymorphism(final List allModels) { property.isDiscriminator = true; // Attributes based on the discriminator value - property.baseName = discriminator; - property.name = discriminator; - property.nameInCamelCase = camelize(discriminator); + property.baseName = discriminator.getPropertyBaseName(); + property.name = discriminator.getPropertyName(); + property.nameInCamelCase = camelize(discriminator.getPropertyName()); property.nameInPascalCase = property.nameInCamelCase.substring(0, 1).toUpperCase(Locale.ROOT) + property.nameInCamelCase.substring(1); - property.nameInSnakeCase = underscore(discriminator).toUpperCase(Locale.ROOT); + property.nameInSnakeCase = underscore(discriminator.getPropertyName()).toUpperCase(Locale.ROOT); property.getter = String.format(Locale.ROOT, "get%s", property.nameInPascalCase); property.setter = String.format(Locale.ROOT, "set%s", property.nameInPascalCase); property.defaultValueWithParam = String.format(Locale.ROOT, " = data.%s;", property.name); // Attributes based on the model name property.defaultValue = String.format(Locale.ROOT, "r#\"%s\"#.to_string()", cm.getSchemaName()); + property.discriminatorValue = getDiscriminatorValue(cm.getClassname(), discriminator); property.jsonSchema = String.format(Locale.ROOT, "{ \"default\":\"%s\"; \"type\":\"string\" }", cm.getSchemaName()); cm.vars.add(property); @@ -743,14 +746,27 @@ private void postProcessPolymorphism(final List allModels) { } } - private static boolean discriminating(final List discriminatorsForModel, final CodegenModel cm) { + private static String getDiscriminatorValue(String modelName, CodegenDiscriminator discriminator) { + if (discriminator == null || discriminator.getMappedModels() == null) { + return modelName; + } + return discriminator + .getMappedModels() + .stream() + .filter(m -> m.getModelName().equals(modelName) && m.getMappingName() != null) + .map(CodegenDiscriminator.MappedModel::getMappingName) + .findFirst() + .orElse(modelName); + } + + private static boolean discriminating(final List discriminatorsForModel, final CodegenModel cm) { resetDiscriminatorProperty(cm); // Discriminator will be presented as enum tag -> One and only one tag is allowed int countString = 0; int countNonString = 0; for (final CodegenProperty var : cm.vars) { - if (discriminatorsForModel.stream().anyMatch(discriminator -> var.baseName.equals(discriminator) || var.name.equals(discriminator))) { + if (discriminatorsForModel.stream().anyMatch(discriminator -> var.baseName.equals(discriminator.getPropertyBaseName()) || var.name.equals(discriminator.getPropertyName()))) { if (var.isString) { var.isDiscriminator = true; ++countString; @@ -773,7 +789,7 @@ private static void resetDiscriminatorProperty(final CodegenModel cm) { } } - private static void processPolymorphismDataType(final List cp) { + private static void processPolymorphismDataType(final List cp, CodegenDiscriminator discriminator) { final HashSet dedupDataTypeWithEnum = new HashSet<>(); final HashMap dedupDataType = new HashMap<>(); @@ -783,6 +799,7 @@ private static void processPolymorphismDataType(final List cp) // Mainly needed for primitive types. model.datatypeWithEnum = camelize(model.dataType.replaceAll("(?:\\w+::)+(\\w+)", "$1") .replace("<", "Of").replace(">", "")).replace(" ", "").replace(",", ""); + model.discriminatorValue = getDiscriminatorValue(model.datatypeWithEnum, discriminator); if (!dedupDataTypeWithEnum.add(model.datatypeWithEnum)) { model.datatypeWithEnum += ++idx; } diff --git a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache index 56173f1fb043..c8e22210fe75 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -796,6 +796,9 @@ impl std::str::FromStr for {{{classname}}} { pub enum {{{classname}}} { {{#composedSchemas}} {{#anyOf}} + {{#discriminator}} + #[serde(alias = "{{{discriminatorValue}}}")] + {{/discriminator}} {{{datatypeWithEnum}}}({{{dataType}}}), {{/anyOf}} {{/composedSchemas}} @@ -871,6 +874,9 @@ impl From<{{{dataType}}}> for {{{classname}}} { pub enum {{{classname}}} { {{#composedSchemas}} {{#oneOf}} + {{#discriminator}} + #[serde(alias = "{{{discriminatorValue}}}")] + {{/discriminator}} {{{datatypeWithEnum}}}({{{dataType}}}), {{/oneOf}} {{/composedSchemas}} @@ -1071,7 +1077,7 @@ pub struct {{{classname}}} { {{#isString}} impl {{{classname}}} { fn _name_for_{{{name}}}() -> String { - String::from("{{{classname}}}") + String::from("{{#discriminatorValue}}{{{discriminatorValue}}}{{/discriminatorValue}}{{^discriminatorValue}}{{classname}}{{/discriminatorValue}}") } fn _serialize_{{{name}}}(_: &String, s: S) -> Result diff --git a/modules/openapi-generator/src/test/resources/3_0/petstore.yaml b/modules/openapi-generator/src/test/resources/3_0/petstore.yaml index a8f9809a1249..50cde3f1f12c 100644 --- a/modules/openapi-generator/src/test/resources/3_0/petstore.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/petstore.yaml @@ -738,4 +738,4 @@ components: type: type: string message: - type: string + type: string \ No newline at end of file diff --git a/modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-oneof.yaml b/modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-oneof.yaml index dda34a57914d..3fb0272a91c6 100644 --- a/modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-oneof.yaml +++ b/modules/openapi-generator/src/test/resources/3_0/rust-axum/rust-axum-oneof.yaml @@ -76,11 +76,14 @@ components: additionalProperties: false discriminator: propertyName: op + mapping: + yo: "#/components/schemas/YoMessage" oneOf: - "$ref": "#/components/schemas/Hello" - "$ref": "#/components/schemas/Greeting" - "$ref": "#/components/schemas/Goodbye" - "$ref": "#/components/schemas/SomethingCompletelyDifferent" + - "$ref": "#/components/schemas/YoMessage" title: Message Hello: type: object @@ -141,3 +144,17 @@ components: type: object type: array - type: object + YoMessage: + type: object + title: Yo + properties: + d: + type: object + properties: + nickname: + type: string + required: + - nickname + required: + - op + - d \ No newline at end of file diff --git a/samples/server/petstore/rust-axum/output/rust-axum-oneof/src/models.rs b/samples/server/petstore/rust-axum/output/rust-axum-oneof/src/models.rs index 7a4c207040cb..caa96343c512 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-oneof/src/models.rs +++ b/samples/server/petstore/rust-axum/output/rust-axum-oneof/src/models.rs @@ -983,10 +983,16 @@ impl std::convert::TryFrom for header::IntoHeaderValue { #[serde(tag = "op")] #[allow(non_camel_case_types, clippy::large_enum_variant)] pub enum Message { + #[serde(alias = "Hello")] Hello(models::Hello), + #[serde(alias = "Greeting")] Greeting(models::Greeting), + #[serde(alias = "Goodbye")] Goodbye(models::Goodbye), + #[serde(alias = "SomethingCompletelyDifferent")] SomethingCompletelyDifferent(models::SomethingCompletelyDifferent), + #[serde(alias = "yo")] + YoMessage(models::YoMessage), } impl validator::Validate for Message { @@ -996,6 +1002,7 @@ impl validator::Validate for Message { Self::Greeting(v) => v.validate(), Self::Goodbye(v) => v.validate(), Self::SomethingCompletelyDifferent(v) => v.validate(), + Self::YoMessage(v) => v.validate(), } } } @@ -1021,6 +1028,7 @@ impl serde::Serialize for Message { Self::Greeting(x) => x.serialize(serializer), Self::Goodbye(x) => x.serialize(serializer), Self::SomethingCompletelyDifferent(x) => x.serialize(serializer), + Self::YoMessage(x) => x.serialize(serializer), } } } @@ -1045,6 +1053,11 @@ impl From for Message { Self::SomethingCompletelyDifferent(value) } } +impl From for Message { + fn from(value: models::YoMessage) -> Self { + Self::YoMessage(value) + } +} #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] @@ -1084,3 +1097,306 @@ impl From for SomethingCompletelyDifferent { Self::Object(value) } } + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct YoMessage { + #[serde(rename = "d")] + #[validate(nested)] + pub d: models::YoMessageD, + + #[serde(default = "YoMessage::_name_for_op")] + #[serde(serialize_with = "YoMessage::_serialize_op")] + #[serde(rename = "op")] + pub op: String, +} + +impl YoMessage { + fn _name_for_op() -> String { + String::from("yo") + } + + fn _serialize_op(_: &String, s: S) -> Result + where + S: serde::Serializer, + { + s.serialize_str(&Self::_name_for_op()) + } +} + +impl YoMessage { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(d: models::YoMessageD) -> YoMessage { + YoMessage { + d, + op: Self::_name_for_op(), + } + } +} + +/// Converts the YoMessage 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 YoMessage { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + // Skipping d in query parameter serialization + Some("op".to_string()), + Some(self.op.to_string()), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a YoMessage value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for YoMessage { + 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 d: Vec, + pub op: 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 YoMessage".to_string(), + ); + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "d" => intermediate_rep.d.push( + ::from_str(val) + .map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "op" => intermediate_rep.op.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing YoMessage".to_string(), + ); + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(YoMessage { + d: intermediate_rep + .d + .into_iter() + .next() + .ok_or_else(|| "d missing in YoMessage".to_string())?, + op: intermediate_rep + .op + .into_iter() + .next() + .ok_or_else(|| "op missing in YoMessage".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!( + r#"Invalid header value for YoMessage - value: {hdr_value} is invalid {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!( + r#"Unable to convert header value '{value}' into YoMessage - {err}"# + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + r#"Unable to convert header: {hdr_value:?} to string: {e}"# + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct YoMessageD { + #[serde(rename = "nickname")] + #[validate(custom(function = "check_xss_string"))] + pub nickname: String, +} + +impl YoMessageD { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(nickname: String) -> YoMessageD { + YoMessageD { nickname } + } +} + +/// Converts the YoMessageD 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 YoMessageD { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("nickname".to_string()), + Some(self.nickname.to_string()), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a YoMessageD value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for YoMessageD { + 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 nickname: 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 YoMessageD".to_string(), + ); + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "nickname" => intermediate_rep.nickname.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing YoMessageD".to_string(), + ); + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(YoMessageD { + nickname: intermediate_rep + .nickname + .into_iter() + .next() + .ok_or_else(|| "nickname missing in YoMessageD".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!( + r#"Invalid header value for YoMessageD - value: {hdr_value} is invalid {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!( + r#"Unable to convert header value '{value}' into YoMessageD - {err}"# + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + r#"Unable to convert header: {hdr_value:?} to string: {e}"# + )), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-oneof/tests/oneof_with_discriminator.rs b/samples/server/petstore/rust-axum/output/rust-axum-oneof/tests/oneof_with_discriminator.rs index 95d99b0a5578..a15177a4bfab 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-oneof/tests/oneof_with_discriminator.rs +++ b/samples/server/petstore/rust-axum/output/rust-axum-oneof/tests/oneof_with_discriminator.rs @@ -7,6 +7,7 @@ fn test_oneof_schema_with_discriminator() { let test1 = r#"{"op": "Hello", "d": {"welcome_message": "test1"}}"#; let test2 = r#"{"op": "Greeting", "d": {"greet_message": "test2"}}"#; let test3 = r#"{"op": "Goodbye", "d": {"goodbye_message": "test3"}}"#; + let test_yo_json: &str = r#"{"op": "yo", "d": {"nickname": "Big Guy"}}"#; let test4 = Hello { op: "ignored".to_string(), @@ -29,31 +30,44 @@ fn test_oneof_schema_with_discriminator() { }, }; + let test_yo_struct = YoMessage { + op: "ignored".to_string(), + d: YoMessageD { + nickname: "Dude".to_string(), + }, + }; + let test7 = Message::Hello(test4.clone().into()); let test8 = Message::Greeting(test5.clone().into()); let test9 = Message::Goodbye(test6.clone().into()); + let test_yo_enum = Message::YoMessage(test_yo_struct.clone().into()); let test10: Message = test4.clone().into(); let test11: Message = test5.clone().into(); let test12: Message = test6.clone().into(); + let test_yo_enum_to_message: Message = test_yo_enum.clone().into(); let test13 = r#"{"op":"Hello","d":{"welcome_message":"test4"}}"#; let test14 = r#"{"d":{"greet_message":"test5"},"op":"Greeting"}"#; let test15 = r#"{"op":"Goodbye","d":{"goodbye_message":"test6"}}"#; + let test_yo_struct_to_json = r#"{"d":{"nickname":"Dude"},"op":"yo"}"#; assert!(serde_json::from_str::(test0).is_err()); assert!(serde_json::from_str::(test0).is_ok()); assert!(serde_json::from_str::(test0).is_err()); assert!(serde_json::from_str::(test0).is_err()); + assert!(serde_json::from_str::(test0).is_err()); assert!(serde_json::from_str::(test1).is_ok()); assert!(serde_json::from_str::(test2).is_ok()); assert!(serde_json::from_str::(test3).is_ok()); + assert!(serde_json::from_str::(test_yo_json).is_ok()); assert!(serde_json::from_str::(test1).is_ok()); assert!(serde_json::from_str::(test2).is_ok()); assert!(serde_json::from_str::(test3).is_ok()); + assert!(serde_json::from_str::(test_yo_json).is_ok()); assert_eq!( serde_json::to_string(&test4).expect("Serialization error"), @@ -67,6 +81,10 @@ fn test_oneof_schema_with_discriminator() { serde_json::to_string(&test6).expect("Serialization error"), test15 ); + assert_eq!( + serde_json::to_string(&test_yo_struct).expect("Serialization error"), + test_yo_struct_to_json + ); assert_eq!( serde_json::to_string(&test7).expect("Serialization error"), @@ -80,6 +98,10 @@ fn test_oneof_schema_with_discriminator() { serde_json::to_string(&test9).expect("Serialization error"), test15 ); + assert_eq!( + serde_json::to_string(&test_yo_enum).expect("Serialization error"), + test_yo_struct_to_json + ); assert_eq!( serde_json::to_string(&test10).expect("Serialization error"), @@ -93,4 +115,8 @@ fn test_oneof_schema_with_discriminator() { serde_json::to_string(&test12).expect("Serialization error"), test15 ); + assert_eq!( + serde_json::to_string(&test_yo_enum_to_message).expect("Serialization error"), + test_yo_struct_to_json + ); } diff --git a/samples/server/petstore/rust-axum/output/rust-axum-test/src/models.rs b/samples/server/petstore/rust-axum/output/rust-axum-test/src/models.rs index af882b9fd96c..5647097ae923 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-test/src/models.rs +++ b/samples/server/petstore/rust-axum/output/rust-axum-test/src/models.rs @@ -286,23 +286,23 @@ impl ::std::str::FromStr for FooAdditionalPropertiesObject { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct FooAllOfObject { - #[serde(rename = "sampleProperty")] + #[serde(rename = "sampleBaseProperty")] #[validate(custom(function = "check_xss_string"))] #[serde(skip_serializing_if = "Option::is_none")] - pub sample_property: Option, + pub sample_base_property: Option, - #[serde(rename = "sampleBaseProperty")] + #[serde(rename = "sampleProperty")] #[validate(custom(function = "check_xss_string"))] #[serde(skip_serializing_if = "Option::is_none")] - pub sample_base_property: Option, + pub sample_property: Option, } impl FooAllOfObject { #[allow(clippy::new_without_default, clippy::too_many_arguments)] pub fn new() -> FooAllOfObject { FooAllOfObject { - sample_property: None, sample_base_property: None, + sample_property: None, } } } @@ -313,9 +313,6 @@ impl FooAllOfObject { impl std::fmt::Display for FooAllOfObject { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ - self.sample_property.as_ref().map(|sample_property| { - ["sampleProperty".to_string(), sample_property.to_string()].join(",") - }), self.sample_base_property .as_ref() .map(|sample_base_property| { @@ -325,6 +322,9 @@ impl std::fmt::Display for FooAllOfObject { ] .join(",") }), + self.sample_property.as_ref().map(|sample_property| { + ["sampleProperty".to_string(), sample_property.to_string()].join(",") + }), ]; write!( @@ -346,8 +346,8 @@ impl std::str::FromStr for FooAllOfObject { #[derive(Default)] #[allow(dead_code)] struct IntermediateRep { - pub sample_property: Vec, pub sample_base_property: Vec, + pub sample_property: Vec, } let mut intermediate_rep = IntermediateRep::default(); @@ -370,11 +370,11 @@ impl std::str::FromStr for FooAllOfObject { #[allow(clippy::match_single_binding)] match key { #[allow(clippy::redundant_clone)] - "sampleProperty" => intermediate_rep.sample_property.push( + "sampleBaseProperty" => intermediate_rep.sample_base_property.push( ::from_str(val).map_err(|x| x.to_string())?, ), #[allow(clippy::redundant_clone)] - "sampleBaseProperty" => intermediate_rep.sample_base_property.push( + "sampleProperty" => intermediate_rep.sample_property.push( ::from_str(val).map_err(|x| x.to_string())?, ), _ => { @@ -391,8 +391,8 @@ impl std::str::FromStr for FooAllOfObject { // Use the intermediate representation to return the struct std::result::Result::Ok(FooAllOfObject { - sample_property: intermediate_rep.sample_property.into_iter().next(), sample_base_property: intermediate_rep.sample_base_property.into_iter().next(), + sample_property: intermediate_rep.sample_property.into_iter().next(), }) } }