From 10ea8c6222769696dd5667eda6d0cadc9f963230 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Thu, 11 Sep 2025 14:57:27 +0900 Subject: [PATCH 01/15] Support AnyOf, AllOf --- docs/generators/rust-axum.md | 4 +- .../languages/RustAxumServerCodegen.java | 80 +++-- .../main/resources/rust-axum/Cargo.mustache | 1 + .../main/resources/rust-axum/models.mustache | 135 ++++++-- .../output/apikey-authorization/Cargo.toml | 1 + .../rust-axum/output/apikey-auths/Cargo.toml | 1 + .../rust-axum/output/multipart-v3/Cargo.toml | 1 + .../rust-axum/output/openapi-v3/Cargo.toml | 1 + .../rust-axum/output/openapi-v3/src/models.rs | 114 +++---- .../output/openapi-v3/tests/oneof_untagged.rs | 6 +- .../rust-axum/output/ops-v3/Cargo.toml | 1 + .../Cargo.toml | 1 + .../src/models.rs | 317 ++---------------- .../rust-axum/output/petstore/Cargo.toml | 1 + .../output/ping-bearer-auth/Cargo.toml | 1 + .../rust-axum-array-params-test/Cargo.toml | 1 + .../output/rust-axum-header-uuid/Cargo.toml | 1 + .../output/rust-axum-oneof/Cargo.toml | 1 + .../output/rust-axum-oneof/src/models.rs | 60 +--- .../output/rust-axum-test/Cargo.toml | 1 + .../output/rust-axum-test/src/models.rs | 152 +-------- .../rust-axum-validation-test/Cargo.toml | 1 + 22 files changed, 281 insertions(+), 601 deletions(-) diff --git a/docs/generators/rust-axum.md b/docs/generators/rust-axum.md index 743b58779eb3..43841fa6bf28 100644 --- a/docs/generators/rust-axum.md +++ b/docs/generators/rust-axum.md @@ -207,8 +207,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl |Composite|✓|OAS2,OAS3 |Polymorphism|✗|OAS2,OAS3 |Union|✗|OAS3 -|allOf|✗|OAS2,OAS3 -|anyOf|✗|OAS3 +|allOf|✓|OAS2,OAS3 +|anyOf|✓|OAS3 |oneOf|✓|OAS3 |not|✗|OAS3 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 1c883774d85e..c03db9dfc9d3 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 @@ -112,7 +112,9 @@ public RustAxumServerCodegen() { .schemaSupportFeatures(EnumSet.of( SchemaSupportFeature.Simple, SchemaSupportFeature.Composite, - SchemaSupportFeature.oneOf + SchemaSupportFeature.oneOf, + SchemaSupportFeature.anyOf, + SchemaSupportFeature.allOf )) .excludeGlobalFeatures( GlobalFeature.Info, @@ -633,8 +635,8 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation return op; } - private void postProcessOneOfModels(List allModels) { - final HashMap> oneOfMapDiscriminator = new HashMap<>(); + private void postProcessOneAllAnyOfModels(List allModels) { + final HashMap> mapDiscriminator = new HashMap<>(); for (ModelMap mo : allModels) { final CodegenModel cm = mo.getModel(); @@ -642,31 +644,44 @@ private void postProcessOneOfModels(List allModels) { final CodegenComposedSchemas cs = cm.getComposedSchemas(); if (cs != null) { final List csOneOf = cs.getOneOf(); - if (csOneOf != null) { - for (CodegenProperty model : csOneOf) { - // Generate a valid name for the enum variant. - // Mainly needed for primitive types. - - model.datatypeWithEnum = camelize(model.dataType.replaceAll("(?:\\w+::)+(\\w+)", "$1") - .replace("<", "Of").replace(">", "")); + processOneAllAnyOfModelDataType(csOneOf); + cs.setAnyOf(csOneOf); + cm.setComposedSchemas(cs); + } - // Primitive type is not properly set, this overrides it to guarantee adequate model generation. - if (!model.getDataType().matches(String.format(Locale.ROOT, ".*::%s", model.getDatatypeWithEnum()))) { - model.isPrimitiveType = true; - } - } + final List csAnyOf = cs.getAnyOf(); + if (csAnyOf != null) { + processOneAllAnyOfModelDataType(csAnyOf); + cs.setAnyOf(csAnyOf); + cm.setComposedSchemas(cs); + } - cs.setOneOf(csOneOf); + final List csAllOf = cs.getAllOf(); + if (csAllOf != null) { + processOneAllAnyOfModelDataType(csAllOf); + cs.setAllOf(csAllOf); cm.setComposedSchemas(cs); } } if (cm.discriminator != null) { for (String model : cm.oneOf) { - List discriminators = oneOfMapDiscriminator.getOrDefault(model, new ArrayList<>()); + final List discriminators = mapDiscriminator.getOrDefault(model, new ArrayList<>()); + discriminators.add(cm.discriminator.getPropertyName()); + mapDiscriminator.put(model, discriminators); + } + + for (String model : cm.anyOf) { + final List discriminators = mapDiscriminator.getOrDefault(model, new ArrayList<>()); + discriminators.add(cm.discriminator.getPropertyName()); + mapDiscriminator.put(model, discriminators); + } + + for (String model : cm.allOf) { + final List discriminators = mapDiscriminator.getOrDefault(model, new ArrayList<>()); discriminators.add(cm.discriminator.getPropertyName()); - oneOfMapDiscriminator.put(model, discriminators); + mapDiscriminator.put(model, discriminators); } } } @@ -678,7 +693,7 @@ private void postProcessOneOfModels(List allModels) { var.isDiscriminator = false; } - final List discriminatorsForModel = oneOfMapDiscriminator.get(cm.getSchemaName()); + final List discriminatorsForModel = mapDiscriminator.get(cm.getSchemaName()); if (discriminatorsForModel != null) { for (String discriminator : discriminatorsForModel) { @@ -729,9 +744,34 @@ private void postProcessOneOfModels(List allModels) { } } + private static void processOneAllAnyOfModelDataType(final List cp) { + final HashSet dedup = new HashSet<>(); + + final List toRemove = new ArrayList(); + for (CodegenProperty model : cp) { + // Generate a valid name for the enum variant. + // Mainly needed for primitive types. + model.datatypeWithEnum = camelize(model.dataType.replaceAll("(?:\\w+::)+(\\w+)", "$1") + .replace("<", "Of").replace(">", "")).replace(" ", "").replace(",", ""); + + if (!model.getDataType().matches(String.format(Locale.ROOT, ".*::%s", model.getDatatypeWithEnum()))) { + model.isPrimitiveType = true; + model.datatypeWithEnum += "Type"; + } + + if (!dedup.add(model.datatypeWithEnum)) { + toRemove.add(model); + } + } + + for (var model : toRemove) { + cp.remove(model); + } + } + @Override public OperationsMap postProcessOperationsWithModels(final OperationsMap operationsMap, List allModels) { - postProcessOneOfModels(allModels); + postProcessOneAllAnyOfModels(allModels); final OperationMap operations = operationsMap.getOperations(); operations.put("classnamePascalCase", camelize(operations.getClassname())); diff --git a/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache b/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache index 92f89a371e4f..bbbf7bbcbcc2 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache @@ -46,6 +46,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 681d374d85a3..47400bf210f4 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -751,17 +751,38 @@ impl std::str::FromStr for {{{classname}}} { {{^arrayModelType}} {{! general struct}} {{#anyOf.size}} -/// Any of: -{{#anyOf}} -/// - {{{.}}} -{{/anyOf}} -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct {{{classname}}}(Box); +{{#discriminator}} +#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] +#[serde(tag = "{{{propertyBaseName}}}")] +{{/discriminator}} +{{^discriminator}} +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +{{/discriminator}} +#[allow(non_camel_case_types)] +pub enum {{{classname}}} { + {{#composedSchemas}} + {{#anyOf}} + {{{datatypeWithEnum}}}({{{dataType}}}), + {{/anyOf}} + {{/composedSchemas}} +} impl validator::Validate for {{{classname}}} { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - std::result::Result::Ok(()) + match self { + {{#composedSchemas}} + {{#anyOf}} + {{^isModel}} + Self::{{{datatypeWithEnum}}}(_) => std::result::Result::Ok(()), + {{/isModel}} + {{#isModel}} + Self::{{{datatypeWithEnum}}}(v) => v.validate(), + {{/isModel}} + {{/anyOf}} + {{/composedSchemas}} + } } } @@ -776,27 +797,21 @@ impl std::str::FromStr for {{{classname}}} { } } -impl PartialEq for {{{classname}}} { - fn eq(&self, other: &Self) -> bool { - self.0.get() == other.0.get() - } -} - {{/anyOf.size}} {{#oneOf.size}} {{#discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] #[serde(tag = "{{{propertyBaseName}}}")] {{/discriminator}} {{^discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] #[serde(untagged)] {{/discriminator}} #[allow(non_camel_case_types)] pub enum {{{classname}}} { {{#composedSchemas}} {{#oneOf}} - {{{datatypeWithEnum}}}(Box<{{{dataType}}}>), + {{{datatypeWithEnum}}}({{{dataType}}}), {{/oneOf}} {{/composedSchemas}} } @@ -806,14 +821,14 @@ impl validator::Validate for {{{classname}}} fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { {{#composedSchemas}} - {{#oneOf}} - {{#isPrimitiveType}} + {{#anyOf}} + {{^isModel}} Self::{{{datatypeWithEnum}}}(_) => std::result::Result::Ok(()), - {{/isPrimitiveType}} - {{^isPrimitiveType}} - Self::{{{datatypeWithEnum}}}(x) => x.validate(), - {{/isPrimitiveType}} - {{/oneOf}} + {{/isModel}} + {{#isModel}} + Self::{{{datatypeWithEnum}}}(v) => v.validate(), + {{/isModel}} + {{/anyOf}} {{/composedSchemas}} } } @@ -834,17 +849,68 @@ impl serde::Serialize for {{{classname}}} { } {{/discriminator}} +/// Converts Query Parameters representation (style=form, explode=false) to a {{{classname}}} value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for {{{classname}}} { + type Err = serde_json::Error; + + fn from_str(s: &str) -> std::result::Result { + serde_json::from_str(s) + } +} + +{{/oneOf.size}} +{{#allOf.size}} +{{#discriminator}} +#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] +#[serde(tag = "{{{propertyBaseName}}}")] +{{/discriminator}} +{{^discriminator}} +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +{{/discriminator}} +#[allow(non_camel_case_types)] +pub enum {{{classname}}} { + {{#composedSchemas}} + {{#allOf}} + {{{datatypeWithEnum}}}({{{dataType}}}), + {{/allOf}} + {{/composedSchemas}} +} +impl validator::Validate for {{{classname}}} +{ + fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { + match self { + {{#composedSchemas}} + {{#allOf}} + {{^isModel}} + Self::{{{datatypeWithEnum}}}(_) => std::result::Result::Ok(()), + {{/isModel}} + {{#isModel}} + Self::{{{datatypeWithEnum}}}(v) => v.validate(), + {{/isModel}} + {{/allOf}} + {{/composedSchemas}} + } + } +} -{{#composedSchemas}} -{{#oneOf}} -impl From<{{{dataType}}}> for {{{classname}}} { - fn from(value: {{{dataType}}}) -> Self { - Self::{{{datatypeWithEnum}}}(Box::new(value)) +{{#discriminator}} +impl serde::Serialize for {{{classname}}} { + fn serialize(&self, serializer: S) -> Result + where S: serde::Serializer { + match self { + {{#composedSchemas}} + {{#allOf}} + Self::{{{datatypeWithEnum}}}(x) => x.serialize(serializer), + {{/allOf}} + {{/composedSchemas}} + } } } -{{/oneOf}} -{{/composedSchemas}} +{{/discriminator}} /// Converts Query Parameters representation (style=form, explode=false) to a {{{classname}}} value /// as specified in https://swagger.io/docs/specification/serialization/ @@ -857,9 +923,10 @@ impl std::str::FromStr for {{{classname}}} { } } -{{/oneOf.size}} +{{/allOf.size}} {{^anyOf.size}} {{^oneOf.size}} +{{^allOf.size}} #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct {{{classname}}} { @@ -989,7 +1056,6 @@ pub struct {{{classname}}} { {{/vars}} } - {{#vars}} {{#isDiscriminator}} impl {{{classname}}} { @@ -1007,7 +1073,6 @@ impl {{{classname}}} { {{/isDiscriminator}} {{/vars}} - {{#vars}} {{#hasValidation}} {{#pattern}} @@ -1189,12 +1254,14 @@ impl std::str::FromStr for {{{classname}}} { }) } } +{{/allOf.size}} {{/oneOf.size}} {{/anyOf.size}} {{/arrayModelType}} {{^anyOf.size}} {{^oneOf.size}} +{{^allOf.size}} // Methods for converting between header::IntoHeaderValue<{{{classname}}}> and HeaderValue #[cfg(feature = "server")] @@ -1226,7 +1293,7 @@ impl std::convert::TryFrom for header::IntoHeaderValue<{{{classname } } } - +{{/allOf.size}} {{/oneOf.size}} {{/anyOf.size}} diff --git a/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml b/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml index 0746dcfc636c..497867c7e6dd 100644 --- a/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml b/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml index eaffaa470cd9..89112488d64f 100644 --- a/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml b/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml index 3d86baffa19f..cf5bc2054129 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml b/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml index 3ad8cd54cd75..eedc191abb99 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs index 4d2fb31b5ab6..4ae4215b9d58 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs @@ -767,15 +767,20 @@ impl std::convert::TryFrom for header::IntoHeaderValue); +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +#[allow(non_camel_case_types)] +pub enum AnyOfGet202Response { + StringType(String), + Uuid(uuid::Uuid), +} impl validator::Validate for AnyOfGet202Response { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - std::result::Result::Ok(()) + match self { + Self::StringType(_) => std::result::Result::Ok(()), + Self::Uuid(_) => std::result::Result::Ok(()), + } } } @@ -790,22 +795,21 @@ impl std::str::FromStr for AnyOfGet202Response { } } -impl PartialEq for AnyOfGet202Response { - fn eq(&self, other: &Self) -> bool { - self.0.get() == other.0.get() - } -} - /// Test a model containing an anyOf of a hash map -/// Any of: -/// - String -/// - std::collections::HashMap -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AnyOfHashMapObject(Box); +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +#[allow(non_camel_case_types)] +pub enum AnyOfHashMapObject { + StringType(String), + HashMapOfStringStringType(std::collections::HashMap), +} impl validator::Validate for AnyOfHashMapObject { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - std::result::Result::Ok(()) + match self { + Self::StringType(_) => std::result::Result::Ok(()), + Self::HashMapOfStringStringType(_) => std::result::Result::Ok(()), + } } } @@ -820,21 +824,19 @@ impl std::str::FromStr for AnyOfHashMapObject { } } -impl PartialEq for AnyOfHashMapObject { - fn eq(&self, other: &Self) -> bool { - self.0.get() == other.0.get() - } -} - /// Test a model containing an anyOf -/// Any of: -/// - String -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct AnyOfObject(Box); +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +#[allow(non_camel_case_types)] +pub enum AnyOfObject { + StringType(String), +} impl validator::Validate for AnyOfObject { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - std::result::Result::Ok(()) + match self { + Self::StringType(_) => std::result::Result::Ok(()), + } } } @@ -849,12 +851,6 @@ impl std::str::FromStr for AnyOfObject { } } -impl PartialEq for AnyOfObject { - fn eq(&self, other: &Self) -> bool { - self.0.get() == other.0.get() - } -} - /// Test containing an anyOf object #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] @@ -1451,14 +1447,18 @@ impl std::convert::TryFrom for header::IntoHeaderValue); +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +#[allow(non_camel_case_types)] +pub enum Model12345AnyOfObject { + StringType(String), +} impl validator::Validate for Model12345AnyOfObject { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - std::result::Result::Ok(()) + match self { + Self::StringType(_) => std::result::Result::Ok(()), + } } } @@ -1473,12 +1473,6 @@ impl std::str::FromStr for Model12345AnyOfObject { } } -impl PartialEq for Model12345AnyOfObject { - fn eq(&self, other: &Self) -> bool { - self.0.get() == other.0.get() - } -} - #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct MultigetGet201Response { @@ -2319,8 +2313,9 @@ pub struct ObjectParam { pub required_param: bool, #[serde(rename = "optionalParam")] + #[validate(range(min = 1u64, max = 10000000000000000000u64))] #[serde(skip_serializing_if = "Option::is_none")] - pub optional_param: Option, + pub optional_param: Option, } impl ObjectParam { @@ -2366,7 +2361,7 @@ impl std::str::FromStr for ObjectParam { #[allow(dead_code)] struct IntermediateRep { pub required_param: Vec, - pub optional_param: Vec, + pub optional_param: Vec, } let mut intermediate_rep = IntermediateRep::default(); @@ -2394,7 +2389,7 @@ impl std::str::FromStr for ObjectParam { ), #[allow(clippy::redundant_clone)] "optionalParam" => intermediate_rep.optional_param.push( - ::from_str(val).map_err(|x| x.to_string())?, + ::from_str(val).map_err(|x| x.to_string())?, ), _ => { return std::result::Result::Err( @@ -2811,34 +2806,23 @@ impl std::ops::DerefMut for Ok { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] #[serde(untagged)] #[allow(non_camel_case_types)] pub enum OneOfGet200Response { - I32(Box), - VecOfString(Box>), + I32Type(i32), + VecOfStringType(Vec), } impl validator::Validate for OneOfGet200Response { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { - Self::I32(_) => std::result::Result::Ok(()), - Self::VecOfString(_) => std::result::Result::Ok(()), + Self::I32Type(_) => std::result::Result::Ok(()), + Self::VecOfStringType(_) => std::result::Result::Ok(()), } } } -impl From for OneOfGet200Response { - fn from(value: i32) -> Self { - Self::I32(Box::new(value)) - } -} -impl From> for OneOfGet200Response { - fn from(value: Vec) -> Self { - Self::VecOfString(Box::new(value)) - } -} - /// Converts Query Parameters representation (style=form, explode=false) to a OneOfGet200Response value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs b/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs index 63c04df4913f..27fa4c5d3375 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs @@ -12,10 +12,12 @@ fn test_oneof_schema_untagged() { let test2 = r#"{"value": ["foo", "bar"]}"#; let test3 = Test { - value: OneOfGet200Response::I32(123.into()), + value: OneOfGet200Response::I32Type(123), }; let test4 = Test { - value: OneOfGet200Response::VecOfString(vec!["foo".to_string(), "bar".to_string()].into()), + value: OneOfGet200Response::VecOfStringType( + vec!["foo".to_string(), "bar".to_string()].into(), + ), }; let test5 = r#"{"value":123}"#; diff --git a/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml b/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml index 01a33e010d1c..fc8ad952d34c 100644 --- a/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml index 111e81d65823..67cfe795ed55 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml @@ -27,6 +27,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 69f5dda3e2ee..10440f80c0bb 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 @@ -1484,163 +1484,31 @@ impl std::convert::TryFrom for header::IntoHeaderValue, - - #[serde(rename = "declawed")] - #[serde(skip_serializing_if = "Option::is_none")] - pub declawed: Option, +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +#[allow(non_camel_case_types)] +pub enum Cat { + Animal(models::Animal), + Object(crate::types::Object), } -impl Cat { - #[allow(clippy::new_without_default, clippy::too_many_arguments)] - pub fn new(class_name: String) -> Cat { - Cat { - class_name, - color: Some(r#"red"#.to_string()), - declawed: None, +impl validator::Validate for Cat { + fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { + match self { + Self::Animal(v) => v.validate(), + Self::Object(v) => v.validate(), } } } -/// Converts the Cat 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 Cat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let params: Vec> = vec![ - Some("className".to_string()), - Some(self.class_name.to_string()), - self.color - .as_ref() - .map(|color| ["color".to_string(), color.to_string()].join(",")), - self.declawed - .as_ref() - .map(|declawed| ["declawed".to_string(), declawed.to_string()].join(",")), - ]; - - write!( - f, - "{}", - params.into_iter().flatten().collect::>().join(",") - ) - } -} - /// Converts Query Parameters representation (style=form, explode=false) to a Cat value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer impl std::str::FromStr for Cat { - type Err = String; + type Err = serde_json::Error; 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 class_name: Vec, - pub color: Vec, - pub declawed: 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 Cat".to_string()); - } - }; - - if let Some(key) = key_result { - #[allow(clippy::match_single_binding)] - match key { - #[allow(clippy::redundant_clone)] - "className" => intermediate_rep.class_name.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - #[allow(clippy::redundant_clone)] - "color" => intermediate_rep.color.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - #[allow(clippy::redundant_clone)] - "declawed" => intermediate_rep.declawed.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - _ => { - return std::result::Result::Err( - "Unexpected key while parsing Cat".to_string(), - ); - } - } - } - - // Get the next key - key_result = string_iter.next(); - } - - // Use the intermediate representation to return the struct - std::result::Result::Ok(Cat { - class_name: intermediate_rep - .class_name - .into_iter() - .next() - .ok_or_else(|| "className missing in Cat".to_string())?, - color: intermediate_rep.color.into_iter().next(), - declawed: intermediate_rep.declawed.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!( - r#"Invalid header value for Cat - 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 Cat - {err}"# - )), - }, - std::result::Result::Err(e) => std::result::Result::Err(format!( - r#"Unable to convert header: {hdr_value:?} to string: {e}"# - )), - } + serde_json::from_str(s) } } @@ -2060,164 +1928,31 @@ 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 Dog { - #[serde(rename = "className")] - #[validate(custom(function = "check_xss_string"))] - pub class_name: String, - - #[serde(rename = "color")] - #[validate(custom(function = "check_xss_string"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub color: Option, - - #[serde(rename = "breed")] - #[validate(custom(function = "check_xss_string"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub breed: Option, +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +#[allow(non_camel_case_types)] +pub enum Dog { + Animal(models::Animal), + Object(crate::types::Object), } -impl Dog { - #[allow(clippy::new_without_default, clippy::too_many_arguments)] - pub fn new(class_name: String) -> Dog { - Dog { - class_name, - color: Some(r#"red"#.to_string()), - breed: None, +impl validator::Validate for Dog { + fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { + match self { + Self::Animal(v) => v.validate(), + Self::Object(v) => v.validate(), } } } -/// Converts the Dog 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 Dog { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let params: Vec> = vec![ - Some("className".to_string()), - Some(self.class_name.to_string()), - self.color - .as_ref() - .map(|color| ["color".to_string(), color.to_string()].join(",")), - self.breed - .as_ref() - .map(|breed| ["breed".to_string(), breed.to_string()].join(",")), - ]; - - write!( - f, - "{}", - params.into_iter().flatten().collect::>().join(",") - ) - } -} - /// Converts Query Parameters representation (style=form, explode=false) to a Dog value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer impl std::str::FromStr for Dog { - type Err = String; + type Err = serde_json::Error; 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 class_name: Vec, - pub color: Vec, - pub breed: 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 Dog".to_string()); - } - }; - - if let Some(key) = key_result { - #[allow(clippy::match_single_binding)] - match key { - #[allow(clippy::redundant_clone)] - "className" => intermediate_rep.class_name.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - #[allow(clippy::redundant_clone)] - "color" => intermediate_rep.color.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - #[allow(clippy::redundant_clone)] - "breed" => intermediate_rep.breed.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - _ => { - return std::result::Result::Err( - "Unexpected key while parsing Dog".to_string(), - ); - } - } - } - - // Get the next key - key_result = string_iter.next(); - } - - // Use the intermediate representation to return the struct - std::result::Result::Ok(Dog { - class_name: intermediate_rep - .class_name - .into_iter() - .next() - .ok_or_else(|| "className missing in Dog".to_string())?, - color: intermediate_rep.color.into_iter().next(), - breed: intermediate_rep.breed.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!( - r#"Invalid header value for Dog - 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 Dog - {err}"# - )), - }, - std::result::Result::Err(e) => std::result::Result::Err(format!( - r#"Unable to convert header: {hdr_value:?} to string: {e}"# - )), - } + serde_json::from_str(s) } } diff --git a/samples/server/petstore/rust-axum/output/petstore/Cargo.toml b/samples/server/petstore/rust-axum/output/petstore/Cargo.toml index 75607ff75963..9d6f7ef2c60f 100644 --- a/samples/server/petstore/rust-axum/output/petstore/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/petstore/Cargo.toml @@ -26,6 +26,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml b/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml index d89007acc726..3ee3c892d33b 100644 --- a/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml index 64fff80aef2e..7807a09849ac 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 index ae19725687b0..cf921ed4c312 100644 --- 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 @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml index 7f53563aedb0..50ac080009b7 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 dbf993dae262..093a718e3727 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 @@ -969,23 +969,23 @@ impl std::convert::TryFrom for header::IntoHeaderValue { } } -#[derive(Debug, Clone, PartialEq, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] #[serde(tag = "op")] #[allow(non_camel_case_types)] pub enum Message { - Hello(Box), - Greeting(Box), - Goodbye(Box), - SomethingCompletelyDifferent(Box), + Hello(models::Hello), + Greeting(models::Greeting), + Goodbye(models::Goodbye), + SomethingCompletelyDifferent(models::SomethingCompletelyDifferent), } impl validator::Validate for Message { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { - Self::Hello(x) => x.validate(), - Self::Greeting(x) => x.validate(), - Self::Goodbye(x) => x.validate(), - Self::SomethingCompletelyDifferent(x) => x.validate(), + Self::Hello(v) => v.validate(), + Self::Greeting(v) => v.validate(), + Self::Goodbye(v) => v.validate(), + Self::SomethingCompletelyDifferent(v) => v.validate(), } } } @@ -1004,27 +1004,6 @@ impl serde::Serialize for Message { } } -impl From for Message { - fn from(value: models::Hello) -> Self { - Self::Hello(Box::new(value)) - } -} -impl From for Message { - fn from(value: models::Greeting) -> Self { - Self::Greeting(Box::new(value)) - } -} -impl From for Message { - fn from(value: models::Goodbye) -> Self { - Self::Goodbye(Box::new(value)) - } -} -impl From for Message { - fn from(value: models::SomethingCompletelyDifferent) -> Self { - Self::SomethingCompletelyDifferent(Box::new(value)) - } -} - /// Converts Query Parameters representation (style=form, explode=false) to a Message value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer @@ -1036,34 +1015,23 @@ impl std::str::FromStr for Message { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] #[serde(untagged)] #[allow(non_camel_case_types)] pub enum SomethingCompletelyDifferent { - VecOfObject(Box>), - Object(Box), + VecOfObjectType(Vec), + Object(crate::types::Object), } impl validator::Validate for SomethingCompletelyDifferent { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { - Self::VecOfObject(_) => std::result::Result::Ok(()), - Self::Object(x) => x.validate(), + Self::VecOfObjectType(_) => std::result::Result::Ok(()), + Self::Object(_) => std::result::Result::Ok(()), } } } -impl From> for SomethingCompletelyDifferent { - fn from(value: Vec) -> Self { - Self::VecOfObject(Box::new(value)) - } -} -impl From for SomethingCompletelyDifferent { - fn from(value: crate::types::Object) -> Self { - Self::Object(Box::new(value)) - } -} - /// Converts Query Parameters representation (style=form, explode=false) to a SomethingCompletelyDifferent value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer diff --git a/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml index 34d2ef981a4c..261c899ae243 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 1d09683c0009..5f4e998f2f92 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 @@ -276,159 +276,29 @@ 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")] - #[validate(custom(function = "check_xss_string"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub sample_property: Option, - - #[serde(rename = "sampleBaseProperty")] - #[validate(custom(function = "check_xss_string"))] - #[serde(skip_serializing_if = "Option::is_none")] - pub sample_base_property: Option, +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[serde(untagged)] +#[allow(non_camel_case_types)] +pub enum FooAllOfObject { + FooBaseAllOf(models::FooBaseAllOf), } -impl FooAllOfObject { - #[allow(clippy::new_without_default, clippy::too_many_arguments)] - pub fn new() -> FooAllOfObject { - FooAllOfObject { - sample_property: None, - sample_base_property: None, +impl validator::Validate for FooAllOfObject { + fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { + match self { + Self::FooBaseAllOf(v) => v.validate(), } } } -/// Converts the FooAllOfObject 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 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| { - [ - "sampleBaseProperty".to_string(), - sample_base_property.to_string(), - ] - .join(",") - }), - ]; - - write!( - f, - "{}", - params.into_iter().flatten().collect::>().join(",") - ) - } -} - /// Converts Query Parameters representation (style=form, explode=false) to a FooAllOfObject value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer impl std::str::FromStr for FooAllOfObject { - type Err = String; + type Err = serde_json::Error; 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 sample_property: Vec, - pub sample_base_property: 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 FooAllOfObject".to_string(), - ); - } - }; - - if let Some(key) = key_result { - #[allow(clippy::match_single_binding)] - match key { - #[allow(clippy::redundant_clone)] - "sampleProperty" => intermediate_rep.sample_property.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - #[allow(clippy::redundant_clone)] - "sampleBaseProperty" => intermediate_rep.sample_base_property.push( - ::from_str(val).map_err(|x| x.to_string())?, - ), - _ => { - return std::result::Result::Err( - "Unexpected key while parsing FooAllOfObject".to_string(), - ); - } - } - } - - // Get the next key - key_result = string_iter.next(); - } - - // 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(), - }) - } -} - -// 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 FooAllOfObject - 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 FooAllOfObject - {err}"# - )), - } - } - std::result::Result::Err(e) => std::result::Result::Err(format!( - r#"Unable to convert header: {hdr_value:?} to string: {e}"# - )), - } + serde_json::from_str(s) } } diff --git a/samples/server/petstore/rust-axum/output/rust-axum-validation-test/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/Cargo.toml index 9186b14edd0e..449f10cdd30a 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-validation-test/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/Cargo.toml @@ -25,6 +25,7 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } +derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } From 49c22e1bfc2a6c4c3281fb8783a7bfc720d5a490 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Thu, 11 Sep 2025 15:02:54 +0900 Subject: [PATCH 02/15] Update --- bin/utils/test_file_list.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/utils/test_file_list.yaml b/bin/utils/test_file_list.yaml index 1d0fb391de09..56c35c27209c 100644 --- a/bin/utils/test_file_list.yaml +++ b/bin/utils/test_file_list.yaml @@ -61,4 +61,4 @@ - filename: "samples/server/petstore/rust-axum/output/rust-axum-oneof/tests/oneof_with_discriminator.rs" sha256: 2d4f5a069fdcb3057bb078d5e75b3de63cd477b97725e457079df24bd2c30600 - filename: "samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs" - sha256: e72fbf81a9849dc7abb7e2169f2fc355c8b1cf991c0e2ffc083126abd9e966e7 + sha256: 605e36c0d4bc3098f577c9bd42f72a1c6357a02fd1af258a6197ca27c2fa0e6d From 20035128e4654dc30dd5ed193340e4ed5a7f3f2c Mon Sep 17 00:00:00 2001 From: linxGnu Date: Fri, 12 Sep 2025 17:05:12 +0900 Subject: [PATCH 03/15] Fix --- bin/utils/test_file_list.yaml | 2 +- .../languages/RustAxumServerCodegen.java | 39 +-- .../main/resources/rust-axum/Cargo.mustache | 1 - .../main/resources/rust-axum/models.mustache | 100 ++---- .../output/apikey-authorization/Cargo.toml | 1 - .../rust-axum/output/apikey-auths/Cargo.toml | 1 - .../rust-axum/output/multipart-v3/Cargo.toml | 1 - .../rust-axum/output/openapi-v3/Cargo.toml | 1 - .../rust-axum/output/openapi-v3/src/models.rs | 75 +++-- .../output/openapi-v3/tests/oneof_untagged.rs | 6 +- .../rust-axum/output/ops-v3/Cargo.toml | 1 - .../Cargo.toml | 1 - .../src/models.rs | 317 ++++++++++++++++-- .../rust-axum/output/petstore/Cargo.toml | 1 - .../output/ping-bearer-auth/Cargo.toml | 1 - .../rust-axum-array-params-test/Cargo.toml | 1 - .../output/rust-axum-header-uuid/Cargo.toml | 1 - .../output/rust-axum-oneof/Cargo.toml | 1 - .../output/rust-axum-oneof/src/models.rs | 40 ++- .../output/rust-axum-test/Cargo.toml | 1 - .../output/rust-axum-test/src/models.rs | 152 ++++++++- .../rust-axum-validation-test/Cargo.toml | 1 - 22 files changed, 577 insertions(+), 168 deletions(-) diff --git a/bin/utils/test_file_list.yaml b/bin/utils/test_file_list.yaml index 56c35c27209c..d9f0ef85e8dc 100644 --- a/bin/utils/test_file_list.yaml +++ b/bin/utils/test_file_list.yaml @@ -61,4 +61,4 @@ - filename: "samples/server/petstore/rust-axum/output/rust-axum-oneof/tests/oneof_with_discriminator.rs" sha256: 2d4f5a069fdcb3057bb078d5e75b3de63cd477b97725e457079df24bd2c30600 - filename: "samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs" - sha256: 605e36c0d4bc3098f577c9bd42f72a1c6357a02fd1af258a6197ca27c2fa0e6d + 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 c03db9dfc9d3..90062643d949 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 @@ -113,8 +113,7 @@ public RustAxumServerCodegen() { SchemaSupportFeature.Simple, SchemaSupportFeature.Composite, SchemaSupportFeature.oneOf, - SchemaSupportFeature.anyOf, - SchemaSupportFeature.allOf + SchemaSupportFeature.anyOf )) .excludeGlobalFeatures( GlobalFeature.Info, @@ -646,7 +645,7 @@ private void postProcessOneAllAnyOfModels(List allModels) { final List csOneOf = cs.getOneOf(); if (csOneOf != null) { processOneAllAnyOfModelDataType(csOneOf); - cs.setAnyOf(csOneOf); + cs.setOneOf(csOneOf); cm.setComposedSchemas(cs); } @@ -656,13 +655,6 @@ private void postProcessOneAllAnyOfModels(List allModels) { cs.setAnyOf(csAnyOf); cm.setComposedSchemas(cs); } - - final List csAllOf = cs.getAllOf(); - if (csAllOf != null) { - processOneAllAnyOfModelDataType(csAllOf); - cs.setAllOf(csAllOf); - cm.setComposedSchemas(cs); - } } if (cm.discriminator != null) { @@ -677,12 +669,6 @@ private void postProcessOneAllAnyOfModels(List allModels) { discriminators.add(cm.discriminator.getPropertyName()); mapDiscriminator.put(model, discriminators); } - - for (String model : cm.allOf) { - final List discriminators = mapDiscriminator.getOrDefault(model, new ArrayList<>()); - discriminators.add(cm.discriminator.getPropertyName()); - mapDiscriminator.put(model, discriminators); - } } } @@ -745,27 +731,30 @@ private void postProcessOneAllAnyOfModels(List allModels) { } private static void processOneAllAnyOfModelDataType(final List cp) { - final HashSet dedup = new HashSet<>(); + HashSet dedupDataTypeWithEnum = new HashSet(); + HashMap dedupDataType = new HashMap(); - final List toRemove = new ArrayList(); + int idx = 0; for (CodegenProperty model : cp) { // Generate a valid name for the enum variant. // Mainly needed for primitive types. model.datatypeWithEnum = camelize(model.dataType.replaceAll("(?:\\w+::)+(\\w+)", "$1") .replace("<", "Of").replace(">", "")).replace(" ", "").replace(",", ""); + if (!dedupDataTypeWithEnum.add(model.datatypeWithEnum)) { + model.datatypeWithEnum += ++idx; + } + + dedupDataType.put(model.getDataType(), dedupDataType.getOrDefault(model.getDataType(), 0) + 1); if (!model.getDataType().matches(String.format(Locale.ROOT, ".*::%s", model.getDatatypeWithEnum()))) { model.isPrimitiveType = true; - model.datatypeWithEnum += "Type"; - } - - if (!dedup.add(model.datatypeWithEnum)) { - toRemove.add(model); } } - for (var model : toRemove) { - cp.remove(model); + for (CodegenProperty model : cp) { + if (dedupDataType.get(model.getDataType()) == 1) { + model.vendorExtensions.put("x-from-trait", true); + } } } diff --git a/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache b/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache index bbbf7bbcbcc2..92f89a371e4f 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/Cargo.mustache @@ -46,7 +46,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 47400bf210f4..a4dd59133390 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -752,11 +752,11 @@ impl std::str::FromStr for {{{classname}}} { {{! general struct}} {{#anyOf.size}} {{#discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(tag = "{{{propertyBaseName}}}")] {{/discriminator}} {{^discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] {{/discriminator}} #[allow(non_camel_case_types)] @@ -797,85 +797,49 @@ impl std::str::FromStr for {{{classname}}} { } } -{{/anyOf.size}} -{{#oneOf.size}} -{{#discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] -#[serde(tag = "{{{propertyBaseName}}}")] -{{/discriminator}} -{{^discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] -#[serde(untagged)] -{{/discriminator}} -#[allow(non_camel_case_types)] -pub enum {{{classname}}} { - {{#composedSchemas}} - {{#oneOf}} - {{{datatypeWithEnum}}}({{{dataType}}}), - {{/oneOf}} - {{/composedSchemas}} -} - -impl validator::Validate for {{{classname}}} -{ - fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - match self { - {{#composedSchemas}} - {{#anyOf}} - {{^isModel}} - Self::{{{datatypeWithEnum}}}(_) => std::result::Result::Ok(()), - {{/isModel}} - {{#isModel}} - Self::{{{datatypeWithEnum}}}(v) => v.validate(), - {{/isModel}} - {{/anyOf}} - {{/composedSchemas}} - } - } -} - {{#discriminator}} impl serde::Serialize for {{{classname}}} { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer { match self { {{#composedSchemas}} - {{#oneOf}} + {{#anyOf}} Self::{{{datatypeWithEnum}}}(x) => x.serialize(serializer), - {{/oneOf}} + {{/anyOf}} {{/composedSchemas}} } } } {{/discriminator}} -/// Converts Query Parameters representation (style=form, explode=false) to a {{{classname}}} value -/// as specified in https://swagger.io/docs/specification/serialization/ -/// Should be implemented in a serde deserializer -impl std::str::FromStr for {{{classname}}} { - type Err = serde_json::Error; - - fn from_str(s: &str) -> std::result::Result { - serde_json::from_str(s) +{{#composedSchemas}} +{{#anyOf}} +{{#vendorExtensions.x-from-trait}} +impl From<{{{dataType}}}> for {{{classname}}} { + fn from(value: {{{dataType}}}) -> Self { + Self::{{{datatypeWithEnum}}}(value) } } +{{/vendorExtensions.x-from-trait}} +{{/anyOf}} +{{/composedSchemas}} -{{/oneOf.size}} -{{#allOf.size}} +{{/anyOf.size}} +{{#oneOf.size}} {{#discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(tag = "{{{propertyBaseName}}}")] {{/discriminator}} {{^discriminator}} -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] {{/discriminator}} #[allow(non_camel_case_types)] pub enum {{{classname}}} { {{#composedSchemas}} - {{#allOf}} + {{#oneOf}} {{{datatypeWithEnum}}}({{{dataType}}}), - {{/allOf}} + {{/oneOf}} {{/composedSchemas}} } @@ -884,14 +848,14 @@ impl validator::Validate for {{{classname}}} fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { {{#composedSchemas}} - {{#allOf}} + {{#oneOf}} {{^isModel}} Self::{{{datatypeWithEnum}}}(_) => std::result::Result::Ok(()), {{/isModel}} {{#isModel}} Self::{{{datatypeWithEnum}}}(v) => v.validate(), {{/isModel}} - {{/allOf}} + {{/oneOf}} {{/composedSchemas}} } } @@ -903,9 +867,9 @@ impl serde::Serialize for {{{classname}}} { where S: serde::Serializer { match self { {{#composedSchemas}} - {{#allOf}} + {{#oneOf}} Self::{{{datatypeWithEnum}}}(x) => x.serialize(serializer), - {{/allOf}} + {{/oneOf}} {{/composedSchemas}} } } @@ -923,10 +887,21 @@ impl std::str::FromStr for {{{classname}}} { } } -{{/allOf.size}} +{{#composedSchemas}} +{{#oneOf}} +{{#vendorExtensions.x-from-trait}} +impl From<{{{dataType}}}> for {{{classname}}} { + fn from(value: {{{dataType}}}) -> Self { + Self::{{{datatypeWithEnum}}}(value) + } +} +{{/vendorExtensions.x-from-trait}} +{{/oneOf}} +{{/composedSchemas}} + +{{/oneOf.size}} {{^anyOf.size}} {{^oneOf.size}} -{{^allOf.size}} #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct {{{classname}}} { @@ -1254,14 +1229,12 @@ impl std::str::FromStr for {{{classname}}} { }) } } -{{/allOf.size}} {{/oneOf.size}} {{/anyOf.size}} {{/arrayModelType}} {{^anyOf.size}} {{^oneOf.size}} -{{^allOf.size}} // Methods for converting between header::IntoHeaderValue<{{{classname}}}> and HeaderValue #[cfg(feature = "server")] @@ -1293,7 +1266,6 @@ impl std::convert::TryFrom for header::IntoHeaderValue<{{{classname } } } -{{/allOf.size}} {{/oneOf.size}} {{/anyOf.size}} diff --git a/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml b/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml index 497867c7e6dd..0746dcfc636c 100644 --- a/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/apikey-authorization/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml b/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml index 89112488d64f..eaffaa470cd9 100644 --- a/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml b/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml index cf5bc2054129..3d86baffa19f 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/multipart-v3/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml b/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml index eedc191abb99..3ad8cd54cd75 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/openapi-v3/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs index 4ae4215b9d58..09d89a5d0fe7 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs @@ -767,18 +767,18 @@ impl std::convert::TryFrom for header::IntoHeaderValue std::result::Result<(), validator::ValidationErrors> { match self { - Self::StringType(_) => std::result::Result::Ok(()), + Self::String(_) => std::result::Result::Ok(()), Self::Uuid(_) => std::result::Result::Ok(()), } } @@ -795,20 +795,31 @@ impl std::str::FromStr for AnyOfGet202Response { } } +impl From for AnyOfGet202Response { + fn from(value: String) -> Self { + Self::String(value) + } +} +impl From for AnyOfGet202Response { + fn from(value: uuid::Uuid) -> Self { + Self::Uuid(value) + } +} + /// Test a model containing an anyOf of a hash map -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] #[allow(non_camel_case_types)] pub enum AnyOfHashMapObject { - StringType(String), - HashMapOfStringStringType(std::collections::HashMap), + String(String), + HashMapOfStringString(std::collections::HashMap), } impl validator::Validate for AnyOfHashMapObject { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { - Self::StringType(_) => std::result::Result::Ok(()), - Self::HashMapOfStringStringType(_) => std::result::Result::Ok(()), + Self::String(_) => std::result::Result::Ok(()), + Self::HashMapOfStringString(_) => std::result::Result::Ok(()), } } } @@ -824,18 +835,31 @@ impl std::str::FromStr for AnyOfHashMapObject { } } +impl From for AnyOfHashMapObject { + fn from(value: String) -> Self { + Self::String(value) + } +} +impl From> for AnyOfHashMapObject { + fn from(value: std::collections::HashMap) -> Self { + Self::HashMapOfStringString(value) + } +} + /// Test a model containing an anyOf -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] #[allow(non_camel_case_types)] pub enum AnyOfObject { - StringType(String), + String(String), + String1(String), } impl validator::Validate for AnyOfObject { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { - Self::StringType(_) => std::result::Result::Ok(()), + Self::String(_) => std::result::Result::Ok(()), + Self::String1(_) => std::result::Result::Ok(()), } } } @@ -1447,17 +1471,19 @@ impl std::convert::TryFrom for header::IntoHeaderValue std::result::Result<(), validator::ValidationErrors> { match self { - Self::StringType(_) => std::result::Result::Ok(()), + Self::String(_) => std::result::Result::Ok(()), + Self::String1(_) => std::result::Result::Ok(()), } } } @@ -2806,19 +2832,19 @@ impl std::ops::DerefMut for Ok { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] #[allow(non_camel_case_types)] pub enum OneOfGet200Response { - I32Type(i32), - VecOfStringType(Vec), + I32(i32), + VecOfString(Vec), } impl validator::Validate for OneOfGet200Response { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { - Self::I32Type(_) => std::result::Result::Ok(()), - Self::VecOfStringType(_) => std::result::Result::Ok(()), + Self::I32(_) => std::result::Result::Ok(()), + Self::VecOfString(_) => std::result::Result::Ok(()), } } } @@ -2834,6 +2860,17 @@ impl std::str::FromStr for OneOfGet200Response { } } +impl From for OneOfGet200Response { + fn from(value: i32) -> Self { + Self::I32(value) + } +} +impl From> for OneOfGet200Response { + fn from(value: Vec) -> Self { + Self::VecOfString(value) + } +} + #[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct OptionalObjectHeader(i32); diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs b/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs index 27fa4c5d3375..40d18c33605f 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/tests/oneof_untagged.rs @@ -12,12 +12,10 @@ fn test_oneof_schema_untagged() { let test2 = r#"{"value": ["foo", "bar"]}"#; let test3 = Test { - value: OneOfGet200Response::I32Type(123), + value: OneOfGet200Response::I32(123), }; let test4 = Test { - value: OneOfGet200Response::VecOfStringType( - vec!["foo".to_string(), "bar".to_string()].into(), - ), + value: OneOfGet200Response::VecOfString(vec!["foo".to_string(), "bar".to_string()].into()), }; let test5 = r#"{"value":123}"#; diff --git a/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml b/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml index fc8ad952d34c..01a33e010d1c 100644 --- a/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/ops-v3/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml index 67cfe795ed55..111e81d65823 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/Cargo.toml @@ -27,7 +27,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 10440f80c0bb..69f5dda3e2ee 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 @@ -1484,31 +1484,163 @@ impl std::convert::TryFrom for header::IntoHeaderValue, + + #[serde(rename = "declawed")] + #[serde(skip_serializing_if = "Option::is_none")] + pub declawed: Option, } -impl validator::Validate for Cat { - fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - match self { - Self::Animal(v) => v.validate(), - Self::Object(v) => v.validate(), +impl Cat { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(class_name: String) -> Cat { + Cat { + class_name, + color: Some(r#"red"#.to_string()), + declawed: None, } } } +/// Converts the Cat 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 Cat { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("className".to_string()), + Some(self.class_name.to_string()), + self.color + .as_ref() + .map(|color| ["color".to_string(), color.to_string()].join(",")), + self.declawed + .as_ref() + .map(|declawed| ["declawed".to_string(), declawed.to_string()].join(",")), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + /// Converts Query Parameters representation (style=form, explode=false) to a Cat value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer impl std::str::FromStr for Cat { - type Err = serde_json::Error; + type Err = String; fn from_str(s: &str) -> std::result::Result { - serde_json::from_str(s) + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub class_name: Vec, + pub color: Vec, + pub declawed: 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 Cat".to_string()); + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "className" => intermediate_rep.class_name.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "color" => intermediate_rep.color.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "declawed" => intermediate_rep.declawed.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing Cat".to_string(), + ); + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(Cat { + class_name: intermediate_rep + .class_name + .into_iter() + .next() + .ok_or_else(|| "className missing in Cat".to_string())?, + color: intermediate_rep.color.into_iter().next(), + declawed: intermediate_rep.declawed.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!( + r#"Invalid header value for Cat - 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 Cat - {err}"# + )), + }, + std::result::Result::Err(e) => std::result::Result::Err(format!( + r#"Unable to convert header: {hdr_value:?} to string: {e}"# + )), + } } } @@ -1928,31 +2060,164 @@ impl std::convert::TryFrom for header::IntoHeaderValue { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] -#[serde(untagged)] -#[allow(non_camel_case_types)] -pub enum Dog { - Animal(models::Animal), - Object(crate::types::Object), +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct Dog { + #[serde(rename = "className")] + #[validate(custom(function = "check_xss_string"))] + pub class_name: String, + + #[serde(rename = "color")] + #[validate(custom(function = "check_xss_string"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub color: Option, + + #[serde(rename = "breed")] + #[validate(custom(function = "check_xss_string"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub breed: Option, } -impl validator::Validate for Dog { - fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - match self { - Self::Animal(v) => v.validate(), - Self::Object(v) => v.validate(), +impl Dog { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(class_name: String) -> Dog { + Dog { + class_name, + color: Some(r#"red"#.to_string()), + breed: None, } } } +/// Converts the Dog 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 Dog { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("className".to_string()), + Some(self.class_name.to_string()), + self.color + .as_ref() + .map(|color| ["color".to_string(), color.to_string()].join(",")), + self.breed + .as_ref() + .map(|breed| ["breed".to_string(), breed.to_string()].join(",")), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + /// Converts Query Parameters representation (style=form, explode=false) to a Dog value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer impl std::str::FromStr for Dog { - type Err = serde_json::Error; + type Err = String; fn from_str(s: &str) -> std::result::Result { - serde_json::from_str(s) + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub class_name: Vec, + pub color: Vec, + pub breed: 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 Dog".to_string()); + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "className" => intermediate_rep.class_name.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "color" => intermediate_rep.color.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "breed" => intermediate_rep.breed.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing Dog".to_string(), + ); + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(Dog { + class_name: intermediate_rep + .class_name + .into_iter() + .next() + .ok_or_else(|| "className missing in Dog".to_string())?, + color: intermediate_rep.color.into_iter().next(), + breed: intermediate_rep.breed.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!( + r#"Invalid header value for Dog - 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 Dog - {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/petstore/Cargo.toml b/samples/server/petstore/rust-axum/output/petstore/Cargo.toml index 9d6f7ef2c60f..75607ff75963 100644 --- a/samples/server/petstore/rust-axum/output/petstore/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/petstore/Cargo.toml @@ -26,7 +26,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml b/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml index 3ee3c892d33b..d89007acc726 100644 --- a/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/ping-bearer-auth/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml index 7807a09849ac..64fff80aef2e 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 index cf921ed4c312..ae19725687b0 100644 --- 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 @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } diff --git a/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml index 50ac080009b7..7f53563aedb0 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-oneof/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 093a718e3727..7f02e99cfc71 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 @@ -969,7 +969,7 @@ impl std::convert::TryFrom for header::IntoHeaderValue { } } -#[derive(Debug, Clone, PartialEq, serde::Deserialize, derive_more::From)] +#[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(tag = "op")] #[allow(non_camel_case_types)] pub enum Message { @@ -1015,18 +1015,39 @@ impl std::str::FromStr for Message { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] +impl From for Message { + fn from(value: models::Hello) -> Self { + Self::Hello(value) + } +} +impl From for Message { + fn from(value: models::Greeting) -> Self { + Self::Greeting(value) + } +} +impl From for Message { + fn from(value: models::Goodbye) -> Self { + Self::Goodbye(value) + } +} +impl From for Message { + fn from(value: models::SomethingCompletelyDifferent) -> Self { + Self::SomethingCompletelyDifferent(value) + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] #[allow(non_camel_case_types)] pub enum SomethingCompletelyDifferent { - VecOfObjectType(Vec), + VecOfObject(Vec), Object(crate::types::Object), } impl validator::Validate for SomethingCompletelyDifferent { fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { match self { - Self::VecOfObjectType(_) => std::result::Result::Ok(()), + Self::VecOfObject(_) => std::result::Result::Ok(()), Self::Object(_) => std::result::Result::Ok(()), } } @@ -1042,3 +1063,14 @@ impl std::str::FromStr for SomethingCompletelyDifferent { serde_json::from_str(s) } } + +impl From> for SomethingCompletelyDifferent { + fn from(value: Vec) -> Self { + Self::VecOfObject(value) + } +} +impl From for SomethingCompletelyDifferent { + fn from(value: crate::types::Object) -> Self { + Self::Object(value) + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml index 261c899ae243..34d2ef981a4c 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-test/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } 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 5f4e998f2f92..1d09683c0009 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 @@ -276,29 +276,159 @@ impl ::std::str::FromStr for FooAdditionalPropertiesObject { } } -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, derive_more::From)] -#[serde(untagged)] -#[allow(non_camel_case_types)] -pub enum FooAllOfObject { - FooBaseAllOf(models::FooBaseAllOf), +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct FooAllOfObject { + #[serde(rename = "sampleProperty")] + #[validate(custom(function = "check_xss_string"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub sample_property: Option, + + #[serde(rename = "sampleBaseProperty")] + #[validate(custom(function = "check_xss_string"))] + #[serde(skip_serializing_if = "Option::is_none")] + pub sample_base_property: Option, } -impl validator::Validate for FooAllOfObject { - fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - match self { - Self::FooBaseAllOf(v) => v.validate(), +impl FooAllOfObject { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> FooAllOfObject { + FooAllOfObject { + sample_property: None, + sample_base_property: None, } } } +/// Converts the FooAllOfObject 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 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| { + [ + "sampleBaseProperty".to_string(), + sample_base_property.to_string(), + ] + .join(",") + }), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + /// Converts Query Parameters representation (style=form, explode=false) to a FooAllOfObject value /// as specified in https://swagger.io/docs/specification/serialization/ /// Should be implemented in a serde deserializer impl std::str::FromStr for FooAllOfObject { - type Err = serde_json::Error; + type Err = String; fn from_str(s: &str) -> std::result::Result { - serde_json::from_str(s) + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub sample_property: Vec, + pub sample_base_property: 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 FooAllOfObject".to_string(), + ); + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "sampleProperty" => intermediate_rep.sample_property.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "sampleBaseProperty" => intermediate_rep.sample_base_property.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing FooAllOfObject".to_string(), + ); + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // 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(), + }) + } +} + +// 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 FooAllOfObject - 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 FooAllOfObject - {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-validation-test/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/Cargo.toml index 449f10cdd30a..9186b14edd0e 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-validation-test/Cargo.toml +++ b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/Cargo.toml @@ -25,7 +25,6 @@ axum-extra = { version = "0.10", features = ["cookie", "query"] } base64 = "0.22" bytes = "1" chrono = { version = "0.4", features = ["serde"] } -derive_more = { version = "2", features = ["from"] } frunk = { version = "0.4", optional = true } frunk-enum-core = { version = "0.3", optional = true } frunk-enum-derive = { version = "0.3", optional = true } From 02747aabd8dbb5e053f0d25bc9c88263056d0710 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Fri, 12 Sep 2025 17:10:32 +0900 Subject: [PATCH 04/15] Update --- .../codegen/languages/RustAxumServerCodegen.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) 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 90062643d949..876172a9b405 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 @@ -634,7 +634,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation return op; } - private void postProcessOneAllAnyOfModels(List allModels) { + private void postProcessPolymorphism(List allModels) { final HashMap> mapDiscriminator = new HashMap<>(); for (ModelMap mo : allModels) { @@ -644,14 +644,14 @@ private void postProcessOneAllAnyOfModels(List allModels) { if (cs != null) { final List csOneOf = cs.getOneOf(); if (csOneOf != null) { - processOneAllAnyOfModelDataType(csOneOf); + processPolymorphismDataType(csOneOf); cs.setOneOf(csOneOf); cm.setComposedSchemas(cs); } final List csAnyOf = cs.getAnyOf(); if (csAnyOf != null) { - processOneAllAnyOfModelDataType(csAnyOf); + processPolymorphismDataType(csAnyOf); cs.setAnyOf(csAnyOf); cm.setComposedSchemas(cs); } @@ -730,7 +730,7 @@ private void postProcessOneAllAnyOfModels(List allModels) { } } - private static void processOneAllAnyOfModelDataType(final List cp) { + private static void processPolymorphismDataType(final List cp) { HashSet dedupDataTypeWithEnum = new HashSet(); HashMap dedupDataType = new HashMap(); @@ -760,7 +760,7 @@ private static void processOneAllAnyOfModelDataType(final List @Override public OperationsMap postProcessOperationsWithModels(final OperationsMap operationsMap, List allModels) { - postProcessOneAllAnyOfModels(allModels); + postProcessPolymorphism(allModels); final OperationMap operations = operationsMap.getOperations(); operations.put("classnamePascalCase", camelize(operations.getClassname())); From e954648eeaca8348c25891f03b14d32304794ea0 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Fri, 12 Sep 2025 17:21:52 +0900 Subject: [PATCH 05/15] Update --- .../openapitools/codegen/languages/RustAxumServerCodegen.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java index 876172a9b405..44d40f2fec47 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 @@ -113,7 +113,8 @@ public RustAxumServerCodegen() { SchemaSupportFeature.Simple, SchemaSupportFeature.Composite, SchemaSupportFeature.oneOf, - SchemaSupportFeature.anyOf + SchemaSupportFeature.anyOf, + SchemaSupportFeature.allOf )) .excludeGlobalFeatures( GlobalFeature.Info, From 3cbdef1321d39bd5e4c368f7dc265a272be63be6 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Fri, 12 Sep 2025 17:28:19 +0900 Subject: [PATCH 06/15] Update --- .../main/resources/rust-axum/models.mustache | 22 +++++++++---------- .../output/rust-axum-oneof/src/models.rs | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) 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 a4dd59133390..98c40d339b28 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -861,6 +861,17 @@ impl validator::Validate for {{{classname}}} } } +/// Converts Query Parameters representation (style=form, explode=false) to a {{{classname}}} value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for {{{classname}}} { + type Err = serde_json::Error; + + fn from_str(s: &str) -> std::result::Result { + serde_json::from_str(s) + } +} + {{#discriminator}} impl serde::Serialize for {{{classname}}} { fn serialize(&self, serializer: S) -> Result @@ -876,17 +887,6 @@ impl serde::Serialize for {{{classname}}} { } {{/discriminator}} -/// Converts Query Parameters representation (style=form, explode=false) to a {{{classname}}} value -/// as specified in https://swagger.io/docs/specification/serialization/ -/// Should be implemented in a serde deserializer -impl std::str::FromStr for {{{classname}}} { - type Err = serde_json::Error; - - fn from_str(s: &str) -> std::result::Result { - serde_json::from_str(s) - } -} - {{#composedSchemas}} {{#oneOf}} {{#vendorExtensions.x-from-trait}} 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 7f02e99cfc71..afcbf83f5bfb 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 @@ -990,6 +990,17 @@ impl validator::Validate for Message { } } +/// Converts Query Parameters representation (style=form, explode=false) to a Message value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for Message { + type Err = serde_json::Error; + + fn from_str(s: &str) -> std::result::Result { + serde_json::from_str(s) + } +} + impl serde::Serialize for Message { fn serialize(&self, serializer: S) -> Result where @@ -1004,17 +1015,6 @@ impl serde::Serialize for Message { } } -/// Converts Query Parameters representation (style=form, explode=false) to a Message value -/// as specified in https://swagger.io/docs/specification/serialization/ -/// Should be implemented in a serde deserializer -impl std::str::FromStr for Message { - type Err = serde_json::Error; - - fn from_str(s: &str) -> std::result::Result { - serde_json::from_str(s) - } -} - impl From for Message { fn from(value: models::Hello) -> Self { Self::Hello(value) From 26f1ad4230345bba0204198be6329557d513ce9a Mon Sep 17 00:00:00 2001 From: linxGnu Date: Sat, 13 Sep 2025 01:55:43 +0900 Subject: [PATCH 07/15] Update --- .../languages/RustAxumServerCodegen.java | 94 +++++++++++-------- .../main/resources/rust-axum/models.mustache | 27 ++++-- .../output/apikey-authorization/src/models.rs | 14 +-- .../output/apikey-auths/src/models.rs | 14 +-- .../output/multipart-v3/src/models.rs | 6 ++ .../rust-axum/output/openapi-v3/src/models.rs | 18 +++- .../src/models.rs | 73 ++++++++++---- .../rust-axum/output/petstore/src/models.rs | 18 ++-- .../output/rust-axum-oneof/src/models.rs | 15 +-- .../output/rust-axum-test/src/models.rs | 8 +- 10 files changed, 179 insertions(+), 108 deletions(-) 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 44d40f2fec47..9f1dfb019e10 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 @@ -676,56 +676,56 @@ private void postProcessPolymorphism(List allModels) { for (ModelMap mo : allModels) { final CodegenModel cm = mo.getModel(); - for (CodegenProperty var : cm.vars) { - var.isDiscriminator = false; - } - final List discriminatorsForModel = mapDiscriminator.get(cm.getSchemaName()); - if (discriminatorsForModel != null) { - for (String discriminator : discriminatorsForModel) { - boolean hasDiscriminatorDefined = false; + for (CodegenProperty var : cm.vars) { + var.isDiscriminator = false; + } + boolean hasDiscriminatorDefined = false; + for (String discriminator : discriminatorsForModel) { for (CodegenProperty var : cm.vars) { - if (var.baseName.equals(discriminator)) { + if (var.baseName.equals(discriminator) || var.name.equals(discriminator)) { var.isDiscriminator = true; hasDiscriminatorDefined = true; break; } } + } - // If the discriminator field is not a defined attribute in the variant structure, create it. - if (!hasDiscriminatorDefined) { - CodegenProperty property = new CodegenProperty(); - - // Static attributes - // Only strings are supported by serde for tag field types, so it's the only one we'll deal with - property.openApiType = "string"; - property.complexType = "string"; - property.dataType = "String"; - property.datatypeWithEnum = "String"; - property.baseType = "string"; - property.required = true; - property.isPrimitiveType = true; - property.isString = true; - property.isDiscriminator = true; - - // Attributes based on the discriminator value - property.baseName = discriminator; - property.name = discriminator; - property.nameInCamelCase = camelize(discriminator); - property.nameInPascalCase = property.nameInCamelCase.substring(0, 1).toUpperCase(Locale.ROOT) + property.nameInCamelCase.substring(1); - property.nameInSnakeCase = underscore(discriminator).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.jsonSchema = String.format(Locale.ROOT, "{ \"default\":\"%s\"; \"type\":\"string\" }", cm.getSchemaName()); - - cm.vars.add(property); - } + // If the discriminator field is not a defined attribute in the variant structure, create it. + if (!hasDiscriminatorDefined && !discriminatorsForModel.isEmpty()) { + final String discriminator = discriminatorsForModel.get(0); + + CodegenProperty property = new CodegenProperty(); + + // Static attributes + // Only strings are supported by serde for tag field types, so it's the only one we'll deal with + property.openApiType = "string"; + property.complexType = "string"; + property.dataType = "String"; + property.datatypeWithEnum = "String"; + property.baseType = "string"; + property.required = true; + property.isPrimitiveType = true; + property.isString = true; + property.isDiscriminator = true; + + // Attributes based on the discriminator value + property.baseName = discriminator; + property.name = discriminator; + property.nameInCamelCase = camelize(discriminator); + property.nameInPascalCase = property.nameInCamelCase.substring(0, 1).toUpperCase(Locale.ROOT) + property.nameInCamelCase.substring(1); + property.nameInSnakeCase = underscore(discriminator).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.jsonSchema = String.format(Locale.ROOT, "{ \"default\":\"%s\"; \"type\":\"string\" }", cm.getSchemaName()); + + cm.vars.add(property); } } } @@ -995,6 +995,9 @@ public String toDefaultValue(final Schema p) { } else if (ModelUtils.isNumberSchema(p)) { if (p.getDefault() != null) { defaultValue = p.getDefault().toString(); + if (!defaultValue.contains(".")) { + defaultValue += ".0"; + } } } else if (ModelUtils.isIntegerSchema(p)) { if (p.getDefault() != null) { @@ -1111,7 +1114,7 @@ public void postProcessFile(File file, String fileType) { String cmd = System.getenv("RUST_POST_PROCESS_FILE"); if (StringUtils.isEmpty(cmd)) { cmd = "rustfmt"; - command = new String[]{cmd, "--edition", "2021", fileName}; + command = new String[]{cmd, "--edition", "2024", fileName}; } else { command = new String[]{cmd, fileName}; } @@ -1182,6 +1185,15 @@ protected String getParameterDataType(final Parameter parameter, final Schema sc return null; } + @Override + public String toVarName(String name) { + final var varName = super.toVarName(name); + if (varName.startsWith("r#")) + return "r_" + varName.substring(2); + else + return varName; + } + static class PathMethodOperations { public String path; public ArrayList methodOperations; 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 98c40d339b28..ef25abc6c45a 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -1017,15 +1017,17 @@ pub struct {{{classname}}} { {{/isNullable}} {{/hasValidation}} {{#required}} - pub {{{name}}}: {{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, + pub {{{name}}}: {{^isDiscriminator}}{{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}{{/isDiscriminator}}{{#isDiscriminator}}{{{dataType}}}{{/isDiscriminator}}, {{/required}} {{^required}} +{{^isDiscriminator}} {{#isNullable}} #[serde(deserialize_with = "deserialize_optional_nullable")] #[serde(default = "default_optional_nullable")] {{/isNullable}} #[serde(skip_serializing_if="Option::is_none")] - pub {{{name}}}: Option<{{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}>, +{{/isDiscriminator}} + pub {{{name}}}: {{^isDiscriminator}}Option<{{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}>{{/isDiscriminator}}{{#isDiscriminator}}{{{dataType}}}{{/isDiscriminator}}, {{/required}} {{/vars}} @@ -1075,9 +1077,9 @@ fn validate_byte_{{#lambda.lowercase}}{{{classname}}}_{{{name}}}{{/lambda.lowerc impl {{{classname}}} { #[allow(clippy::new_without_default, clippy::too_many_arguments)] - pub fn new({{#vars}}{{^defaultValue}}{{{name}}}: {{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, {{/defaultValue}}{{/vars}}) -> {{{classname}}} { + pub fn new({{#vars}}{{^defaultValue}}{{{name}}}: {{^isDiscriminator}}{{#isNullable}}Nullable<{{/isNullable}}{{/isDiscriminator}}{{{dataType}}}{{^isDiscriminator}}{{#isNullable}}>{{/isNullable}}{{/isDiscriminator}}, {{/defaultValue}}{{/vars}}) -> {{{classname}}} { {{{classname}}} { -{{#vars}} {{#defaultValue}}{{{name}}}: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{{name}}}{{/defaultValue}}, +{{#vars}} {{^isDiscriminator}}{{#defaultValue}}{{{name}}}: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{{name}}}{{/defaultValue}}{{/isDiscriminator}}{{#isDiscriminator}}{{{name}}}: Self::_name_for_{{{name}}}(){{/isDiscriminator}}, {{/vars}} } } @@ -1090,6 +1092,7 @@ impl std::fmt::Display for {{{classname}}} { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ {{#vars}} +{{^isDiscriminator}} {{#isByteArray}} // Skipping {{baseName}} in query parameter serialization {{/isByteArray}} @@ -1102,21 +1105,19 @@ impl std::fmt::Display for {{{classname}}} { {{^isPrimitiveType}} // Skipping {{baseName}} in query parameter serialization {{/isPrimitiveType}} -{{^isByteArray}}{{^isBinary}}{{^isMap}}{{#isPrimitiveType}} +{{^isByteArray}}{{^isBinary}}{{^isMap}} +{{#isPrimitiveType}} {{#required}} Some("{{{baseName}}}".to_string()), {{^isArray}} {{#isNullable}} - Some(self.{{{name}}}.as_ref().map_or("null".to_string(), |x| x.to_string())), + // Skipping {{baseName}} in query parameter serialization {{/isNullable}} {{^isNullable}} Some(self.{{{name}}}.to_string()), {{/isNullable}} {{/isArray}} {{#isArray}} -{{#isNullable}} - Some(self.{{{name}}}.as_ref().map_or(vec!["null".to_string()], |x| x.iter().map(|x| x.to_string()).collect::>().join(","))), -{{/isNullable}} {{^isNullable}} Some(self.{{{name}}}.iter().map(|x| x.to_string()).collect::>().join(",")), {{/isNullable}} @@ -1145,7 +1146,8 @@ impl std::fmt::Display for {{{classname}}} { ].join(",") }), {{/required}} -{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}} +{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}}{{/isDiscriminator}} +{{#isDiscriminator}} Some("{{{baseName}}}".to_string()), Some(self.{{{name}}}.clone()),{{/isDiscriminator}} {{/vars}} ]; @@ -1219,12 +1221,17 @@ impl std::str::FromStr for {{{classname}}} { // Use the intermediate representation to return the struct std::result::Result::Ok({{{classname}}} { {{#vars}} + {{^isDiscriminator}} {{#isNullable}} {{{name}}}: std::result::Result::Err("Nullable types not supported in {{{classname}}}".to_string())?, {{/isNullable}} {{^isNullable}} {{{name}}}: intermediate_rep.{{{name}}}.into_iter().next(){{#required}}.ok_or_else(|| "{{{baseName}}} missing in {{{classname}}}".to_string())?{{/required}}, {{/isNullable}} + {{/isDiscriminator}} + {{#isDiscriminator}} + {{{name}}}: intermediate_rep.{{{name}}}.into_iter().next().ok_or_else(|| "{{{baseName}}} missing in {{{classname}}}".to_string())?, + {{/isDiscriminator}} {{/vars}} }) } diff --git a/samples/server/petstore/rust-axum/output/apikey-authorization/src/models.rs b/samples/server/petstore/rust-axum/output/apikey-authorization/src/models.rs index b20237912cd0..5d7b8b641a22 100644 --- a/samples/server/petstore/rust-axum/output/apikey-authorization/src/models.rs +++ b/samples/server/petstore/rust-axum/output/apikey-authorization/src/models.rs @@ -594,7 +594,7 @@ pub struct PaymentMethod { #[serde(rename = "type")] #[validate(custom(function = "check_xss_string"))] #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, + pub r_type: Option, } impl PaymentMethod { @@ -602,7 +602,7 @@ impl PaymentMethod { pub fn new() -> PaymentMethod { PaymentMethod { name: None, - r#type: None, + r_type: None, } } } @@ -616,9 +616,9 @@ impl std::fmt::Display for PaymentMethod { self.name .as_ref() .map(|name| ["name".to_string(), name.to_string()].join(",")), - self.r#type + self.r_type .as_ref() - .map(|r#type| ["type".to_string(), r#type.to_string()].join(",")), + .map(|r_type| ["type".to_string(), r_type.to_string()].join(",")), ]; write!( @@ -641,7 +641,7 @@ impl std::str::FromStr for PaymentMethod { #[allow(dead_code)] struct IntermediateRep { pub name: Vec, - pub r#type: Vec, + pub r_type: Vec, } let mut intermediate_rep = IntermediateRep::default(); @@ -668,7 +668,7 @@ impl std::str::FromStr for PaymentMethod { ::from_str(val).map_err(|x| x.to_string())?, ), #[allow(clippy::redundant_clone)] - "type" => intermediate_rep.r#type.push( + "type" => intermediate_rep.r_type.push( ::from_str(val).map_err(|x| x.to_string())?, ), _ => { @@ -686,7 +686,7 @@ impl std::str::FromStr for PaymentMethod { // Use the intermediate representation to return the struct std::result::Result::Ok(PaymentMethod { name: intermediate_rep.name.into_iter().next(), - r#type: intermediate_rep.r#type.into_iter().next(), + r_type: intermediate_rep.r_type.into_iter().next(), }) } } diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/src/models.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/models.rs index b20237912cd0..5d7b8b641a22 100644 --- a/samples/server/petstore/rust-axum/output/apikey-auths/src/models.rs +++ b/samples/server/petstore/rust-axum/output/apikey-auths/src/models.rs @@ -594,7 +594,7 @@ pub struct PaymentMethod { #[serde(rename = "type")] #[validate(custom(function = "check_xss_string"))] #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, + pub r_type: Option, } impl PaymentMethod { @@ -602,7 +602,7 @@ impl PaymentMethod { pub fn new() -> PaymentMethod { PaymentMethod { name: None, - r#type: None, + r_type: None, } } } @@ -616,9 +616,9 @@ impl std::fmt::Display for PaymentMethod { self.name .as_ref() .map(|name| ["name".to_string(), name.to_string()].join(",")), - self.r#type + self.r_type .as_ref() - .map(|r#type| ["type".to_string(), r#type.to_string()].join(",")), + .map(|r_type| ["type".to_string(), r_type.to_string()].join(",")), ]; write!( @@ -641,7 +641,7 @@ impl std::str::FromStr for PaymentMethod { #[allow(dead_code)] struct IntermediateRep { pub name: Vec, - pub r#type: Vec, + pub r_type: Vec, } let mut intermediate_rep = IntermediateRep::default(); @@ -668,7 +668,7 @@ impl std::str::FromStr for PaymentMethod { ::from_str(val).map_err(|x| x.to_string())?, ), #[allow(clippy::redundant_clone)] - "type" => intermediate_rep.r#type.push( + "type" => intermediate_rep.r_type.push( ::from_str(val).map_err(|x| x.to_string())?, ), _ => { @@ -686,7 +686,7 @@ impl std::str::FromStr for PaymentMethod { // Use the intermediate representation to return the struct std::result::Result::Ok(PaymentMethod { name: intermediate_rep.name.into_iter().next(), - r#type: intermediate_rep.r#type.into_iter().next(), + r_type: intermediate_rep.r_type.into_iter().next(), }) } } diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs b/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs index 7b10d2066db3..8b6885bab3ed 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs @@ -96,12 +96,16 @@ impl std::fmt::Display for MultipartRelatedRequest { let params: Vec> = vec![ // Skipping object_field in query parameter serialization + + // Skipping optional_binary_field in query parameter serialization // Skipping optional_binary_field in query parameter serialization + // Skipping required_binary_field in query parameter serialization // Skipping required_binary_field in query parameter serialization + ]; write!( @@ -587,9 +591,11 @@ impl std::fmt::Display for MultipleIdenticalMimeTypesPostRequest { // Skipping binary1 in query parameter serialization // Skipping binary1 in query parameter serialization + // Skipping binary2 in query parameter serialization // Skipping binary2 in query parameter serialization + ]; write!( diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs index 09d89a5d0fe7..1f2d19c9a80b 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs @@ -907,8 +907,12 @@ impl std::fmt::Display for AnyOfProperty { let params: Vec> = vec![ // Skipping requiredAnyOf in query parameter serialization + + // Skipping optionalAnyOf in query parameter serialization + + ]; write!( @@ -1925,11 +1929,7 @@ impl std::fmt::Display for NullableTest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("nullable".to_string()), - Some( - self.nullable - .as_ref() - .map_or("null".to_string(), |x| x.to_string()), - ), + // Skipping nullable in query parameter serialization self.nullable_with_null_default .as_ref() .map(|nullable_with_null_default| { @@ -2524,12 +2524,20 @@ impl std::fmt::Display for ObjectUntypedProps { let params: Vec> = vec![ // Skipping required_untyped in query parameter serialization + + // Skipping required_untyped_nullable in query parameter serialization + + // Skipping not_required_untyped in query parameter serialization + + // Skipping not_required_untyped_nullable in query parameter serialization + + ]; write!( 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 69f5dda3e2ee..f876073374a5 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 @@ -238,9 +238,11 @@ impl std::fmt::Display for AdditionalPropertiesClass { let params: Vec> = vec![ // Skipping map_property in query parameter serialization + // Skipping map_of_map_property in query parameter serialization // Skipping map_of_map_property in query parameter serialization + ]; write!( @@ -349,6 +351,8 @@ impl std::convert::TryFrom for header::IntoHeaderValue, } +impl Animal { + fn _name_for_class_name() -> String { + String::from("Animal") + } + + fn _serialize_class_name(_: &String, s: S) -> Result + where + S: serde::Serializer, + { + s.serialize_str(&Self::_name_for_class_name()) + } +} + impl Animal { #[allow(clippy::new_without_default, clippy::too_many_arguments)] pub fn new(class_name: String) -> Animal { Animal { - class_name, + class_name: Self::_name_for_class_name(), color: Some(r#"red"#.to_string()), } } @@ -376,7 +393,7 @@ impl std::fmt::Display for Animal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("className".to_string()), - Some(self.class_name.to_string()), + Some(self.class_name.clone()), self.color .as_ref() .map(|color| ["color".to_string(), color.to_string()].join(",")), @@ -649,7 +666,7 @@ pub struct ApiResponse { #[serde(rename = "type")] #[validate(custom(function = "check_xss_string"))] #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, + pub r_type: Option, #[serde(rename = "message")] #[validate(custom(function = "check_xss_string"))] @@ -662,7 +679,7 @@ impl ApiResponse { pub fn new() -> ApiResponse { ApiResponse { code: None, - r#type: None, + r_type: None, message: None, } } @@ -677,9 +694,9 @@ impl std::fmt::Display for ApiResponse { self.code .as_ref() .map(|code| ["code".to_string(), code.to_string()].join(",")), - self.r#type + self.r_type .as_ref() - .map(|r#type| ["type".to_string(), r#type.to_string()].join(",")), + .map(|r_type| ["type".to_string(), r_type.to_string()].join(",")), self.message .as_ref() .map(|message| ["message".to_string(), message.to_string()].join(",")), @@ -705,7 +722,7 @@ impl std::str::FromStr for ApiResponse { #[allow(dead_code)] struct IntermediateRep { pub code: Vec, - pub r#type: Vec, + pub r_type: Vec, pub message: Vec, } @@ -733,7 +750,7 @@ impl std::str::FromStr for ApiResponse { ::from_str(val).map_err(|x| x.to_string())?, ), #[allow(clippy::redundant_clone)] - "type" => intermediate_rep.r#type.push( + "type" => intermediate_rep.r_type.push( ::from_str(val).map_err(|x| x.to_string())?, ), #[allow(clippy::redundant_clone)] @@ -755,7 +772,7 @@ impl std::str::FromStr for ApiResponse { // Use the intermediate representation to return the struct std::result::Result::Ok(ApiResponse { code: intermediate_rep.code.into_iter().next(), - r#type: intermediate_rep.r#type.into_iter().next(), + r_type: intermediate_rep.r_type.into_iter().next(), message: intermediate_rep.message.into_iter().next(), }) } @@ -828,6 +845,8 @@ impl std::fmt::Display for ArrayOfArrayOfNumberOnly { let params: Vec> = vec![ // Skipping ArrayArrayNumber in query parameter serialization + + ]; write!( @@ -3337,7 +3356,9 @@ impl std::str::FromStr for List { let val = match string_iter.next() { Some(x) => x, None => { - return std::result::Result::Err("Missing value while parsing List".to_string()); + return std::result::Result::Err( + "Missing value while parsing List".to_string(), + ); } }; @@ -3450,11 +3471,14 @@ impl std::fmt::Display for MapTest { // Skipping map_map_of_string in query parameter serialization // Skipping map_map_of_string in query parameter serialization + // Skipping map_map_of_enum in query parameter serialization // Skipping map_map_of_enum in query parameter serialization + // Skipping map_of_enum_string in query parameter serialization + ]; write!( @@ -3619,11 +3643,16 @@ impl std::fmt::Display for MixedPropertiesAndAdditionalPropertiesClass { let params: Vec> = vec![ // Skipping uuid in query parameter serialization + + // Skipping dateTime in query parameter serialization + + // Skipping map in query parameter serialization // Skipping map in query parameter serialization + ]; write!( @@ -3976,7 +4005,9 @@ impl std::str::FromStr for Name { let val = match string_iter.next() { Some(x) => x, None => { - return std::result::Result::Err("Missing value while parsing Name".to_string()); + return std::result::Result::Err( + "Missing value while parsing Name".to_string(), + ); } }; @@ -4221,6 +4252,8 @@ impl std::fmt::Display for ObjectContainingObjectWithOnlyAdditionalProperties { let params: Vec> = vec![ // Skipping inner in query parameter serialization + + ]; write!( @@ -5283,13 +5316,13 @@ impl std::convert::TryFrom for header::IntoHeaderValue, + pub r_return: Option, } impl Return { #[allow(clippy::new_without_default, clippy::too_many_arguments)] pub fn new() -> Return { - Return { r#return: None } + Return { r_return: None } } } @@ -5299,9 +5332,9 @@ impl Return { impl std::fmt::Display for Return { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ - self.r#return + self.r_return .as_ref() - .map(|r#return| ["return".to_string(), r#return.to_string()].join(",")), + .map(|r_return| ["return".to_string(), r_return.to_string()].join(",")), ]; write!( @@ -5323,7 +5356,7 @@ impl std::str::FromStr for Return { #[derive(Default)] #[allow(dead_code)] struct IntermediateRep { - pub r#return: Vec, + pub r_return: Vec, } let mut intermediate_rep = IntermediateRep::default(); @@ -5346,7 +5379,7 @@ impl std::str::FromStr for Return { #[allow(clippy::match_single_binding)] match key { #[allow(clippy::redundant_clone)] - "return" => intermediate_rep.r#return.push( + "return" => intermediate_rep.r_return.push( ::from_str(val).map_err(|x| x.to_string())?, ), _ => { @@ -5363,7 +5396,7 @@ impl std::str::FromStr for Return { // Use the intermediate representation to return the struct std::result::Result::Ok(Return { - r#return: intermediate_rep.r#return.into_iter().next(), + r_return: intermediate_rep.r_return.into_iter().next(), }) } } @@ -6611,7 +6644,9 @@ impl std::str::FromStr for User { let val = match string_iter.next() { Some(x) => x, None => { - return std::result::Result::Err("Missing value while parsing User".to_string()); + return std::result::Result::Err( + "Missing value while parsing User".to_string(), + ); } }; 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 107ff2617b50..9db0d59cbfa3 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/models.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/models.rs @@ -177,7 +177,7 @@ pub struct ApiResponse { #[serde(rename = "type")] #[validate(custom(function = "check_xss_string"))] #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, + pub r_type: Option, #[serde(rename = "message")] #[validate(custom(function = "check_xss_string"))] @@ -190,7 +190,7 @@ impl ApiResponse { pub fn new() -> ApiResponse { ApiResponse { code: None, - r#type: None, + r_type: None, message: None, } } @@ -205,9 +205,9 @@ impl std::fmt::Display for ApiResponse { self.code .as_ref() .map(|code| ["code".to_string(), code.to_string()].join(",")), - self.r#type + self.r_type .as_ref() - .map(|r#type| ["type".to_string(), r#type.to_string()].join(",")), + .map(|r_type| ["type".to_string(), r_type.to_string()].join(",")), self.message .as_ref() .map(|message| ["message".to_string(), message.to_string()].join(",")), @@ -233,7 +233,7 @@ impl std::str::FromStr for ApiResponse { #[allow(dead_code)] struct IntermediateRep { pub code: Vec, - pub r#type: Vec, + pub r_type: Vec, pub message: Vec, } @@ -261,7 +261,7 @@ impl std::str::FromStr for ApiResponse { ::from_str(val).map_err(|x| x.to_string())?, ), #[allow(clippy::redundant_clone)] - "type" => intermediate_rep.r#type.push( + "type" => intermediate_rep.r_type.push( ::from_str(val).map_err(|x| x.to_string())?, ), #[allow(clippy::redundant_clone)] @@ -283,7 +283,7 @@ impl std::str::FromStr for ApiResponse { // Use the intermediate representation to return the struct std::result::Result::Ok(ApiResponse { code: intermediate_rep.code.into_iter().next(), - r#type: intermediate_rep.r#type.into_iter().next(), + r_type: intermediate_rep.r_type.into_iter().next(), message: intermediate_rep.message.into_iter().next(), }) } @@ -1491,7 +1491,9 @@ impl std::str::FromStr for User { let val = match string_iter.next() { Some(x) => x, None => { - return std::result::Result::Err("Missing value while parsing User".to_string()); + return std::result::Result::Err( + "Missing value while parsing User".to_string(), + ); } }; 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 afcbf83f5bfb..165accb47190 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 @@ -92,7 +92,10 @@ impl Goodbye { impl Goodbye { #[allow(clippy::new_without_default, clippy::too_many_arguments)] pub fn new(op: String, d: models::GoodbyeD) -> Goodbye { - Goodbye { op, d } + Goodbye { + op: Self::_name_for_op(), + d, + } } } @@ -103,7 +106,7 @@ impl std::fmt::Display for Goodbye { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("op".to_string()), - Some(self.op.to_string()), + Some(self.op.clone()), // Skipping d in query parameter serialization ]; @@ -394,7 +397,7 @@ impl Greeting { pub fn new(d: models::GreetingD) -> Greeting { Greeting { d, - op: r#"Greeting"#.to_string(), + op: Self::_name_for_op(), } } } @@ -407,7 +410,7 @@ impl std::fmt::Display for Greeting { let params: Vec> = vec![ // Skipping d in query parameter serialization Some("op".to_string()), - Some(self.op.to_string()), + Some(self.op.clone()), ]; write!( @@ -698,7 +701,7 @@ impl Hello { #[allow(clippy::new_without_default, clippy::too_many_arguments)] pub fn new(d: models::HelloD) -> Hello { Hello { - op: r#"Hello"#.to_string(), + op: Self::_name_for_op(), d, } } @@ -711,7 +714,7 @@ impl std::fmt::Display for Hello { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("op".to_string()), - Some(self.op.to_string()), + Some(self.op.clone()), // Skipping d in query parameter serialization ]; 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 1d09683c0009..56d54a25c248 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 @@ -100,11 +100,7 @@ impl std::fmt::Display for FooANullableContainer { .join(",") }), Some("RequiredNullableThing".to_string()), - Some( - self.required_nullable_thing - .as_ref() - .map_or("null".to_string(), |x| x.to_string()), - ), + // Skipping RequiredNullableThing in query parameter serialization ]; write!( @@ -883,6 +879,8 @@ impl std::fmt::Display for FooObjectOfObjects { let params: Vec> = vec![ // Skipping inner in query parameter serialization + + ]; write!( From bb922e4da6dd590907bf9a8a44ef0cc81d98a5e3 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Sat, 13 Sep 2025 02:13:36 +0900 Subject: [PATCH 08/15] Update --- .../codegen/languages/RustAxumServerCodegen.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 9f1dfb019e10..c408df9fc5cb 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 @@ -1188,10 +1188,15 @@ protected String getParameterDataType(final Parameter parameter, final Schema sc @Override public String toVarName(String name) { final var varName = super.toVarName(name); + if (varName.startsWith("r#")) return "r_" + varName.substring(2); - else - return varName; + + // Special case: validate(nested) macros has internal field errors, thus, result in compilation error + if (varName == "errors") + return "errors_"; + + return varName; } static class PathMethodOperations { From d68847d41ecea09341dbdb768fc644428f12f9f2 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Sat, 13 Sep 2025 03:07:30 +0900 Subject: [PATCH 09/15] Update --- .../codegen/languages/RustAxumServerCodegen.java | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) 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 c408df9fc5cb..b309ebd0277e 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 @@ -99,6 +99,18 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege public RustAxumServerCodegen() { super(); + // The `#[validate(nested)]` macro relies on an internal field named `errors` to accumulate validation results. Therefore, defining a struct like this will fail: + // + // ```rust + // struct A { + // #[validate(nested)] + // errors: B, + // } + // ``` + // + // To avoid this, either rename the field to something other than "errors", or reserve it. + this.reservedWords.add("errors"); + modifyFeatureSet(features -> features .wireFormatFeatures(EnumSet.of( WireFormatFeature.JSON, @@ -1192,10 +1204,6 @@ public String toVarName(String name) { if (varName.startsWith("r#")) return "r_" + varName.substring(2); - // Special case: validate(nested) macros has internal field errors, thus, result in compilation error - if (varName == "errors") - return "errors_"; - return varName; } From fa938975c77d54e1da2b6337998a8649fc15b4d6 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Sat, 13 Sep 2025 03:21:23 +0900 Subject: [PATCH 10/15] Update --- docs/generators/rust-axum.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/generators/rust-axum.md b/docs/generators/rust-axum.md index 43841fa6bf28..19d87abc1240 100644 --- a/docs/generators/rust-axum.md +++ b/docs/generators/rust-axum.md @@ -77,6 +77,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
  • dyn
  • else
  • enum
  • +
  • errors
  • extern
  • false
  • final
  • From a9515ce52a3ac804a4e6f27f1f75d91e361619ee Mon Sep 17 00:00:00 2001 From: linxGnu Date: Sat, 13 Sep 2025 20:32:37 +0900 Subject: [PATCH 11/15] Update --- .../languages/RustAxumServerCodegen.java | 18 +++++++---- .../main/resources/rust-axum/models.mustache | 32 +++++++++++-------- .../output/multipart-v3/src/models.rs | 5 --- .../rust-axum/output/openapi-v3/src/models.rs | 6 ---- .../src/models.rs | 16 ++-------- .../output/rust-axum-oneof/src/models.rs | 8 ++--- .../output/rust-axum-test/src/models.rs | 1 - 7 files changed, 36 insertions(+), 50 deletions(-) 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 b309ebd0277e..6ad0272fc79b 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 @@ -744,8 +744,8 @@ private void postProcessPolymorphism(List allModels) { } private static void processPolymorphismDataType(final List cp) { - HashSet dedupDataTypeWithEnum = new HashSet(); - HashMap dedupDataType = new HashMap(); + final HashSet dedupDataTypeWithEnum = new HashSet<>(); + final HashMap dedupDataType = new HashMap<>(); int idx = 0; for (CodegenProperty model : cp) { @@ -943,7 +943,7 @@ public void addOperationToGroup(String tag, String resourcePath, Operation opera // restore things to sensible values. @Override public CodegenParameter fromRequestBody(RequestBody body, Set imports, String bodyParameterName) { - final Schema original_schema = ModelUtils.getSchemaFromRequestBody(body); + final var original_schema = ModelUtils.getSchemaFromRequestBody(body); CodegenParameter codegenParameter = super.fromRequestBody(body, imports, bodyParameterName); if (StringUtils.isNotBlank(original_schema.get$ref())) { @@ -962,10 +962,10 @@ public CodegenParameter fromRequestBody(RequestBody body, Set imports, S @Override public String toInstantiationType(final Schema p) { if (ModelUtils.isArraySchema(p)) { - final Schema inner = ModelUtils.getSchemaItems(p); + final var inner = ModelUtils.getSchemaItems(p); return instantiationTypes.get("array") + "<" + getSchemaType(inner) + ">"; } else if (ModelUtils.isMapSchema(p)) { - final Schema inner = ModelUtils.getAdditionalProperties(p); + final var inner = ModelUtils.getAdditionalProperties(p); return instantiationTypes.get("map") + "<" + typeMapping.get("string") + ", " + getSchemaType(inner) + ">"; } else { return null; @@ -994,6 +994,10 @@ public Map postProcessSupportingFileData(Map bun @Override public String toDefaultValue(final Schema p) { String defaultValue = null; + + if (ModelUtils.isEnumSchema(p)) + return null; + if ((ModelUtils.isNullable(p)) && (p.getDefault() != null) && ("null".equalsIgnoreCase(p.getDefault().toString()))) return "Nullable::Null"; @@ -1138,7 +1142,7 @@ public void postProcessFile(File file, String fileType) { } @Override - protected void updateParameterForString(CodegenParameter codegenParameter, Schema parameterSchema) { + protected void updateParameterForString(CodegenParameter codegenParameter, final Schema parameterSchema) { if (ModelUtils.isEmailSchema(parameterSchema)) { codegenParameter.isEmail = true; } else if (ModelUtils.isUUIDSchema(parameterSchema)) { @@ -1165,7 +1169,7 @@ protected void updateParameterForString(CodegenParameter codegenParameter, Schem codegenParameter.isDecimal = true; codegenParameter.isPrimitiveType = true; } - if (Boolean.TRUE.equals(codegenParameter.isString)) { + if (codegenParameter.isString) { codegenParameter.isPrimitiveType = true; } } 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 ef25abc6c45a..e41ebca57679 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -913,8 +913,10 @@ pub struct {{{classname}}} { /// Note: inline enums are not fully supported by openapi-generator {{/isEnum}} {{#isDiscriminator}} +{{#isString}} #[serde(default = "{{{classname}}}::_name_for_{{{name}}}")] #[serde(serialize_with = "{{{classname}}}::_serialize_{{{name}}}")] +{{/isString}} {{/isDiscriminator}} #[serde(rename = "{{{baseName}}}")] {{#hasValidation}} @@ -1017,7 +1019,7 @@ pub struct {{{classname}}} { {{/isNullable}} {{/hasValidation}} {{#required}} - pub {{{name}}}: {{^isDiscriminator}}{{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}{{/isDiscriminator}}{{#isDiscriminator}}{{{dataType}}}{{/isDiscriminator}}, + pub {{{name}}}: {{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, {{/required}} {{^required}} {{^isDiscriminator}} @@ -1025,9 +1027,17 @@ pub struct {{{classname}}} { #[serde(deserialize_with = "deserialize_optional_nullable")] #[serde(default = "default_optional_nullable")] {{/isNullable}} - #[serde(skip_serializing_if="Option::is_none")] {{/isDiscriminator}} - pub {{{name}}}: {{^isDiscriminator}}Option<{{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}>{{/isDiscriminator}}{{#isDiscriminator}}{{{dataType}}}{{/isDiscriminator}}, +{{#isDiscriminator}} +{{^isString}} +{{#isNullable}} + #[serde(deserialize_with = "deserialize_optional_nullable")] + #[serde(default = "default_optional_nullable")] +{{/isNullable}} +{{/isString}} +{{/isDiscriminator}} + #[serde(skip_serializing_if="Option::is_none")] + pub {{{name}}}: Option<{{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}>, {{/required}} {{/vars}} @@ -1035,6 +1045,7 @@ pub struct {{{classname}}} { {{#vars}} {{#isDiscriminator}} +{{#isString}} impl {{{classname}}} { fn _name_for_{{{name}}}() -> String { String::from("{{{classname}}}") @@ -1047,6 +1058,7 @@ impl {{{classname}}} { s.serialize_str(&Self::_name_for_{{{name}}}()) } } +{{/isString}} {{/isDiscriminator}} {{/vars}} @@ -1077,9 +1089,9 @@ fn validate_byte_{{#lambda.lowercase}}{{{classname}}}_{{{name}}}{{/lambda.lowerc impl {{{classname}}} { #[allow(clippy::new_without_default, clippy::too_many_arguments)] - pub fn new({{#vars}}{{^defaultValue}}{{{name}}}: {{^isDiscriminator}}{{#isNullable}}Nullable<{{/isNullable}}{{/isDiscriminator}}{{{dataType}}}{{^isDiscriminator}}{{#isNullable}}>{{/isNullable}}{{/isDiscriminator}}, {{/defaultValue}}{{/vars}}) -> {{{classname}}} { + pub fn new({{#vars}}{{^isDiscriminator}}{{^defaultValue}}{{{name}}}: {{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, {{/defaultValue}}{{/isDiscriminator}}{{#isDiscriminator}}{{^isString}}{{^defaultValue}}{{{name}}}: {{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, {{/defaultValue}}{{/isString}}{{/isDiscriminator}}{{/vars}}) -> {{{classname}}} { {{{classname}}} { -{{#vars}} {{^isDiscriminator}}{{#defaultValue}}{{{name}}}: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{{name}}}{{/defaultValue}}{{/isDiscriminator}}{{#isDiscriminator}}{{{name}}}: Self::_name_for_{{{name}}}(){{/isDiscriminator}}, +{{#vars}} {{^isDiscriminator}}{{#defaultValue}}{{{name}}}: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{{name}}}{{/defaultValue}}{{/isDiscriminator}}{{#isDiscriminator}}{{#isString}}{{{name}}}: Self::_name_for_{{{name}}}(){{/isString}}{{^isString}}{{#defaultValue}}{{{name}}}: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{{name}}}{{/defaultValue}}{{/isString}}{{/isDiscriminator}}, {{/vars}} } } @@ -1092,7 +1104,6 @@ impl std::fmt::Display for {{{classname}}} { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ {{#vars}} -{{^isDiscriminator}} {{#isByteArray}} // Skipping {{baseName}} in query parameter serialization {{/isByteArray}} @@ -1145,9 +1156,7 @@ impl std::fmt::Display for {{{classname}}} { {{/isArray}} ].join(",") }), -{{/required}} -{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}}{{/isDiscriminator}} -{{#isDiscriminator}} Some("{{{baseName}}}".to_string()), Some(self.{{{name}}}.clone()),{{/isDiscriminator}} +{{/required}}{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}} {{/vars}} ]; @@ -1221,17 +1230,12 @@ impl std::str::FromStr for {{{classname}}} { // Use the intermediate representation to return the struct std::result::Result::Ok({{{classname}}} { {{#vars}} - {{^isDiscriminator}} {{#isNullable}} {{{name}}}: std::result::Result::Err("Nullable types not supported in {{{classname}}}".to_string())?, {{/isNullable}} {{^isNullable}} {{{name}}}: intermediate_rep.{{{name}}}.into_iter().next(){{#required}}.ok_or_else(|| "{{{baseName}}} missing in {{{classname}}}".to_string())?{{/required}}, {{/isNullable}} - {{/isDiscriminator}} - {{#isDiscriminator}} - {{{name}}}: intermediate_rep.{{{name}}}.into_iter().next().ok_or_else(|| "{{{baseName}}} missing in {{{classname}}}".to_string())?, - {{/isDiscriminator}} {{/vars}} }) } diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs b/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs index 8b6885bab3ed..2063d4ce45c9 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs @@ -97,15 +97,12 @@ impl std::fmt::Display for MultipartRelatedRequest { // Skipping object_field in query parameter serialization - // Skipping optional_binary_field in query parameter serialization // Skipping optional_binary_field in query parameter serialization - // Skipping required_binary_field in query parameter serialization // Skipping required_binary_field in query parameter serialization - ]; write!( @@ -591,11 +588,9 @@ impl std::fmt::Display for MultipleIdenticalMimeTypesPostRequest { // Skipping binary1 in query parameter serialization // Skipping binary1 in query parameter serialization - // Skipping binary2 in query parameter serialization // Skipping binary2 in query parameter serialization - ]; write!( diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs index 1f2d19c9a80b..a46a0a944fe2 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs @@ -908,11 +908,9 @@ impl std::fmt::Display for AnyOfProperty { // Skipping requiredAnyOf in query parameter serialization - // Skipping optionalAnyOf in query parameter serialization - ]; write!( @@ -2525,19 +2523,15 @@ impl std::fmt::Display for ObjectUntypedProps { // Skipping required_untyped in query parameter serialization - // Skipping required_untyped_nullable in query parameter serialization - // Skipping not_required_untyped in query parameter serialization - // Skipping not_required_untyped_nullable in query parameter serialization - ]; write!( 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 f876073374a5..98e1b934136e 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 @@ -238,11 +238,9 @@ impl std::fmt::Display for AdditionalPropertiesClass { let params: Vec> = vec![ // Skipping map_property in query parameter serialization - // Skipping map_of_map_property in query parameter serialization // Skipping map_of_map_property in query parameter serialization - ]; write!( @@ -378,7 +376,7 @@ impl Animal { impl Animal { #[allow(clippy::new_without_default, clippy::too_many_arguments)] - pub fn new(class_name: String) -> Animal { + pub fn new() -> Animal { Animal { class_name: Self::_name_for_class_name(), color: Some(r#"red"#.to_string()), @@ -393,7 +391,7 @@ impl std::fmt::Display for Animal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("className".to_string()), - Some(self.class_name.clone()), + Some(self.class_name.to_string()), self.color .as_ref() .map(|color| ["color".to_string(), color.to_string()].join(",")), @@ -846,7 +844,6 @@ impl std::fmt::Display for ArrayOfArrayOfNumberOnly { // Skipping ArrayArrayNumber in query parameter serialization - ]; write!( @@ -3471,14 +3468,11 @@ impl std::fmt::Display for MapTest { // Skipping map_map_of_string in query parameter serialization // Skipping map_map_of_string in query parameter serialization - // Skipping map_map_of_enum in query parameter serialization // Skipping map_map_of_enum in query parameter serialization - // Skipping map_of_enum_string in query parameter serialization - ]; write!( @@ -3644,15 +3638,12 @@ impl std::fmt::Display for MixedPropertiesAndAdditionalPropertiesClass { // Skipping uuid in query parameter serialization - // Skipping dateTime in query parameter serialization - // Skipping map in query parameter serialization // Skipping map in query parameter serialization - ]; write!( @@ -4253,7 +4244,6 @@ impl std::fmt::Display for ObjectContainingObjectWithOnlyAdditionalProperties { // Skipping inner in query parameter serialization - ]; write!( @@ -5932,7 +5922,7 @@ impl TestEnumParametersRequest { #[allow(clippy::new_without_default, clippy::too_many_arguments)] pub fn new() -> TestEnumParametersRequest { TestEnumParametersRequest { - enum_form_string: Some(r#"-efg"#.to_string()), + enum_form_string: None, } } } 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 165accb47190..66159932b3d0 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 @@ -91,7 +91,7 @@ impl Goodbye { impl Goodbye { #[allow(clippy::new_without_default, clippy::too_many_arguments)] - pub fn new(op: String, d: models::GoodbyeD) -> Goodbye { + pub fn new(d: models::GoodbyeD) -> Goodbye { Goodbye { op: Self::_name_for_op(), d, @@ -106,7 +106,7 @@ impl std::fmt::Display for Goodbye { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("op".to_string()), - Some(self.op.clone()), + Some(self.op.to_string()), // Skipping d in query parameter serialization ]; @@ -410,7 +410,7 @@ impl std::fmt::Display for Greeting { let params: Vec> = vec![ // Skipping d in query parameter serialization Some("op".to_string()), - Some(self.op.clone()), + Some(self.op.to_string()), ]; write!( @@ -714,7 +714,7 @@ impl std::fmt::Display for Hello { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("op".to_string()), - Some(self.op.clone()), + Some(self.op.to_string()), // Skipping d in query parameter serialization ]; 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 56d54a25c248..b1973b642748 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 @@ -880,7 +880,6 @@ impl std::fmt::Display for FooObjectOfObjects { // Skipping inner in query parameter serialization - ]; write!( From ce3ee3afc88728670fa9d12dbcca84bb714bf188 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Sat, 13 Sep 2025 21:08:37 +0900 Subject: [PATCH 12/15] Update --- .../src/main/resources/rust-axum/models.mustache | 11 +++++++---- .../rust-axum/output/multipart-v3/src/models.rs | 1 - .../rust-axum/output/openapi-v3/src/models.rs | 12 +++++------- .../src/models.rs | 4 ---- .../rust-axum/output/rust-axum-test/src/models.rs | 7 +++++-- 5 files changed, 17 insertions(+), 18 deletions(-) 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 e41ebca57679..f7faf5b0aeea 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -1116,19 +1116,21 @@ impl std::fmt::Display for {{{classname}}} { {{^isPrimitiveType}} // Skipping {{baseName}} in query parameter serialization {{/isPrimitiveType}} -{{^isByteArray}}{{^isBinary}}{{^isMap}} -{{#isPrimitiveType}} +{{^isByteArray}}{{^isBinary}}{{^isMap}}{{#isPrimitiveType}} {{#required}} Some("{{{baseName}}}".to_string()), {{^isArray}} {{#isNullable}} - // Skipping {{baseName}} in query parameter serialization + Some(self.{{{name}}}.as_ref().map_or("null".to_string(), |x| x.to_string())), {{/isNullable}} {{^isNullable}} Some(self.{{{name}}}.to_string()), {{/isNullable}} {{/isArray}} {{#isArray}} +{{#isNullable}} + Some(self.{{{name}}}.as_ref().map_or("null".to_string(), |x| x.iter().map(|x| x.to_string()).collect::>().join(","))), +{{/isNullable}} {{^isNullable}} Some(self.{{{name}}}.iter().map(|x| x.to_string()).collect::>().join(",")), {{/isNullable}} @@ -1156,7 +1158,8 @@ impl std::fmt::Display for {{{classname}}} { {{/isArray}} ].join(",") }), -{{/required}}{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}} +{{/required}} +{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}} {{/vars}} ]; diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs b/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs index 2063d4ce45c9..7b10d2066db3 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/multipart-v3/src/models.rs @@ -96,7 +96,6 @@ impl std::fmt::Display for MultipartRelatedRequest { let params: Vec> = vec![ // Skipping object_field in query parameter serialization - // Skipping optional_binary_field in query parameter serialization // Skipping optional_binary_field in query parameter serialization diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs index a46a0a944fe2..09d89a5d0fe7 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs @@ -907,10 +907,8 @@ impl std::fmt::Display for AnyOfProperty { let params: Vec> = vec![ // Skipping requiredAnyOf in query parameter serialization - // Skipping optionalAnyOf in query parameter serialization - ]; write!( @@ -1927,7 +1925,11 @@ impl std::fmt::Display for NullableTest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let params: Vec> = vec![ Some("nullable".to_string()), - // Skipping nullable in query parameter serialization + Some( + self.nullable + .as_ref() + .map_or("null".to_string(), |x| x.to_string()), + ), self.nullable_with_null_default .as_ref() .map(|nullable_with_null_default| { @@ -2522,16 +2524,12 @@ impl std::fmt::Display for ObjectUntypedProps { let params: Vec> = vec![ // Skipping required_untyped in query parameter serialization - // Skipping required_untyped_nullable in query parameter serialization - // Skipping not_required_untyped in query parameter serialization - // Skipping not_required_untyped_nullable in query parameter serialization - ]; write!( 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 98e1b934136e..61d756716131 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 @@ -843,7 +843,6 @@ impl std::fmt::Display for ArrayOfArrayOfNumberOnly { let params: Vec> = vec![ // Skipping ArrayArrayNumber in query parameter serialization - ]; write!( @@ -3637,10 +3636,8 @@ impl std::fmt::Display for MixedPropertiesAndAdditionalPropertiesClass { let params: Vec> = vec![ // Skipping uuid in query parameter serialization - // Skipping dateTime in query parameter serialization - // Skipping map in query parameter serialization // Skipping map in query parameter serialization @@ -4243,7 +4240,6 @@ impl std::fmt::Display for ObjectContainingObjectWithOnlyAdditionalProperties { let params: Vec> = vec![ // Skipping inner in query parameter serialization - ]; write!( 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 b1973b642748..1d09683c0009 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 @@ -100,7 +100,11 @@ impl std::fmt::Display for FooANullableContainer { .join(",") }), Some("RequiredNullableThing".to_string()), - // Skipping RequiredNullableThing in query parameter serialization + Some( + self.required_nullable_thing + .as_ref() + .map_or("null".to_string(), |x| x.to_string()), + ), ]; write!( @@ -879,7 +883,6 @@ impl std::fmt::Display for FooObjectOfObjects { let params: Vec> = vec![ // Skipping inner in query parameter serialization - ]; write!( From 5d96de6b4e949362df5f739086f1e2665a6f2cd1 Mon Sep 17 00:00:00 2001 From: linxGnu Date: Sat, 13 Sep 2025 21:19:48 +0900 Subject: [PATCH 13/15] Update --- .../src/main/resources/rust-axum/models.mustache | 10 ---------- 1 file changed, 10 deletions(-) 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 f7faf5b0aeea..ba76131c4409 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -1022,20 +1022,10 @@ pub struct {{{classname}}} { pub {{{name}}}: {{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, {{/required}} {{^required}} -{{^isDiscriminator}} {{#isNullable}} #[serde(deserialize_with = "deserialize_optional_nullable")] #[serde(default = "default_optional_nullable")] {{/isNullable}} -{{/isDiscriminator}} -{{#isDiscriminator}} -{{^isString}} -{{#isNullable}} - #[serde(deserialize_with = "deserialize_optional_nullable")] - #[serde(default = "default_optional_nullable")] -{{/isNullable}} -{{/isString}} -{{/isDiscriminator}} #[serde(skip_serializing_if="Option::is_none")] pub {{{name}}}: Option<{{#isNullable}}Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}>, {{/required}} From edc0e8381e7680687b0469fc6914f8cd8e041bff Mon Sep 17 00:00:00 2001 From: linxGnu Date: Mon, 15 Sep 2025 11:42:08 +0900 Subject: [PATCH 14/15] Update --- .../src/main/resources/rust-axum/models.mustache | 6 +++--- .../rust-axum/output/openapi-v3/src/models.rs | 14 +++++++------- .../src/models.rs | 4 ++-- .../rust-axum/output/rust-axum-oneof/src/models.rs | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) 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 ba76131c4409..6fedb1420423 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/models.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/models.mustache @@ -524,7 +524,7 @@ pub fn check_xss_map(v: &std::collections::HashMap) -> std::result /// Enumeration of values. /// Since this enum's variants do not hold data, we can easily define them as `#[repr(C)]` /// which helps with FFI. -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)] #[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))] @@ -759,7 +759,7 @@ impl std::str::FromStr for {{{classname}}} { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] {{/discriminator}} -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] pub enum {{{classname}}} { {{#composedSchemas}} {{#anyOf}} @@ -834,7 +834,7 @@ impl From<{{{dataType}}}> for {{{classname}}} { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] {{/discriminator}} -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] pub enum {{{classname}}} { {{#composedSchemas}} {{#oneOf}} diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs index 09d89a5d0fe7..a02749ce055d 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/models.rs @@ -769,7 +769,7 @@ impl std::convert::TryFrom for header::IntoHeaderValue for AnyOfGet202Response { /// Test a model containing an anyOf of a hash map #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] pub enum AnyOfHashMapObject { String(String), HashMapOfStringString(std::collections::HashMap), @@ -849,7 +849,7 @@ impl From> for AnyOfHashMapObject { /// Test a model containing an anyOf #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] pub enum AnyOfObject { String(String), String1(String), @@ -1186,7 +1186,7 @@ impl std::convert::TryFrom for header::IntoHeaderValue for header::IntoHeaderValue), @@ -2992,7 +2992,7 @@ impl std::ops::DerefMut for Result { /// Enumeration of values. /// Since this enum's variants do not hold data, we can easily define them as `#[repr(C)]` /// which helps with FFI. -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] #[repr(C)] #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, 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 61d756716131..bb81873332e9 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 @@ -2554,7 +2554,7 @@ impl std::convert::TryFrom for header::IntoHeaderValue /// Enumeration of values. /// Since this enum's variants do not hold data, we can easily define them as `#[repr(C)]` /// which helps with FFI. -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] #[repr(C)] #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, @@ -4805,7 +4805,7 @@ impl std::convert::TryFrom for header::IntoHeaderValue for header::IntoHeaderValue { #[derive(Debug, Clone, PartialEq, serde::Deserialize)] #[serde(tag = "op")] -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] pub enum Message { Hello(models::Hello), Greeting(models::Greeting), @@ -1041,7 +1041,7 @@ impl From for Message { #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(untagged)] -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::large_enum_variant)] pub enum SomethingCompletelyDifferent { VecOfObject(Vec), Object(crate::types::Object), From da1488605e17e9d3e630257cb2393e47dc751ccc Mon Sep 17 00:00:00 2001 From: linxGnu Date: Tue, 16 Sep 2025 11:25:22 +0900 Subject: [PATCH 15/15] Update --- .../languages/RustAxumServerCodegen.java | 86 +++++++++++++------ 1 file changed, 58 insertions(+), 28 deletions(-) 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 6ad0272fc79b..1366797328a5 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 @@ -647,10 +647,10 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation return op; } - private void postProcessPolymorphism(List allModels) { - final HashMap> mapDiscriminator = new HashMap<>(); + private void postProcessPolymorphism(final List allModels) { + final HashMap> discriminatorsForModel = new HashMap<>(); - for (ModelMap mo : allModels) { + for (final ModelMap mo : allModels) { final CodegenModel cm = mo.getModel(); final CodegenComposedSchemas cs = cm.getComposedSchemas(); @@ -671,43 +671,29 @@ private void postProcessPolymorphism(List allModels) { } if (cm.discriminator != null) { - for (String model : cm.oneOf) { - final List discriminators = mapDiscriminator.getOrDefault(model, new ArrayList<>()); + for (final String model : cm.oneOf) { + final List discriminators = discriminatorsForModel.getOrDefault(model, new ArrayList<>()); discriminators.add(cm.discriminator.getPropertyName()); - mapDiscriminator.put(model, discriminators); + discriminatorsForModel.put(model, discriminators); } - for (String model : cm.anyOf) { - final List discriminators = mapDiscriminator.getOrDefault(model, new ArrayList<>()); + for (final String model : cm.anyOf) { + final List discriminators = discriminatorsForModel.getOrDefault(model, new ArrayList<>()); discriminators.add(cm.discriminator.getPropertyName()); - mapDiscriminator.put(model, discriminators); + discriminatorsForModel.put(model, discriminators); } } } + final var blocking = new HashSet(); for (ModelMap mo : allModels) { final CodegenModel cm = mo.getModel(); - final List discriminatorsForModel = mapDiscriminator.get(cm.getSchemaName()); - if (discriminatorsForModel != null) { - for (CodegenProperty var : cm.vars) { - var.isDiscriminator = false; - } - - boolean hasDiscriminatorDefined = false; - for (String discriminator : discriminatorsForModel) { - for (CodegenProperty var : cm.vars) { - if (var.baseName.equals(discriminator) || var.name.equals(discriminator)) { - var.isDiscriminator = true; - hasDiscriminatorDefined = true; - break; - } - } - } - + 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 (!hasDiscriminatorDefined && !discriminatorsForModel.isEmpty()) { - final String discriminator = discriminatorsForModel.get(0); + if (!discriminating(discriminators, cm)) { + final String discriminator = discriminators.get(0); CodegenProperty property = new CodegenProperty(); @@ -740,6 +726,50 @@ private void postProcessPolymorphism(List allModels) { cm.vars.add(property); } } + + if (cm.vars.stream().noneMatch(v -> v.isDiscriminator)) { + blocking.add(cm.getSchemaName()); + } + } + + for (final ModelMap mo : allModels) { + final CodegenModel cm = mo.getModel(); + if (cm.discriminator != null) { + // if no discriminator in any of variant -> disable discriminator + if (cm.oneOf.stream().anyMatch(blocking::contains) || cm.anyOf.stream().anyMatch(blocking::contains)) { + cm.discriminator = null; + } + } + } + } + + 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 (var.isString) { + var.isDiscriminator = true; + ++countString; + } else + ++countNonString; + } + } + + if (countString > 0 && (countNonString > 0 || countString > 1)) { + // at least two discriminator, one of them is string -> should not render serde tag + resetDiscriminatorProperty(cm); + } + + return countNonString > 0 || countString > 0; + } + + private static void resetDiscriminatorProperty(final CodegenModel cm) { + for (final CodegenProperty var : cm.vars) { + var.isDiscriminator = false; } }