diff --git a/bin/configs/manual/rust-axum-integer-types.yaml b/bin/configs/manual/rust-axum-integer-types.yaml new file mode 100644 index 000000000000..a02399901fdf --- /dev/null +++ b/bin/configs/manual/rust-axum-integer-types.yaml @@ -0,0 +1,12 @@ +generatorName: rust-axum +outputDir: samples/server/petstore/rust-axum/output/rust-axum-integer-types-test +inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-axum/integer-types.yaml +templateDir: modules/openapi-generator/src/main/resources/rust-axum +generateAliasAsModel: true +additionalProperties: + hideGenerationTimestamp: "true" + packageName: rust-axum-integer-types-test + homePageUrl: https://github.com/openapitools/openapi-generator +globalProperties: + skipFormModel: false +enablePostProcessFile: true diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java index a6450d6f3f52..669c3302cee3 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 @@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory; import java.io.File; +import java.math.BigDecimal; import java.math.BigInteger; import java.nio.file.Path; import java.util.*; @@ -1024,6 +1025,72 @@ public CodegenParameter fromRequestBody(RequestBody body, Set imports, S return codegenParameter; } + private String getIntegerDataType(String format, + BigInteger minimum, + boolean exclusiveMinimum, + final BigInteger maximum, + final boolean exclusiveMaximum) { + final boolean unsigned = canFitIntoUnsigned(minimum, exclusiveMinimum); + + if (StringUtils.isEmpty(format)) { + return bestFittingIntegerType( + minimum, + exclusiveMinimum, + maximum, + exclusiveMaximum, + unsigned); + } + + switch (format) { + // custom integer formats (legacy) + case "uint32": + return "u32"; + case "uint64": + return "u64"; + case "int32": + return unsigned ? "u32" : "i32"; + case "int64": + return unsigned ? "u64" : "i64"; + default: + LOGGER.warn("The integer format '{}' is not recognized and will be ignored.", format); + return bestFittingIntegerType( + minimum, + exclusiveMinimum, + maximum, + exclusiveMaximum, + unsigned); + } + } + + @Override + public String getSchemaType(Schema p) { + if (Objects.equals(p.getType(), "integer")) { + final boolean hasNoFormat = StringUtils.isEmpty(p.getFormat()); + final boolean hasNoBounds = p.getMinimum() == null + && p.getMaximum() == null + && p.getExclusiveMinimum() == null + && p.getExclusiveMaximum() == null; + + // Preserve legacy schema typing for unconstrained integers so alias models + // keep their expected model resolution flow. + if (hasNoFormat && hasNoBounds) { + return super.getSchemaType(p); + } + + final BigInteger minimum = Optional.ofNullable(p.getMinimum()).map(BigDecimal::toBigInteger).orElse(null); + final BigInteger maximum = Optional.ofNullable(p.getMaximum()).map(BigDecimal::toBigInteger).orElse(null); + + return getIntegerDataType( + p.getFormat(), + minimum, + Optional.ofNullable(p.getExclusiveMinimum()).orElse(false), + maximum, + Optional.ofNullable(p.getExclusiveMaximum()).orElse(false)); + } + + return super.getSchemaType(p); + } + @Override public String toInstantiationType(final Schema p) { if (ModelUtils.isArraySchema(p)) { @@ -1037,6 +1104,45 @@ public String toInstantiationType(final Schema p) { } } + @Override + public CodegenProperty fromProperty(String name, Schema p, boolean required) { + CodegenProperty property = super.fromProperty(name, p, required); + ensureArrayComplexType(property); + return property; + } + + @Override + public CodegenProperty fromProperty(String name, Schema p, boolean required, boolean schemaIsFromAdditionalProperties) { + CodegenProperty property = super.fromProperty(name, p, required, schemaIsFromAdditionalProperties); + ensureArrayComplexType(property); + return property; + } + + private void ensureArrayComplexType(CodegenProperty property) { + if (property == null || !property.isArray || StringUtils.isNotBlank(property.complexType) || property.items == null) { + return; + } + + String candidate = StringUtils.defaultIfBlank(property.items.complexType, property.items.baseType); + if (StringUtils.isBlank(candidate)) { + candidate = property.items.dataType; + } + if (StringUtils.isBlank(candidate)) { + return; + } + + property.complexType = reverseTypeMapping(candidate); + } + + private String reverseTypeMapping(String rustType) { + for (Map.Entry entry : typeMapping.entrySet()) { + if (Objects.equals(entry.getValue(), rustType)) { + return entry.getKey(); + } + } + return rustType; + } + @Override public Map postProcessSupportingFileData(Map bundle) { generateYAMLSpecFile(bundle); @@ -1113,13 +1219,15 @@ public void postProcessModelProperty(CodegenModel model, CodegenProperty propert } // Integer type fitting - if (Objects.equals(property.baseType, "integer")) { - BigInteger minimum = Optional.ofNullable(property.getMinimum()).map(BigInteger::new).orElse(null); - BigInteger maximum = Optional.ofNullable(property.getMaximum()).map(BigInteger::new).orElse(null); - property.dataType = bestFittingIntegerType( - minimum, property.getExclusiveMinimum(), - maximum, property.getExclusiveMaximum(), - true); + if (Boolean.TRUE.equals(property.isInteger) || Boolean.TRUE.equals(property.isLong) || Objects.equals(property.baseType, "UnsignedInteger") || Objects.equals(property.baseType, "UnsignedLong")) { + final BigInteger minimum = Optional.ofNullable(property.getMinimum()).map(BigInteger::new).orElse(null); + final BigInteger maximum = Optional.ofNullable(property.getMaximum()).map(BigInteger::new).orElse(null); + property.dataType = getIntegerDataType( + property.dataFormat, + minimum, + property.getExclusiveMinimum(), + maximum, + property.getExclusiveMaximum()); } property.name = underscore(property.name); diff --git a/modules/openapi-generator/src/main/resources/rust-axum/README.mustache b/modules/openapi-generator/src/main/resources/rust-axum/README.mustache index 592c97af2db5..babe066ad057 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/README.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/README.mustache @@ -11,7 +11,7 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: {{{appVersion}}} {{^hideGenerationTimestamp}} diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustAxumServerCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustAxumServerCodegenTest.java index 7c425ceccf9f..7db98cfc54c4 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustAxumServerCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/rust/RustAxumServerCodegenTest.java @@ -1,12 +1,16 @@ package org.openapitools.codegen.rust; +import io.swagger.v3.oas.models.media.IntegerSchema; import org.openapitools.codegen.DefaultGenerator; import org.openapitools.codegen.TestUtils; import org.openapitools.codegen.config.CodegenConfigurator; +import org.openapitools.codegen.languages.RustAxumServerCodegen; +import org.testng.Assert; import org.testng.annotations.Test; import java.io.File; import java.io.IOException; +import java.math.BigDecimal; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; @@ -49,4 +53,54 @@ public void testPreventDuplicateOperationDeclaration() throws IOException { TestUtils.assertFileExists(outputPath); TestUtils.assertFileContains(outputPath, routerSpec); } -} \ No newline at end of file + + @Test + public void testIntegerSchemaTypeMapping() { + RustAxumServerCodegen codegen = new RustAxumServerCodegen(); + IntegerSchema schema = new IntegerSchema(); + + schema.setFormat("uint32"); + Assert.assertEquals(codegen.getSchemaType(schema), "u32"); + + schema = new IntegerSchema(); + schema.setFormat("uint64"); + Assert.assertEquals(codegen.getSchemaType(schema), "u64"); + + schema = new IntegerSchema(); + schema.setFormat("int32"); + schema.setMinimum(BigDecimal.ZERO); + Assert.assertEquals(codegen.getSchemaType(schema), "u32"); + + schema = new IntegerSchema(); + schema.setFormat("int64"); + schema.setMinimum(BigDecimal.ZERO); + Assert.assertEquals(codegen.getSchemaType(schema), "u64"); + + schema = new IntegerSchema(); + schema.setFormat(null); + schema.setMinimum(BigDecimal.ZERO); + schema.setMaximum(BigDecimal.valueOf(255)); + Assert.assertEquals(codegen.getSchemaType(schema), "u8"); + } + + @Test + public void testGeneratedIntegerTypes() throws IOException { + Path target = Files.createTempDirectory("test"); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName("rust-axum") + .setInputSpec("src/test/resources/3_0/rust-axum/integer-types.yaml") + .setSkipOverwrite(false) + .setOutputDir(target.toAbsolutePath().toString().replace("\\", "/")); + List files = new DefaultGenerator().opts(configurator.toClientOptInput()).generate(); + files.forEach(File::deleteOnExit); + + Path modelsPath = Path.of(target.toString(), "/src/models.rs"); + TestUtils.assertFileExists(modelsPath); + TestUtils.assertFileContains(modelsPath, "pub legacy_uint32: u32"); + TestUtils.assertFileContains(modelsPath, "pub legacy_uint64: u64"); + TestUtils.assertFileContains(modelsPath, "pub positive_int32: u32"); + TestUtils.assertFileContains(modelsPath, "pub positive_int64: u64"); + TestUtils.assertFileContains(modelsPath, "pub small_positive: u8"); + TestUtils.assertFileContains(modelsPath, "pub struct GetIntegersQueryParams"); + } +} diff --git a/modules/openapi-generator/src/test/resources/3_0/rust-axum/integer-types.yaml b/modules/openapi-generator/src/test/resources/3_0/rust-axum/integer-types.yaml new file mode 100644 index 000000000000..2696f7681f40 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/rust-axum/integer-types.yaml @@ -0,0 +1,78 @@ +openapi: 3.0.3 +info: + title: Rust Axum Integer Type Mapping Test + version: 1.0.0 +paths: + /integers: + get: + operationId: getIntegers + parameters: + - name: legacy_uint32 + in: query + required: true + schema: + type: integer + format: uint32 + - name: legacy_uint64 + in: query + required: true + schema: + type: integer + format: uint64 + - name: positive_int32 + in: query + required: true + schema: + type: integer + format: int32 + minimum: 0 + - name: positive_int64 + in: query + required: true + schema: + type: integer + format: int64 + minimum: 0 + - name: small_positive + in: query + required: true + schema: + type: integer + minimum: 0 + maximum: 255 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/IntegerTypes' +components: + schemas: + IntegerTypes: + type: object + required: + - legacy_uint32 + - legacy_uint64 + - positive_int32 + - positive_int64 + - small_positive + properties: + legacy_uint32: + type: integer + format: uint32 + legacy_uint64: + type: integer + format: uint64 + positive_int32: + type: integer + format: int32 + minimum: 0 + positive_int64: + type: integer + format: int64 + minimum: 0 + small_positive: + type: integer + minimum: 0 + maximum: 255 diff --git a/samples/server/petstore/rust-axum/output/apikey-authorization/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/apikey-authorization/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/apikey-authorization/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/apikey-authorization/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/apikey-authorization/README.md b/samples/server/petstore/rust-axum/output/apikey-authorization/README.md index 98310613d995..1a6d35afd775 100644 --- a/samples/server/petstore/rust-axum/output/apikey-authorization/README.md +++ b/samples/server/petstore/rust-axum/output/apikey-authorization/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0.0 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/README.md b/samples/server/petstore/rust-axum/output/apikey-auths/README.md index 185bcdd9daff..bdf2acc5746c 100644 --- a/samples/server/petstore/rust-axum/output/apikey-auths/README.md +++ b/samples/server/petstore/rust-axum/output/apikey-auths/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0.0 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/multipart-v3/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/multipart-v3/README.md b/samples/server/petstore/rust-axum/output/multipart-v3/README.md index 75e40a064107..07401f2271ea 100644 --- a/samples/server/petstore/rust-axum/output/multipart-v3/README.md +++ b/samples/server/petstore/rust-axum/output/multipart-v3/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0.7 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/openapi-v3/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/README.md b/samples/server/petstore/rust-axum/output/openapi-v3/README.md index c63f1afbfd63..0fdb662166fe 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/README.md +++ b/samples/server/petstore/rust-axum/output/openapi-v3/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0.7 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/apis/default.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/apis/default.rs index e8db7581d5f1..5c75aa8fdc64 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/apis/default.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/apis/default.rs @@ -152,6 +152,14 @@ pub enum ParamgetGetResponse { Status200_JSONRsp(models::AnotherXmlObject), } +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[must_use] +#[allow(clippy::large_enum_variant)] +pub enum QueryExampleGetResponse { + /// OK + Status200_OK, +} + #[derive(Debug, PartialEq, Serialize, Deserialize)] #[must_use] #[allow(clippy::large_enum_variant)] @@ -447,6 +455,18 @@ pub trait Default: super::Error query_params: &models::ParamgetGetQueryParams, ) -> Result; + /// Test required query params with and without examples. + /// + /// QueryExampleGet - GET /query-example + async fn query_example_get( + &self, + + method: &Method, + host: &Host, + cookies: &CookieJar, + query_params: &models::QueryExampleGetQueryParams, + ) -> Result; + /// ReadonlyAuthSchemeGet - GET /readonly_auth_scheme async fn readonly_auth_scheme_get( &self, 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 4c676a174121..22ccac1d4315 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 @@ -154,6 +154,15 @@ pub struct ParamgetGetQueryParams { pub some_list: Vec, } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct QueryExampleGetQueryParams { + #[serde(rename = "required_no_example")] + pub required_no_example: String, + #[serde(rename = "required_with_example")] + pub required_with_example: i32, +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct RegisterCallbackPostQueryParams { diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/server/mod.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/server/mod.rs index 285553bd2951..97f969c303b8 100644 --- a/samples/server/petstore/rust-axum/output/openapi-v3/src/server/mod.rs +++ b/samples/server/petstore/rust-axum/output/openapi-v3/src/server/mod.rs @@ -85,6 +85,9 @@ where .route("/paramget", get(paramget_get::) ) + .route("/query-example", + get(query_example_get::) + ) .route("/readonly_auth_scheme", get(readonly_auth_scheme_get::) ) @@ -1336,6 +1339,67 @@ where }) } +#[tracing::instrument(skip_all)] +fn query_example_get_validation( + query_params: models::QueryExampleGetQueryParams, +) -> std::result::Result<(models::QueryExampleGetQueryParams,), ValidationErrors> { + query_params.validate()?; + + Ok((query_params,)) +} +/// QueryExampleGet - GET /query-example +#[tracing::instrument(skip_all)] +async fn query_example_get( + method: Method, + TypedHeader(host): TypedHeader, + cookies: CookieJar, + QueryExtra(query_params): QueryExtra, + State(api_impl): State, +) -> Result +where + I: AsRef + Send + Sync, + A: apis::default::Default + Send + Sync, + E: std::fmt::Debug + Send + Sync + 'static, +{ + let validation = query_example_get_validation(query_params); + + let Ok((query_params,)) = validation else { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(validation.unwrap_err().to_string())) + .map_err(|_| StatusCode::BAD_REQUEST); + }; + + let result = api_impl + .as_ref() + .query_example_get(&method, &host, &cookies, &query_params) + .await; + + let mut response = Response::builder(); + + let resp = match result { + Ok(rsp) => match rsp { + apis::default::QueryExampleGetResponse::Status200_OK => { + let mut response = response.status(200); + response.body(Body::empty()) + } + }, + Err(why) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + return api_impl + .as_ref() + .handle_error(&method, &host, &cookies, why) + .await; + } + }; + + resp.map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + #[tracing::instrument(skip_all)] fn readonly_auth_scheme_get_validation() -> std::result::Result<(), ValidationErrors> { Ok(()) diff --git a/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/ops-v3/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/ops-v3/README.md b/samples/server/petstore/rust-axum/output/ops-v3/README.md index 82dd488c0590..503ed7695cb2 100644 --- a/samples/server/petstore/rust-axum/output/ops-v3/README.md +++ b/samples/server/petstore/rust-axum/output/ops-v3/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 0.0.1 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md index 1eec8170868c..89fc8d899ce3 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0.0 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT 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 da9640a95eb2..e47738e7fa72 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 @@ -176,8 +176,8 @@ pub struct DeleteOrderPathParams { #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct GetOrderByIdPathParams { /// ID of pet that needs to be fetched - #[validate(range(min = 1i64, max = 5i64))] - pub order_id: i64, + #[validate(range(min = 1u64, max = 5u64))] + pub order_id: u64, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] @@ -2812,9 +2812,9 @@ pub struct FormatTest { pub integer: Option, #[serde(rename = "int32")] - #[validate(range(min = 20u8, max = 200u8))] + #[validate(range(min = 20u32, max = 200u32))] #[serde(skip_serializing_if = "Option::is_none")] - pub int32: Option, + pub int32: Option, #[serde(rename = "int64")] #[serde(skip_serializing_if = "Option::is_none")] @@ -2968,7 +2968,7 @@ impl std::str::FromStr for FormatTest { #[allow(dead_code)] struct IntermediateRep { pub integer: Vec, - pub int32: Vec, + pub int32: Vec, pub int64: Vec, pub number: Vec, pub float: Vec, @@ -3006,9 +3006,9 @@ impl std::str::FromStr for FormatTest { .integer .push(::from_str(val).map_err(|x| x.to_string())?), #[allow(clippy::redundant_clone)] - "int32" => intermediate_rep - .int32 - .push(::from_str(val).map_err(|x| x.to_string())?), + "int32" => intermediate_rep.int32.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), #[allow(clippy::redundant_clone)] "int64" => intermediate_rep.int64.push( ::from_str(val).map_err(|x| x.to_string())?, @@ -5584,15 +5584,15 @@ impl std::convert::TryFrom for header::IntoHeaderValue { pub struct TestEndpointParametersRequest { /// None #[serde(rename = "integer")] - #[validate(range(min = 10u8, max = 100u8))] + #[validate(range(min = 10u32, max = 100u32))] #[serde(skip_serializing_if = "Option::is_none")] - pub integer: Option, + pub integer: Option, /// None #[serde(rename = "int32")] - #[validate(range(min = 20u8, max = 200u8))] + #[validate(range(min = 20u32, max = 200u32))] #[serde(skip_serializing_if = "Option::is_none")] - pub int32: Option, + pub int32: Option, /// None #[serde(rename = "int64")] @@ -5761,8 +5761,8 @@ impl std::str::FromStr for TestEndpointParametersRequest { #[derive(Default)] #[allow(dead_code)] struct IntermediateRep { - pub integer: Vec, - pub int32: Vec, + pub integer: Vec, + pub int32: Vec, pub int64: Vec, pub number: Vec, pub float: Vec, @@ -5797,9 +5797,9 @@ impl std::str::FromStr for TestEndpointParametersRequest { #[allow(clippy::match_single_binding)] match key { #[allow(clippy::redundant_clone)] - "integer" => intermediate_rep.integer.push(::from_str(val).map_err(|x| x.to_string())?), + "integer" => intermediate_rep.integer.push(::from_str(val).map_err(|x| x.to_string())?), #[allow(clippy::redundant_clone)] - "int32" => intermediate_rep.int32.push(::from_str(val).map_err(|x| x.to_string())?), + "int32" => intermediate_rep.int32.push(::from_str(val).map_err(|x| x.to_string())?), #[allow(clippy::redundant_clone)] "int64" => intermediate_rep.int64.push(::from_str(val).map_err(|x| x.to_string())?), #[allow(clippy::redundant_clone)] diff --git a/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/petstore/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/petstore/README.md b/samples/server/petstore/rust-axum/output/petstore/README.md index 0bca7b688b55..3a467c9133d2 100644 --- a/samples/server/petstore/rust-axum/output/petstore/README.md +++ b/samples/server/petstore/rust-axum/output/petstore/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0.0 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT 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 c8350e31ff8a..70dab700257e 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/models.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/models.rs @@ -130,8 +130,8 @@ pub struct DeleteOrderPathParams { #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] pub struct GetOrderByIdPathParams { /// ID of pet that needs to be fetched - #[validate(range(min = 1i64, max = 5i64))] - pub order_id: i64, + #[validate(range(min = 1u64, max = 5u64))] + pub order_id: u64, } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] diff --git a/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/ping-bearer-auth/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md b/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md index 7aef5e8e9028..a54d47aed9d3 100644 --- a/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md +++ b/samples/server/petstore/rust-axum/output/ping-bearer-auth/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 1.0 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/README.md b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/README.md index ab5d9a4d2441..d1fce8f28c36 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/README.md +++ b/samples/server/petstore/rust-axum/output/rust-axum-array-params-test/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 0.0.1 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md index cf76c3e4bb87..bb654fdf9d14 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md +++ b/samples/server/petstore/rust-axum/output/rust-axum-header-uuid/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 0.1.9 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.gitignore b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.gitignore new file mode 100644 index 000000000000..a9d37c560c6a --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator-ignore b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator-ignore new file mode 100644 index 000000000000..7484ee590a38 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator/FILES b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator/FILES new file mode 100644 index 000000000000..683914c4c643 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator/FILES @@ -0,0 +1,10 @@ +.gitignore +Cargo.toml +README.md +src/apis/default.rs +src/apis/mod.rs +src/header.rs +src/lib.rs +src/models.rs +src/server/mod.rs +src/types.rs diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator/VERSION new file mode 100644 index 000000000000..f7962df3e243 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/Cargo.toml b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/Cargo.toml new file mode 100644 index 000000000000..79bb0fa04363 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "rust-axum-integer-types-test" +version = "1.0.0" +authors = ["OpenAPI Generator team and contributors"] +description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" +edition = "2024" +homepage = "https://github.com/openapitools/openapi-generator" + +[features] +default = ["server"] +server = [] +conversion = [ + "frunk", + "frunk_derives", + "frunk_core", + "frunk-enum-core", + "frunk-enum-derive", +] + +[dependencies] +ammonia = "4" +async-trait = "0.1" +axum = { version = "0.8", features = ["multipart"] } +axum-extra = { version = "0.12", features = [ + "cookie", + "query", + "typed-header", +] } +base64 = "0.22" +bytes = "1" +chrono = { version = "0.4", features = ["serde"] } +frunk = { version = "0.4", optional = true } +frunk-enum-core = { version = "0.3", optional = true } +frunk-enum-derive = { version = "0.3", optional = true } +frunk_core = { version = "0.4", optional = true } +frunk_derives = { version = "0.4", optional = true } +headers = "0.4" +http = "1" +lazy_static = "1" +regex = "1" +serde = { version = "1", features = ["derive"] } +serde_html_form = "0.2" +serde_json = { version = "1", features = ["raw_value"] } +tokio = { version = "1", default-features = false, features = [ + "signal", + "rt-multi-thread", +] } +tracing = { version = "0.1", features = ["attributes"] } +uuid = { version = "1", features = ["serde"] } +validator = { version = "0.20", features = ["derive"] } + +[dev-dependencies] +tracing-subscriber = "0.3" diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/README.md b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/README.md new file mode 100644 index 000000000000..38ecff70f0ea --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/README.md @@ -0,0 +1,93 @@ +# Rust API for rust-axum-integer-types-test + +No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + +## Overview + +This server was generated by the [openapi-generator] +(https://openapi-generator.tech) project. By using the +[OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote +server, you can easily generate a server stub. + +To see how to make this your own, look here: [README](https://openapi-generator.tech) + +- API version: 1.0.0 +- Generator version: 7.22.0-SNAPSHOT + + + +This autogenerated project defines an API crate `rust-axum-integer-types-test` which contains: +* An `Api` trait defining the API in Rust. +* Data types representing the underlying data model. +* Axum router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. + * Request validations (path, query, body params) are included. + +## Using the generated library + +The generated library has a few optional features that can be activated through Cargo. + +* `server` + * This defaults to enabled and creates the basic skeleton of a server implementation based on Axum. + * To create the server stack you'll need to provide an implementation of the API trait to provide the server function. +* `conversions` + * This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types. + +See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`. + +### Example + +```rust +struct ServerImpl { + // database: sea_orm::DbConn, +} + +#[allow(unused_variables)] +#[async_trait] +impl rust_axum_integer_types_test::apis::default::Api for ServerImpl { + // API implementation goes here +} + +impl rust_axum_integer_types_test::apis::ErrorHandler for ServerImpl {} + +pub async fn start_server(addr: &str) { + // initialize tracing + tracing_subscriber::fmt::init(); + + // Init Axum router + let app = rust_axum_integer_types_test::server::new(Arc::new(ServerImpl)); + + // Add layers to the router + let app = app.layer(...); + + // Run the server with graceful shutdown + let listener = TcpListener::bind(addr).await.unwrap(); + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await + .unwrap(); +} + +async fn shutdown_signal() { + let ctrl_c = async { + signal::ctrl_c() + .await + .expect("failed to install Ctrl+C handler"); + }; + + #[cfg(unix)] + let terminate = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .expect("failed to install signal handler") + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + tokio::select! { + _ = ctrl_c => {}, + _ = terminate => {}, + } +} +``` diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/apis/default.rs b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/apis/default.rs new file mode 100644 index 000000000000..91d7c86df48b --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/apis/default.rs @@ -0,0 +1,32 @@ +use async_trait::async_trait; +use axum::extract::*; +use axum_extra::extract::CookieJar; +use bytes::Bytes; +use headers::Host; +use http::Method; +use serde::{Deserialize, Serialize}; + +use crate::{models, types::*}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[must_use] +#[allow(clippy::large_enum_variant)] +pub enum GetIntegersResponse { + /// OK + Status200_OK(models::IntegerTypes), +} + +/// Default +#[async_trait] +#[allow(clippy::ptr_arg)] +pub trait Default: super::ErrorHandler { + /// GetIntegers - GET /integers + async fn get_integers( + &self, + + method: &Method, + host: &Host, + cookies: &CookieJar, + query_params: &models::GetIntegersQueryParams, + ) -> Result; +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/apis/mod.rs b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/apis/mod.rs new file mode 100644 index 000000000000..da9c9e1b91fa --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/apis/mod.rs @@ -0,0 +1,21 @@ +pub mod default; + +// Error handler for unhandled errors. +#[async_trait::async_trait] +pub trait ErrorHandler { + #[allow(unused_variables)] + #[tracing::instrument(skip_all)] + async fn handle_error( + &self, + method: &::http::Method, + host: &headers::Host, + cookies: &axum_extra::extract::CookieJar, + error: E, + ) -> Result { + tracing::error!("Unhandled error: {:?}", error); + axum::response::Response::builder() + .status(http::StatusCode::INTERNAL_SERVER_ERROR) + .body(axum::body::Body::empty()) + .map_err(|_| http::StatusCode::INTERNAL_SERVER_ERROR) + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/header.rs b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/header.rs new file mode 100644 index 000000000000..970a99da88e4 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/header.rs @@ -0,0 +1,188 @@ +use std::{convert::TryFrom, fmt, ops::Deref}; + +use chrono::{DateTime, Utc}; +use http::HeaderValue; + +/// A struct to allow homogeneous conversion into a HeaderValue. We can't +/// implement the From/Into trait on HeaderValue because we don't own +/// either of the types. +#[derive(Debug, Clone)] +pub(crate) struct IntoHeaderValue(pub T); + +// Generic implementations + +impl Deref for IntoHeaderValue { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +// Derive for each TryFrom in http::HeaderValue + +macro_rules! ihv_generate { + ($t:ident) => { + impl TryFrom for IntoHeaderValue<$t> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match hdr_value.parse::<$t>() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value)), + Err(e) => Err(format!( + r#"Unable to parse {} as a string: {e}"#, + stringify!($t) + )), + }, + Err(e) => Err(format!( + r#"Unable to parse header {hdr_value:?} as a string - {e}"# + )), + } + } + } + + impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue<$t>) -> Result { + Ok(hdr_value.0.into()) + } + } + }; +} + +ihv_generate!(u64); +ihv_generate!(i64); +ihv_generate!(i16); +ihv_generate!(u16); +ihv_generate!(u32); +ihv_generate!(usize); +ihv_generate!(isize); +ihv_generate!(i32); + +// Custom derivations + +// Vec + +impl TryFrom for IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => Ok(IntoHeaderValue( + hdr_value + .split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y.to_string()), + }) + .collect(), + )), + Err(e) => Err(format!( + r#"Unable to parse header: {hdr_value:?} as a string - {e}"# + )), + } + } +} + +impl TryFrom>> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue>) -> Result { + match HeaderValue::from_str(&hdr_value.0.join(", ")) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + r#"Unable to convert {hdr_value:?} into a header - {e}"# + )), + } + } +} + +// String + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value.to_string())), + Err(e) => Err(format!(r#"Unable to convert header {hdr_value:?} to {e}"#)), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_str(&hdr_value.0) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + r#"Unable to convert {hdr_value:?} from a header {e}"# + )), + } + } +} + +// Bool + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match hdr_value.parse() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value)), + Err(e) => Err(format!(r#"Unable to parse bool from {hdr_value} - {e}"#)), + }, + Err(e) => Err(format!( + r#"Unable to convert {hdr_value:?} from a header {e}"# + )), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_str(&hdr_value.0.to_string()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + r#"Unable to convert: {hdr_value:?} into a header: {e}"# + )), + } + } +} + +// DateTime + +impl TryFrom for IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match DateTime::parse_from_rfc3339(hdr_value) { + Ok(date) => Ok(IntoHeaderValue(date.with_timezone(&Utc))), + Err(e) => Err(format!(r#"Unable to parse: {hdr_value} as date - {e}"#)), + }, + Err(e) => Err(format!( + r#"Unable to convert header {hdr_value:?} to string {e}"# + )), + } + } +} + +impl TryFrom>> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue>) -> Result { + match HeaderValue::from_str(hdr_value.0.to_rfc3339().as_str()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + r#"Unable to convert {hdr_value:?} to a header: {e}"# + )), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/lib.rs b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/lib.rs new file mode 100644 index 000000000000..128cc87575eb --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/lib.rs @@ -0,0 +1,28 @@ +#![allow( + missing_docs, + trivial_casts, + unused_variables, + unused_mut, + unused_extern_crates, + non_camel_case_types, + unused_imports, + unused_attributes +)] +#![allow( + clippy::derive_partial_eq_without_eq, + clippy::disallowed_names, + clippy::too_many_arguments +)] + +pub const BASE_PATH: &str = ""; +pub const API_VERSION: &str = "1.0.0"; + +#[cfg(feature = "server")] +pub mod server; + +pub mod apis; +pub mod models; +pub mod types; + +#[cfg(feature = "server")] +pub(crate) mod header; diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/models.rs b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/models.rs new file mode 100644 index 000000000000..87f1b5895485 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/models.rs @@ -0,0 +1,296 @@ +#![allow(unused_qualifications)] + +use http::HeaderValue; +use validator::Validate; + +#[cfg(feature = "server")] +use crate::header; +use crate::{models, types::*}; + +#[allow(dead_code)] +fn from_validation_error(e: validator::ValidationError) -> validator::ValidationErrors { + let mut errs = validator::ValidationErrors::new(); + errs.add("na", e); + errs +} + +#[allow(dead_code)] +pub fn check_xss_string(v: &str) -> std::result::Result<(), validator::ValidationError> { + if ammonia::is_html(v) { + std::result::Result::Err(validator::ValidationError::new("xss detected")) + } else { + std::result::Result::Ok(()) + } +} + +#[allow(dead_code)] +pub fn check_xss_vec_string(v: &[String]) -> std::result::Result<(), validator::ValidationError> { + if v.iter().any(|i| ammonia::is_html(i)) { + std::result::Result::Err(validator::ValidationError::new("xss detected")) + } else { + std::result::Result::Ok(()) + } +} + +#[allow(dead_code)] +pub fn check_xss_map_string( + v: &std::collections::HashMap, +) -> std::result::Result<(), validator::ValidationError> { + if v.keys().any(|k| ammonia::is_html(k)) || v.values().any(|v| ammonia::is_html(v)) { + std::result::Result::Err(validator::ValidationError::new("xss detected")) + } else { + std::result::Result::Ok(()) + } +} + +#[allow(dead_code)] +pub fn check_xss_map_nested( + v: &std::collections::HashMap, +) -> std::result::Result<(), validator::ValidationError> +where + T: validator::Validate, +{ + if v.keys().any(|k| ammonia::is_html(k)) || v.values().any(|v| v.validate().is_err()) { + std::result::Result::Err(validator::ValidationError::new("xss detected")) + } else { + std::result::Result::Ok(()) + } +} + +#[allow(dead_code)] +pub fn check_xss_map( + v: &std::collections::HashMap, +) -> std::result::Result<(), validator::ValidationError> { + if v.keys().any(|k| ammonia::is_html(k)) { + std::result::Result::Err(validator::ValidationError::new("xss detected")) + } else { + std::result::Result::Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct GetIntegersQueryParams { + #[serde(rename = "legacy_uint32")] + pub legacy_uint32: u32, + #[serde(rename = "legacy_uint64")] + pub legacy_uint64: u64, + #[serde(rename = "positive_int32")] + #[validate(range(min = 0u32))] + pub positive_int32: u32, + #[serde(rename = "positive_int64")] + #[validate(range(min = 0u64))] + pub positive_int64: u64, + #[serde(rename = "small_positive")] + #[validate(range(min = 0u8, max = 255u8))] + pub small_positive: u8, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct IntegerTypes { + #[serde(rename = "legacy_uint32")] + pub legacy_uint32: u32, + + #[serde(rename = "legacy_uint64")] + pub legacy_uint64: u64, + + #[serde(rename = "positive_int32")] + #[validate(range(min = 0u32))] + pub positive_int32: u32, + + #[serde(rename = "positive_int64")] + #[validate(range(min = 0u64))] + pub positive_int64: u64, + + #[serde(rename = "small_positive")] + #[validate(range(min = 0u8, max = 255u8))] + pub small_positive: u8, +} + +impl IntegerTypes { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new( + legacy_uint32: u32, + legacy_uint64: u64, + positive_int32: u32, + positive_int64: u64, + small_positive: u8, + ) -> IntegerTypes { + IntegerTypes { + legacy_uint32, + legacy_uint64, + positive_int32, + positive_int64, + small_positive, + } + } +} + +/// Converts the IntegerTypes 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 IntegerTypes { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("legacy_uint32".to_string()), + Some(self.legacy_uint32.to_string()), + Some("legacy_uint64".to_string()), + Some(self.legacy_uint64.to_string()), + Some("positive_int32".to_string()), + Some(self.positive_int32.to_string()), + Some("positive_int64".to_string()), + Some(self.positive_int64.to_string()), + Some("small_positive".to_string()), + Some(self.small_positive.to_string()), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a IntegerTypes value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for IntegerTypes { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub legacy_uint32: Vec, + pub legacy_uint64: Vec, + pub positive_int32: Vec, + pub positive_int64: Vec, + pub small_positive: 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 IntegerTypes".to_string(), + ); + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "legacy_uint32" => intermediate_rep.legacy_uint32.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "legacy_uint64" => intermediate_rep.legacy_uint64.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "positive_int32" => intermediate_rep.positive_int32.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "positive_int64" => intermediate_rep.positive_int64.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "small_positive" => intermediate_rep + .small_positive + .push(::from_str(val).map_err(|x| x.to_string())?), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing IntegerTypes".to_string(), + ); + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(IntegerTypes { + legacy_uint32: intermediate_rep + .legacy_uint32 + .into_iter() + .next() + .ok_or_else(|| "legacy_uint32 missing in IntegerTypes".to_string())?, + legacy_uint64: intermediate_rep + .legacy_uint64 + .into_iter() + .next() + .ok_or_else(|| "legacy_uint64 missing in IntegerTypes".to_string())?, + positive_int32: intermediate_rep + .positive_int32 + .into_iter() + .next() + .ok_or_else(|| "positive_int32 missing in IntegerTypes".to_string())?, + positive_int64: intermediate_rep + .positive_int64 + .into_iter() + .next() + .ok_or_else(|| "positive_int64 missing in IntegerTypes".to_string())?, + small_positive: intermediate_rep + .small_positive + .into_iter() + .next() + .ok_or_else(|| "small_positive missing in IntegerTypes".to_string())?, + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + r#"Invalid header value for IntegerTypes - 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 IntegerTypes - {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-integer-types-test/src/server/mod.rs b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/server/mod.rs new file mode 100644 index 000000000000..d3b621c2ff59 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/server/mod.rs @@ -0,0 +1,121 @@ +use std::collections::HashMap; + +use axum::{body::Body, extract::*, response::Response, routing::*}; +use axum_extra::{ + TypedHeader, + extract::{CookieJar, Query as QueryExtra}, +}; +use bytes::Bytes; +use headers::Host; +use http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, header::CONTENT_TYPE}; +use tracing::error; +use validator::{Validate, ValidationErrors}; + +#[allow(unused_imports)] +use crate::{apis, models}; +use crate::{header, types::*}; +#[allow(unused_imports)] +use crate::{ + models::check_xss_map, models::check_xss_map_nested, models::check_xss_map_string, + models::check_xss_string, models::check_xss_vec_string, +}; + +/// Setup API Server. +pub fn new(api_impl: I) -> Router +where + I: AsRef + Clone + Send + Sync + 'static, + A: apis::default::Default + Send + Sync + 'static, + E: std::fmt::Debug + Send + Sync + 'static, +{ + // build our application with a route + Router::new() + .route("/integers", get(get_integers::)) + .with_state(api_impl) +} + +#[tracing::instrument(skip_all)] +fn get_integers_validation( + query_params: models::GetIntegersQueryParams, +) -> std::result::Result<(models::GetIntegersQueryParams,), ValidationErrors> { + query_params.validate()?; + + Ok((query_params,)) +} +/// GetIntegers - GET /integers +#[tracing::instrument(skip_all)] +async fn get_integers( + method: Method, + TypedHeader(host): TypedHeader, + cookies: CookieJar, + QueryExtra(query_params): QueryExtra, + State(api_impl): State, +) -> Result +where + I: AsRef + Send + Sync, + A: apis::default::Default + Send + Sync, + E: std::fmt::Debug + Send + Sync + 'static, +{ + #[allow(clippy::redundant_closure)] + let validation = tokio::task::spawn_blocking(move || get_integers_validation(query_params)) + .await + .unwrap(); + + let Ok((query_params,)) = validation else { + return Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Body::from(validation.unwrap_err().to_string())) + .map_err(|_| StatusCode::BAD_REQUEST); + }; + + let result = api_impl + .as_ref() + .get_integers(&method, &host, &cookies, &query_params) + .await; + + let mut response = Response::builder(); + + let resp = match result { + Ok(rsp) => match rsp { + apis::default::GetIntegersResponse::Status200_OK(body) => { + let mut response = response.status(200); + { + let mut response_headers = response.headers_mut().unwrap(); + response_headers + .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); + } + + let body_content = tokio::task::spawn_blocking(move || { + serde_json::to_vec(&body).map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) + }) + .await + .unwrap()?; + response.body(Body::from(body_content)) + } + }, + Err(why) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + return api_impl + .as_ref() + .handle_error(&method, &host, &cookies, why) + .await; + } + }; + + resp.map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + +#[allow(dead_code)] +#[inline] +fn response_with_status_code_only(code: StatusCode) -> Result { + Response::builder() + .status(code) + .body(Body::empty()) + .map_err(|_| code) +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/types.rs b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/types.rs new file mode 100644 index 000000000000..23b362c1b583 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/rust-axum-integer-types-test/src/types.rs @@ -0,0 +1,790 @@ +use std::{mem, str::FromStr}; + +use base64::{Engine, engine::general_purpose}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[allow(dead_code)] +pub struct Object(pub serde_json::Value); + +impl validator::Validate for Object { + fn validate(&self) -> Result<(), validator::ValidationErrors> { + Ok(()) + } +} + +impl FromStr for Object { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(Self(serde_json::Value::String(s.to_owned()))) + } +} + +/// Serde helper function to create a default `Option>` while +/// deserializing +pub fn default_optional_nullable() -> Option> { + None +} + +/// Serde helper function to deserialize into an `Option>` +pub fn deserialize_optional_nullable<'de, D, T>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de>, +{ + Option::::deserialize(deserializer).map(|val| match val { + Some(inner) => Some(Nullable::Present(inner)), + None => Some(Nullable::Null), + }) +} + +/// The Nullable type. Represents a value which may be specified as null on an API. +/// Note that this is distinct from a value that is optional and not present! +/// +/// Nullable implements many of the same methods as the Option type (map, unwrap, etc). +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub enum Nullable { + /// Null value + Null, + /// Value is present + Present(T), +} + +impl Nullable { + ///////////////////////////////////////////////////////////////////////// + // Querying the contained values + ///////////////////////////////////////////////////////////////////////// + + /// Returns `true` if the Nullable is a `Present` value. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x: Nullable = Nullable::Present(2); + /// assert_eq!(x.is_present(), true); + /// + /// let x: Nullable = Nullable::Null; + /// assert_eq!(x.is_present(), false); + /// ``` + #[inline] + pub fn is_present(&self) -> bool { + match *self { + Nullable::Present(_) => true, + Nullable::Null => false, + } + } + + /// Returns `true` if the Nullable is a `Null` value. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x: Nullable = Nullable::Present(2); + /// assert_eq!(x.is_null(), false); + /// + /// let x: Nullable = Nullable::Null; + /// assert_eq!(x.is_null(), true); + /// ``` + #[inline] + pub fn is_null(&self) -> bool { + !self.is_present() + } + + ///////////////////////////////////////////////////////////////////////// + // Adapter for working with references + ///////////////////////////////////////////////////////////////////////// + + /// Converts from `Nullable` to `Nullable<&T>`. + /// + /// # Examples + /// + /// Convert an `Nullable<`[`String`]`>` into a `Nullable<`[`usize`]`>`, preserving the original. + /// The [`map`] method takes the `self` argument by value, consuming the original, + /// so this technique uses `as_ref` to first take a `Nullable` to a reference + /// to the value inside the original. + /// + /// [`map`]: enum.Nullable.html#method.map + /// [`String`]: ../../std/string/struct.String.html + /// [`usize`]: ../../std/primitive.usize.html + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let num_as_str: Nullable = Nullable::Present("10".to_string()); + /// // First, cast `Nullable` to `Nullable<&String>` with `as_ref`, + /// // then consume *that* with `map`, leaving `num_as_str` on the stack. + /// let num_as_int: Nullable = num_as_str.as_ref().map(|n| n.len()); + /// println!("still can print num_as_str: {:?}", num_as_str); + /// ``` + #[inline] + pub fn as_ref(&self) -> Nullable<&T> { + match *self { + Nullable::Present(ref x) => Nullable::Present(x), + Nullable::Null => Nullable::Null, + } + } + + /// Converts from `Nullable` to `Nullable<&mut T>`. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let mut x = Nullable::Present(2); + /// match x.as_mut() { + /// Nullable::Present(v) => *v = 42, + /// Nullable::Null => {}, + /// } + /// assert_eq!(x, Nullable::Present(42)); + /// ``` + #[inline] + pub fn as_mut(&mut self) -> Nullable<&mut T> { + match *self { + Nullable::Present(ref mut x) => Nullable::Present(x), + Nullable::Null => Nullable::Null, + } + } + + ///////////////////////////////////////////////////////////////////////// + // Getting to contained values + ///////////////////////////////////////////////////////////////////////// + + /// Unwraps a Nullable, yielding the content of a `Nullable::Present`. + /// + /// # Panics + /// + /// Panics if the value is a [`Nullable::Null`] with a custom panic message provided by + /// `msg`. + /// + /// [`Nullable::Null`]: #variant.Null + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present("value"); + /// assert_eq!(x.expect("the world is ending"), "value"); + /// ``` + /// + /// ```should_panic + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x: Nullable<&str> = Nullable::Null; + /// x.expect("the world is ending"); // panics with `the world is ending` + /// ``` + #[inline] + pub fn expect(self, msg: &str) -> T { + match self { + Nullable::Present(val) => val, + Nullable::Null => expect_failed(msg), + } + } + + /// Moves the value `v` out of the `Nullable` if it is `Nullable::Present(v)`. + /// + /// In general, because this function may panic, its use is discouraged. + /// Instead, prefer to use pattern matching and handle the `Nullable::Null` + /// case explicitly. + /// + /// # Panics + /// + /// Panics if the self value equals [`Nullable::Null`]. + /// + /// [`Nullable::Null`]: #variant.Null + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present("air"); + /// assert_eq!(x.unwrap(), "air"); + /// ``` + /// + /// ```should_panic + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.unwrap(), "air"); // fails + /// ``` + #[inline] + pub fn unwrap(self) -> T { + match self { + Nullable::Present(val) => val, + Nullable::Null => panic!("called `Nullable::unwrap()` on a `Nullable::Null` value"), + } + } + + /// Returns the contained value or a default. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// assert_eq!(Nullable::Present("car").unwrap_or("bike"), "car"); + /// assert_eq!(Nullable::Null.unwrap_or("bike"), "bike"); + /// ``` + #[inline] + pub fn unwrap_or(self, def: T) -> T { + match self { + Nullable::Present(x) => x, + Nullable::Null => def, + } + } + + /// Returns the contained value or computes it from a closure. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let k = 10; + /// assert_eq!(Nullable::Present(4).unwrap_or_else(|| 2 * k), 4); + /// assert_eq!(Nullable::Null.unwrap_or_else(|| 2 * k), 20); + /// ``` + #[inline] + pub fn unwrap_or_else T>(self, f: F) -> T { + match self { + Nullable::Present(x) => x, + Nullable::Null => f(), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Transforming contained values + ///////////////////////////////////////////////////////////////////////// + + /// Maps a `Nullable` to `Nullable` by applying a function to a contained value. + /// + /// # Examples + /// + /// Convert a `Nullable<`[`String`]`>` into a `Nullable<`[`usize`]`>`, consuming the original: + /// + /// [`String`]: ../../std/string/struct.String.html + /// [`usize`]: ../../std/primitive.usize.html + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let maybe_some_string = Nullable::Present(String::from("Hello, World!")); + /// // `Nullable::map` takes self *by value*, consuming `maybe_some_string` + /// let maybe_some_len = maybe_some_string.map(|s| s.len()); + /// + /// assert_eq!(maybe_some_len, Nullable::Present(13)); + /// ``` + #[inline] + pub fn map U>(self, f: F) -> Nullable { + match self { + Nullable::Present(x) => Nullable::Present(f(x)), + Nullable::Null => Nullable::Null, + } + } + + /// Applies a function to the contained value (if any), + /// or returns a `default` (if not). + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.map_or(42, |v| v.len()), 3); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.map_or(42, |v| v.len()), 42); + /// ``` + #[inline] + pub fn map_or U>(self, default: U, f: F) -> U { + match self { + Nullable::Present(t) => f(t), + Nullable::Null => default, + } + } + + /// Applies a function to the contained value (if any), + /// or computes a `default` (if not). + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let k = 21; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 3); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.map_or_else(|| 2 * k, |v| v.len()), 42); + /// ``` + #[inline] + pub fn map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U { + match self { + Nullable::Present(t) => f(t), + Nullable::Null => default(), + } + } + + /// Transforms the `Nullable` into a [`Result`], mapping `Nullable::Present(v)` to + /// [`Ok(v)`] and `Nullable::Null` to [`Err(err)`][Err]. + /// + /// [`Result`]: ../../std/result/enum.Result.html + /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok + /// [Err]: ../../std/result/enum.Result.html#variant.Err + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.ok_or(0), Ok("foo")); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.ok_or(0), Err(0)); + /// ``` + #[inline] + pub fn ok_or(self, err: E) -> Result { + match self { + Nullable::Present(v) => Ok(v), + Nullable::Null => Err(err), + } + } + + /// Transforms the `Nullable` into a [`Result`], mapping `Nullable::Present(v)` to + /// [`Ok(v)`] and `Nullable::Null` to [`Err(err())`][Err]. + /// + /// [`Result`]: ../../std/result/enum.Result.html + /// [`Ok(v)`]: ../../std/result/enum.Result.html#variant.Ok + /// [Err]: ../../std/result/enum.Result.html#variant.Err + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present("foo"); + /// assert_eq!(x.ok_or_else(|| 0), Ok("foo")); + /// + /// let x: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.ok_or_else(|| 0), Err(0)); + /// ``` + #[inline] + pub fn ok_or_else E>(self, err: F) -> Result { + match self { + Nullable::Present(v) => Ok(v), + Nullable::Null => Err(err()), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Boolean operations on the values, eager and lazy + ///////////////////////////////////////////////////////////////////////// + + /// Returns `Nullable::Null` if the Nullable is `Nullable::Null`, otherwise returns `optb`. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present(2); + /// let y: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.and(y), Nullable::Null); + /// + /// let x: Nullable = Nullable::Null; + /// let y = Nullable::Present("foo"); + /// assert_eq!(x.and(y), Nullable::Null); + /// + /// let x = Nullable::Present(2); + /// let y = Nullable::Present("foo"); + /// assert_eq!(x.and(y), Nullable::Present("foo")); + /// + /// let x: Nullable = Nullable::Null; + /// let y: Nullable<&str> = Nullable::Null; + /// assert_eq!(x.and(y), Nullable::Null); + /// ``` + #[inline] + pub fn and(self, optb: Nullable) -> Nullable { + match self { + Nullable::Present(_) => optb, + Nullable::Null => Nullable::Null, + } + } + + /// Returns `Nullable::Null` if the Nullable is `Nullable::Null`, otherwise calls `f` with the + /// wrapped value and returns the result. + /// + /// Some languages call this operation flatmap. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// fn sq(x: u32) -> Nullable { Nullable::Present(x * x) } + /// fn nope(_: u32) -> Nullable { Nullable::Null } + /// + /// assert_eq!(Nullable::Present(2).and_then(sq).and_then(sq), Nullable::Present(16)); + /// assert_eq!(Nullable::Present(2).and_then(sq).and_then(nope), Nullable::Null); + /// assert_eq!(Nullable::Present(2).and_then(nope).and_then(sq), Nullable::Null); + /// assert_eq!(Nullable::Null.and_then(sq).and_then(sq), Nullable::Null); + /// ``` + #[inline] + pub fn and_then Nullable>(self, f: F) -> Nullable { + match self { + Nullable::Present(x) => f(x), + Nullable::Null => Nullable::Null, + } + } + + /// Returns the Nullable if it contains a value, otherwise returns `optb`. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present(2); + /// let y = Nullable::Null; + /// assert_eq!(x.or(y), Nullable::Present(2)); + /// + /// let x = Nullable::Null; + /// let y = Nullable::Present(100); + /// assert_eq!(x.or(y), Nullable::Present(100)); + /// + /// let x = Nullable::Present(2); + /// let y = Nullable::Present(100); + /// assert_eq!(x.or(y), Nullable::Present(2)); + /// + /// let x: Nullable = Nullable::Null; + /// let y = Nullable::Null; + /// assert_eq!(x.or(y), Nullable::Null); + /// ``` + #[inline] + pub fn or(self, optb: Nullable) -> Nullable { + match self { + Nullable::Present(_) => self, + Nullable::Null => optb, + } + } + + /// Returns the Nullable if it contains a value, otherwise calls `f` and + /// returns the result. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// fn nobody() -> Nullable<&'static str> { Nullable::Null } + /// fn vikings() -> Nullable<&'static str> { Nullable::Present("vikings") } + /// + /// assert_eq!(Nullable::Present("barbarians").or_else(vikings), + /// Nullable::Present("barbarians")); + /// assert_eq!(Nullable::Null.or_else(vikings), Nullable::Present("vikings")); + /// assert_eq!(Nullable::Null.or_else(nobody), Nullable::Null); + /// ``` + #[inline] + pub fn or_else Nullable>(self, f: F) -> Nullable { + match self { + Nullable::Present(_) => self, + Nullable::Null => f(), + } + } + + ///////////////////////////////////////////////////////////////////////// + // Misc + ///////////////////////////////////////////////////////////////////////// + + /// Takes the value out of the Nullable, leaving a `Nullable::Null` in its place. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let mut x = Nullable::Present(2); + /// x.take(); + /// assert_eq!(x, Nullable::Null); + /// + /// let mut x: Nullable = Nullable::Null; + /// x.take(); + /// assert_eq!(x, Nullable::Null); + /// ``` + #[inline] + pub fn take(&mut self) -> Nullable { + mem::replace(self, Nullable::Null) + } +} + +impl Nullable<&T> { + /// Maps an `Nullable<&T>` to an `Nullable` by cloning the contents of the + /// Nullable. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = 12; + /// let opt_x = Nullable::Present(&x); + /// assert_eq!(opt_x, Nullable::Present(&12)); + /// let cloned = opt_x.cloned(); + /// assert_eq!(cloned, Nullable::Present(12)); + /// ``` + pub fn cloned(self) -> Nullable { + self.map(Clone::clone) + } +} + +impl Nullable { + /// Returns the contained value or a default + /// + /// Consumes the `self` argument then, if `Nullable::Present`, returns the contained + /// value, otherwise if `Nullable::Null`, returns the default value for that + /// type. + /// + /// # Examples + /// + /// ``` + /// # use rust_axum_integer_types_test::types::Nullable; + /// + /// let x = Nullable::Present(42); + /// assert_eq!(42, x.unwrap_or_default()); + /// + /// let y: Nullable = Nullable::Null; + /// assert_eq!(0, y.unwrap_or_default()); + /// ``` + #[inline] + pub fn unwrap_or_default(self) -> T { + match self { + Nullable::Present(x) => x, + Nullable::Null => Default::default(), + } + } +} + +impl Default for Nullable { + /// Returns None. + #[inline] + fn default() -> Nullable { + Nullable::Null + } +} + +impl From for Nullable { + fn from(val: T) -> Nullable { + Nullable::Present(val) + } +} + +impl Serialize for Nullable +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + Nullable::Present(ref inner) => serializer.serialize_some(&inner), + Nullable::Null => serializer.serialize_none(), + } + } +} + +impl<'de, T> Deserialize<'de> for Nullable +where + T: serde::de::DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + // In order to deserialize a required, but nullable, value, we first have to check whether + // the value is present at all. To do this, we deserialize to a serde_json::Value, which + // fails if the value is missing, or gives serde_json::Value::Null if the value is present. + // If that succeeds as null, we can easily return a Null. + // If that succeeds as some value, we deserialize that value and return a Present. + // If that errors, we return the error. + let presence: Result<::serde_json::Value, _> = + serde::Deserialize::deserialize(deserializer); + match presence { + Ok(serde_json::Value::Null) => Ok(Nullable::Null), + Ok(some_value) => serde_json::from_value(some_value) + .map(Nullable::Present) + .map_err(serde::de::Error::custom), + Err(x) => Err(x), + } + } +} + +impl validator::Validate for Nullable +where + T: validator::Validate, +{ + fn validate(&self) -> Result<(), validator::ValidationErrors> { + match self { + Self::Present(x) => x.validate(), + Self::Null => Ok(()), + } + } +} + +impl<'a, T> validator::ValidateArgs<'a> for Nullable +where + T: validator::ValidateArgs<'a>, +{ + type Args = T::Args; + fn validate_with_args(&self, args: Self::Args) -> Result<(), validator::ValidationErrors> { + match self { + Self::Present(x) => x.validate_with_args(args), + Self::Null => Ok(()), + } + } +} + +impl validator::ValidateEmail for Nullable +where + T: validator::ValidateEmail, +{ + fn as_email_string(&'_ self) -> Option> { + match self { + Self::Present(x) => x.as_email_string(), + Self::Null => None, + } + } +} + +impl validator::ValidateUrl for Nullable +where + T: validator::ValidateUrl, +{ + fn as_url_string(&'_ self) -> Option> { + match self { + Self::Present(x) => x.as_url_string(), + Self::Null => None, + } + } +} + +impl validator::ValidateContains for Nullable +where + T: validator::ValidateContains, +{ + fn validate_contains(&self, needle: &str) -> bool { + match self { + Self::Present(x) => x.validate_contains(needle), + Self::Null => true, + } + } +} + +impl validator::ValidateRequired for Nullable +where + T: validator::ValidateRequired, +{ + fn is_some(&self) -> bool { + self.is_present() + } +} + +impl validator::ValidateRegex for Nullable +where + T: validator::ValidateRegex, +{ + fn validate_regex(&self, regex: impl validator::AsRegex) -> bool { + match self { + Self::Present(x) => x.validate_regex(regex), + Self::Null => true, + } + } +} + +impl validator::ValidateRange for Nullable +where + T: validator::ValidateRange, +{ + fn greater_than(&self, max: I) -> Option { + use validator::ValidateRange; + match self { + Self::Present(x) => x.greater_than(max), + Self::Null => None, + } + } + fn less_than(&self, min: I) -> Option { + use validator::ValidateRange; + match self { + Self::Present(x) => x.less_than(min), + Self::Null => None, + } + } +} + +impl validator::ValidateLength for Nullable +where + T: validator::ValidateLength, + I: PartialEq + PartialOrd, +{ + fn length(&self) -> Option { + use validator::ValidateLength; + match self { + Self::Present(x) => x.length(), + Self::Null => None, + } + } +} + +impl From> for Option { + fn from(value: Nullable) -> Option { + match value { + Nullable::Present(x) => Some(x), + Nullable::Null => None, + } + } +} + +#[inline(never)] +#[cold] +fn expect_failed(msg: &str) -> ! { + panic!("{}", msg) +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +/// Base64-encoded byte array +pub struct ByteArray(pub Vec); + +impl Serialize for ByteArray { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&general_purpose::STANDARD.encode(&self.0)) + } +} + +impl<'de> Deserialize<'de> for ByteArray { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match general_purpose::STANDARD.decode(s) { + Ok(bin) => Ok(ByteArray(bin)), + _ => Err(serde::de::Error::custom("invalid base64")), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/rust-axum-oneof/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-oneof/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-oneof/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/rust-axum-oneof/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-oneof/README.md b/samples/server/petstore/rust-axum/output/rust-axum-oneof/README.md index 5a3b24b05fd4..61d21f6759b5 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-oneof/README.md +++ b/samples/server/petstore/rust-axum/output/rust-axum-oneof/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 0.0.1 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/rust-axum-test/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-test/README.md b/samples/server/petstore/rust-axum/output/rust-axum-test/README.md index 78a4b7ec7845..d603702a532a 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-test/README.md +++ b/samples/server/petstore/rust-axum/output/rust-axum-test/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 2.3.4 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT 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 984471b8f2db..a25538279865 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 @@ -1155,9 +1155,9 @@ impl std::convert::TryFrom for header::IntoHeaderValue, + pub name: Option, } impl FooUnnamedAllofUnderProperties { @@ -1197,7 +1197,7 @@ impl std::str::FromStr for FooUnnamedAllofUnderProperties { #[derive(Default)] #[allow(dead_code)] struct IntermediateRep { - pub name: Vec, + pub name: Vec, } let mut intermediate_rep = IntermediateRep::default(); @@ -1220,9 +1220,9 @@ impl std::str::FromStr for FooUnnamedAllofUnderProperties { #[allow(clippy::match_single_binding)] match key { #[allow(clippy::redundant_clone)] - "name" => intermediate_rep - .name - .push(::from_str(val).map_err(|x| x.to_string())?), + "name" => intermediate_rep.name.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), _ => { return std::result::Result::Err( "Unexpected key while parsing FooUnnamedAllofUnderProperties" @@ -1289,37 +1289,118 @@ impl std::convert::TryFrom } } -#[derive(Debug, Clone, PartialEq, PartialOrd, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] #[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] -pub struct FooUnnamedReference(pub i32); +pub struct FooUnnamedReference {} -impl validator::Validate for FooUnnamedReference { - fn validate(&self) -> std::result::Result<(), validator::ValidationErrors> { - std::result::Result::Ok(()) +impl FooUnnamedReference { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> FooUnnamedReference { + FooUnnamedReference {} } } -impl std::convert::From for FooUnnamedReference { - fn from(x: i32) -> Self { - FooUnnamedReference(x) +/// Converts the FooUnnamedReference 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 FooUnnamedReference { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) } } -impl std::convert::From for i32 { - fn from(x: FooUnnamedReference) -> Self { - x.0 +/// Converts Query Parameters representation (style=form, explode=false) to a FooUnnamedReference value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for FooUnnamedReference { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep {} + + 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 FooUnnamedReference".to_string(), + ); + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + _ => { + return std::result::Result::Err( + "Unexpected key while parsing FooUnnamedReference".to_string(), + ); + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(FooUnnamedReference {}) } } -impl std::ops::Deref for FooUnnamedReference { - type Target = i32; - fn deref(&self) -> &i32 { - &self.0 +// 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 FooUnnamedReference - value: {hdr_value} is invalid {e}"# + )), + } } } -impl std::ops::DerefMut for FooUnnamedReference { - fn deref_mut(&mut self) -> &mut i32 { - &mut self.0 +#[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 FooUnnamedReference - {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/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/.openapi-generator/VERSION index 909dcd0eca63..f7962df3e243 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-validation-test/.openapi-generator/VERSION +++ b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/.openapi-generator/VERSION @@ -1 +1 @@ -7.19.0-SNAPSHOT +7.22.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/rust-axum-validation-test/README.md b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/README.md index 7598653173f1..ec1f69647fe5 100644 --- a/samples/server/petstore/rust-axum/output/rust-axum-validation-test/README.md +++ b/samples/server/petstore/rust-axum/output/rust-axum-validation-test/README.md @@ -9,10 +9,10 @@ This server was generated by the [openapi-generator] [OpenAPI-Spec](https://github.com/OAI/OpenAPI-Specification) from a remote server, you can easily generate a server stub. -To see how to make this your own, look here: [README]((https://openapi-generator.tech)) +To see how to make this your own, look here: [README](https://openapi-generator.tech) - API version: 0.0.1 -- Generator version: 7.19.0-SNAPSHOT +- Generator version: 7.22.0-SNAPSHOT