diff --git a/.github/workflows/samples-rust.yaml b/.github/workflows/samples-rust.yaml
index 02d885611d6..f54dab346c6 100644
--- a/.github/workflows/samples-rust.yaml
+++ b/.github/workflows/samples-rust.yaml
@@ -6,11 +6,13 @@ on:
- "samples/client/others/rust/**"
- "samples/server/petstore/rust-server/**"
- "samples/client/petstore/rust-server/**"
+ - "samples/server/petstore/rust-axum/**"
pull_request:
paths:
- "samples/client/others/rust/**"
- "samples/client/petstore/rust/**"
- "samples/server/petstore/rust-server/**"
+ - "samples/server/petstore/rust-axum/**"
jobs:
build:
@@ -24,6 +26,7 @@ jobs:
- samples/client/others/rust/
- samples/client/petstore/rust/
- samples/server/petstore/rust-server/
+ - samples/server/petstore/rust-axum/
steps:
- uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1
diff --git a/.travis.yml b/.travis.yml
index 306c1da810a..c5c3dbc8459 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,6 +22,7 @@ cache:
- $HOME/samples/client/petstore/ruby/vendor/bundle
- $HOME/samples/client/petstore/python/.venv/
- $HOME/samples/server/petstore/rust-server/target
+ - $HOME/samples/server/petstore/rust-axum/target
- $HOME/perl5
- $HOME/.cargo
- $HOME/.pub-cache
diff --git a/bin/configs/rust-axum-multipart-v3.yaml b/bin/configs/rust-axum-multipart-v3.yaml
new file mode 100644
index 00000000000..13f7d4a3368
--- /dev/null
+++ b/bin/configs/rust-axum-multipart-v3.yaml
@@ -0,0 +1,10 @@
+generatorName: rust-axum
+outputDir: samples/server/petstore/rust-axum/output/multipart-v3
+inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-server/multipart-v3.yaml
+templateDir: modules/openapi-generator/src/main/resources/rust-axum
+generateAliasAsModel: true
+additionalProperties:
+ hideGenerationTimestamp: "true"
+ allowBlockingResponseSerialize: "true"
+ packageName: multipart-v3
+enablePostProcessFile: true
diff --git a/bin/configs/rust-axum-openapi-v3.yaml b/bin/configs/rust-axum-openapi-v3.yaml
new file mode 100644
index 00000000000..35ae45d6950
--- /dev/null
+++ b/bin/configs/rust-axum-openapi-v3.yaml
@@ -0,0 +1,10 @@
+generatorName: rust-axum
+outputDir: samples/server/petstore/rust-axum/output/openapi-v3
+inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-server/openapi-v3.yaml
+templateDir: modules/openapi-generator/src/main/resources/rust-axum
+generateAliasAsModel: true
+additionalProperties:
+ hideGenerationTimestamp: "true"
+ allowBlockingValidator: "true"
+ packageName: openapi-v3
+enablePostProcessFile: true
diff --git a/bin/configs/rust-axum-ops-v3.yaml b/bin/configs/rust-axum-ops-v3.yaml
new file mode 100644
index 00000000000..c0a217166d3
--- /dev/null
+++ b/bin/configs/rust-axum-ops-v3.yaml
@@ -0,0 +1,8 @@
+generatorName: rust-axum
+outputDir: samples/server/petstore/rust-axum/output/ops-v3
+inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-server/ops-v3.yaml
+templateDir: modules/openapi-generator/src/main/resources/rust-axum
+generateAliasAsModel: true
+additionalProperties:
+ hideGenerationTimestamp: "true"
+ packageName: ops-v3
diff --git a/bin/configs/rust-axum-petstore-with-fake-endpoints-models-for-testing.yaml b/bin/configs/rust-axum-petstore-with-fake-endpoints-models-for-testing.yaml
new file mode 100644
index 00000000000..69ca39092e4
--- /dev/null
+++ b/bin/configs/rust-axum-petstore-with-fake-endpoints-models-for-testing.yaml
@@ -0,0 +1,10 @@
+generatorName: rust-axum
+outputDir: samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing
+inputSpec: modules/openapi-generator/src/test/resources/2_0/rust-server/petstore-with-fake-endpoints-models-for-testing.yaml
+templateDir: modules/openapi-generator/src/main/resources/rust-axum
+generateAliasAsModel: true
+additionalProperties:
+ hideGenerationTimestamp: "true"
+ packageName: petstore-with-fake-endpoints-models-for-testing
+ publishRustRegistry: crates-io
+enablePostProcessFile: true
\ No newline at end of file
diff --git a/bin/configs/rust-axum-petstore.yaml b/bin/configs/rust-axum-petstore.yaml
new file mode 100644
index 00000000000..3149c741858
--- /dev/null
+++ b/bin/configs/rust-axum-petstore.yaml
@@ -0,0 +1,9 @@
+generatorName: rust-axum
+outputDir: samples/server/petstore/rust-axum/output/petstore
+inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
+templateDir: modules/openapi-generator/src/main/resources/rust-axum
+generateAliasAsModel: true
+additionalProperties:
+ hideGenerationTimestamp: "true"
+ packageName: petstore
+enablePostProcessFile: true
diff --git a/bin/configs/rust-axum-ping-bearer-auth-v3.yaml b/bin/configs/rust-axum-ping-bearer-auth-v3.yaml
new file mode 100644
index 00000000000..573a6b9f199
--- /dev/null
+++ b/bin/configs/rust-axum-ping-bearer-auth-v3.yaml
@@ -0,0 +1,9 @@
+generatorName: rust-axum
+outputDir: samples/server/petstore/rust-axum/output/ping-bearer-auth
+inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-server/ping-bearer-auth.yaml
+templateDir: modules/openapi-generator/src/main/resources/rust-axum
+generateAliasAsModel: true
+additionalProperties:
+ hideGenerationTimestamp: "true"
+ packageName: ping-bearer-auth
+enablePostProcessFile: true
diff --git a/bin/configs/rust-axum-test.yaml b/bin/configs/rust-axum-test.yaml
new file mode 100644
index 00000000000..b73b3fb5313
--- /dev/null
+++ b/bin/configs/rust-axum-test.yaml
@@ -0,0 +1,9 @@
+generatorName: rust-axum
+outputDir: samples/server/petstore/rust-axum/output/rust-axum-test
+inputSpec: modules/openapi-generator/src/test/resources/2_0/rust-server/rust-server-test.yaml
+templateDir: modules/openapi-generator/src/main/resources/rust-axum
+generateAliasAsModel: true
+additionalProperties:
+ hideGenerationTimestamp: "true"
+ packageName: rust-server-test
+enablePostProcessFile: true
diff --git a/docs/generators.md b/docs/generators.md
index c9e07259a0e..8a72497b0c8 100644
--- a/docs/generators.md
+++ b/docs/generators.md
@@ -130,6 +130,7 @@ The following generators are available:
* [python-flask](generators/python-flask.md)
* [ruby-on-rails](generators/ruby-on-rails.md)
* [ruby-sinatra](generators/ruby-sinatra.md)
+* [rust-axum (beta)](generators/rust-axum.md)
* [rust-server](generators/rust-server.md)
* [scala-akka-http-server (beta)](generators/scala-akka-http-server.md)
* [scala-finch](generators/scala-finch.md)
diff --git a/docs/generators/rust-axum.md b/docs/generators/rust-axum.md
new file mode 100644
index 00000000000..188ecaf27ad
--- /dev/null
+++ b/docs/generators/rust-axum.md
@@ -0,0 +1,235 @@
+---
+title: Documentation for the rust-axum Generator
+---
+
+## METADATA
+
+| Property | Value | Notes |
+| -------- | ----- | ----- |
+| generator name | rust-axum | pass this to the generate command after -g |
+| generator stability | BETA | |
+| generator type | SERVER | |
+| generator language | Rust | |
+| generator default templating engine | mustache | |
+| helpTxt | Generates a Rust server library which bases on Axum. | |
+
+## CONFIG OPTIONS
+These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
+
+| Option | Description | Values | Default |
+| ------ | ----------- | ------ | ------- |
+|allowBlockingResponseSerialize|By default, json/form-urlencoded response serialization, which might perform a lot of compute in a future without yielding, is executed on a blocking thread via tokio::task::spawn_blocking. Set this option to true will override this behaviour and allow blocking call to happen. It helps to improve the performance when response serialization (e.g. returns tiny data) is low cost.| |false|
+|allowBlockingValidator|By default, validation process, which might perform a lot of compute in a future without yielding, is executed on a blocking thread via tokio::task::spawn_blocking. Set this option to true will override this behaviour and allow blocking call to happen. It helps to improve the performance when validating request-data (header, path, query, body) is low cost.| |false|
+|disableValidator|Disable validating request-data (header, path, query, body) against OpenAPI Schema Specification.| |false|
+|packageName|Rust crate name (convention: snake_case).| |openapi|
+|packageVersion|Rust crate version.| |null|
+
+## IMPORT MAPPING
+
+| Type/Alias | Imports |
+| ---------- | ------- |
+
+
+## INSTANTIATION TYPES
+
+| Type/Alias | Instantiated By |
+| ---------- | --------------- |
+|array|Vec|
+|map|std::collections::HashMap|
+
+
+## LANGUAGE PRIMITIVES
+
+
+- String
+- bool
+- char
+- f32
+- f64
+- i16
+- i32
+- i64
+- i8
+- isize
+- str
+- u16
+- u32
+- u64
+- u8
+- usize
+
+
+## RESERVED WORDS
+
+
+- Self
+- abstract
+- as
+- async
+- await
+- become
+- box
+- break
+- const
+- continue
+- crate
+- do
+- dyn
+- else
+- enum
+- extern
+- false
+- final
+- fn
+- for
+- if
+- impl
+- in
+- let
+- loop
+- macro
+- match
+- mod
+- move
+- mut
+- override
+- priv
+- pub
+- ref
+- return
+- self
+- static
+- struct
+- super
+- trait
+- true
+- try
+- type
+- typeof
+- unsafe
+- unsized
+- use
+- virtual
+- where
+- while
+- yield
+
+
+## FEATURE SET
+
+
+### Client Modification Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasePath|✗|ToolingExtension
+|Authorizations|✗|ToolingExtension
+|UserAgent|✗|ToolingExtension
+|MockServer|✗|ToolingExtension
+
+### Data Type Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Custom|✗|OAS2,OAS3
+|Int32|✓|OAS2,OAS3
+|Int64|✓|OAS2,OAS3
+|Float|✓|OAS2,OAS3
+|Double|✓|OAS2,OAS3
+|Decimal|✓|ToolingExtension
+|String|✓|OAS2,OAS3
+|Byte|✓|OAS2,OAS3
+|Binary|✓|OAS2,OAS3
+|Boolean|✓|OAS2,OAS3
+|Date|✓|OAS2,OAS3
+|DateTime|✓|OAS2,OAS3
+|Password|✓|OAS2,OAS3
+|File|✓|OAS2
+|Uuid|✗|
+|Array|✓|OAS2,OAS3
+|Null|✗|OAS3
+|AnyType|✗|OAS2,OAS3
+|Object|✓|OAS2,OAS3
+|Maps|✓|ToolingExtension
+|CollectionFormat|✓|OAS2
+|CollectionFormatMulti|✓|OAS2
+|Enum|✓|OAS2,OAS3
+|ArrayOfEnum|✓|ToolingExtension
+|ArrayOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
+|ArrayOfCollectionOfModel|✓|ToolingExtension
+|ArrayOfCollectionOfEnum|✓|ToolingExtension
+|MapOfEnum|✓|ToolingExtension
+|MapOfModel|✓|ToolingExtension
+|MapOfCollectionOfPrimitives|✓|ToolingExtension
+|MapOfCollectionOfModel|✓|ToolingExtension
+|MapOfCollectionOfEnum|✓|ToolingExtension
+
+### Documentation Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Readme|✗|ToolingExtension
+|Model|✓|ToolingExtension
+|Api|✓|ToolingExtension
+
+### Global Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Host|✓|OAS2,OAS3
+|BasePath|✓|OAS2,OAS3
+|Info|✗|OAS2,OAS3
+|Schemes|✗|OAS2,OAS3
+|PartialSchemes|✓|OAS2,OAS3
+|Consumes|✓|OAS2
+|Produces|✓|OAS2
+|ExternalDocumentation|✗|OAS2,OAS3
+|Examples|✗|OAS2,OAS3
+|XMLStructureDefinitions|✗|OAS2,OAS3
+|MultiServer|✗|OAS3
+|ParameterizedServer|✗|OAS3
+|ParameterStyling|✗|OAS3
+|Callbacks|✗|OAS3
+|LinkObjects|✗|OAS3
+
+### Parameter Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Path|✓|OAS2,OAS3
+|Query|✓|OAS2,OAS3
+|Header|✓|OAS2,OAS3
+|Body|✓|OAS2
+|FormUnencoded|✓|OAS2
+|FormMultipart|✓|OAS2
+|Cookie|✗|OAS3
+
+### Schema Support Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|Simple|✓|OAS2,OAS3
+|Composite|✓|OAS2,OAS3
+|Polymorphism|✗|OAS2,OAS3
+|Union|✗|OAS3
+|allOf|✗|OAS2,OAS3
+|anyOf|✗|OAS3
+|oneOf|✗|OAS3
+|not|✗|OAS3
+
+### Security Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|BasicAuth|✓|OAS2,OAS3
+|ApiKey|✓|OAS2,OAS3
+|OpenIDConnect|✗|OAS3
+|BearerToken|✓|OAS3
+|OAuth2_Implicit|✓|OAS2,OAS3
+|OAuth2_Password|✓|OAS2,OAS3
+|OAuth2_ClientCredentials|✓|OAS2,OAS3
+|OAuth2_AuthorizationCode|✓|OAS2,OAS3
+|SignatureAuth|✗|OAS3
+|AWSV4Signature|✗|ToolingExtension
+
+### Wire Format Feature
+| Name | Supported | Defined By |
+| ---- | --------- | ---------- |
+|JSON|✓|OAS2,OAS3
+|XML|✗|OAS2,OAS3
+|PROTOBUF|✗|ToolingExtension
+|Custom|✓|OAS2,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
new file mode 100644
index 00000000000..342b1ae6652
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustAxumServerCodegen.java
@@ -0,0 +1,1288 @@
+package org.openapitools.codegen.languages;
+
+import com.samskivert.mustache.Mustache;
+import io.swagger.v3.core.util.Json;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.Operation;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.FileSchema;
+import io.swagger.v3.oas.models.media.Schema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.parameters.RequestBody;
+import io.swagger.v3.oas.models.responses.ApiResponse;
+import io.swagger.v3.oas.models.servers.Server;
+import joptsimple.internal.Strings;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.openapitools.codegen.*;
+import org.openapitools.codegen.meta.GeneratorMetadata;
+import org.openapitools.codegen.meta.Stability;
+import org.openapitools.codegen.meta.features.GlobalFeature;
+import org.openapitools.codegen.meta.features.ParameterFeature;
+import org.openapitools.codegen.meta.features.SchemaSupportFeature;
+import org.openapitools.codegen.meta.features.WireFormatFeature;
+import org.openapitools.codegen.model.ModelMap;
+import org.openapitools.codegen.model.ModelsMap;
+import org.openapitools.codegen.model.OperationMap;
+import org.openapitools.codegen.model.OperationsMap;
+import org.openapitools.codegen.utils.ModelUtils;
+import org.openapitools.codegen.utils.URLPathUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.URL;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static org.openapitools.codegen.utils.StringUtils.camelize;
+import static org.openapitools.codegen.utils.StringUtils.underscore;
+
+public class RustAxumServerCodegen extends AbstractRustCodegen implements CodegenConfig {
+ public static final String PROJECT_NAME = "openapi-server";
+ // Types
+ private static final String uuidType = "uuid::Uuid";
+ private static final String bytesType = "ByteArray";
+ private static final String octetMimeType = "application/octet-stream";
+ private static final String plainTextMimeType = "text/plain";
+
+ private static final String xmlMimeType = "application/xml";
+ private static final String textXmlMimeType = "text/xml";
+
+ private static final String formUrlEncodedMimeType = "application/x-www-form-urlencoded";
+
+ private static final String jsonMimeType = "application/json";
+ // RFC 7386 support
+ private static final String mergePatchJsonMimeType = "application/merge-patch+json";
+ // RFC 7807 Support
+ private static final String problemJsonMimeType = "application/problem+json";
+ private static final String problemXmlMimeType = "application/problem+xml";
+
+ private final Logger LOGGER = LoggerFactory.getLogger(RustAxumServerCodegen.class);
+ // Grouping (Method, Operation) by Path.
+ private final Map> pathMethodOpMap = new HashMap<>();
+
+ protected String apiVersion = "1.0.0";
+ protected String apiPath = "rust-axum";
+ protected String packageName;
+ protected String packageVersion;
+ protected Boolean disableValidator = false;
+ protected Boolean allowBlockingValidator = false;
+ protected Boolean allowBlockingResponseSerialize = false;
+ protected String externCrateName;
+ protected int serverPort = 8080;
+
+ public RustAxumServerCodegen() {
+ super();
+
+ modifyFeatureSet(features -> features
+ .wireFormatFeatures(EnumSet.of(
+ WireFormatFeature.JSON,
+ WireFormatFeature.Custom
+ ))
+ .excludeGlobalFeatures(
+ GlobalFeature.Info,
+ GlobalFeature.ExternalDocumentation,
+ GlobalFeature.Examples,
+ GlobalFeature.XMLStructureDefinitions,
+ GlobalFeature.MultiServer,
+ GlobalFeature.ParameterizedServer,
+ GlobalFeature.ParameterStyling,
+ GlobalFeature.Callbacks,
+ GlobalFeature.LinkObjects
+ )
+ .excludeSchemaSupportFeatures(
+ SchemaSupportFeature.Polymorphism
+ )
+ .excludeParameterFeatures(
+ ParameterFeature.Cookie
+ )
+ );
+
+ generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
+ .stability(Stability.BETA)
+ .build();
+
+ // Show the generation timestamp by default
+ hideGenerationTimestamp = Boolean.FALSE;
+
+ // set the output folder here
+ outputFolder = "generated-code" + File.separator + "rust-axum";
+
+ /*
+ * Models. You can write model files using the modelTemplateFiles map.
+ * if you want to create one template for file, you can do so here.
+ * for multiple files for model, just put another entry in the `modelTemplateFiles` with
+ * a different extension
+ */
+ modelTemplateFiles.clear();
+
+ /*
+ * Api classes. You can write classes for each Api file with the apiTemplateFiles map.
+ * as with models, add multiple entries with different extensions for multiple files per
+ * class
+ */
+ apiTemplateFiles.clear();
+
+ /*
+ * Template Location. This is the location which templates will be read from. The generator
+ * will use the resource stream to attempt to read the templates.
+ */
+ embeddedTemplateDir = templateDir = "rust-axum";
+
+ defaultIncludes = new HashSet<>(
+ Arrays.asList(
+ "map",
+ "array")
+ );
+
+ languageSpecificPrimitives = new HashSet<>(
+ Arrays.asList(
+ "bool",
+ "char",
+ "i8",
+ "i16",
+ "i32",
+ "i64",
+ "u8",
+ "u16",
+ "u32",
+ "u64",
+ "isize",
+ "usize",
+ "f32",
+ "f64",
+ "str",
+ "String")
+ );
+
+ instantiationTypes.clear();
+ instantiationTypes.put("array", "Vec");
+ instantiationTypes.put("map", "std::collections::HashMap");
+
+ typeMapping.clear();
+ typeMapping.put("number", "f64");
+ typeMapping.put("integer", "i32");
+ typeMapping.put("long", "i64");
+ typeMapping.put("float", "f32");
+ typeMapping.put("double", "f64");
+ typeMapping.put("string", "String");
+ typeMapping.put("UUID", uuidType);
+ typeMapping.put("URI", "String");
+ typeMapping.put("byte", "u8");
+ typeMapping.put("ByteArray", bytesType);
+ typeMapping.put("binary", bytesType);
+ typeMapping.put("boolean", "bool");
+ typeMapping.put("date", "chrono::naive::NaiveDate");
+ typeMapping.put("DateTime", "chrono::DateTime::");
+ typeMapping.put("password", "String");
+ typeMapping.put("File", "ByteArray");
+ typeMapping.put("file", "ByteArray");
+ typeMapping.put("array", "Vec");
+ typeMapping.put("map", "std::collections::HashMap");
+ typeMapping.put("object", "crate::types::Object");
+ typeMapping.put("AnyType", "crate::types::Object");
+
+ importMapping = new HashMap<>();
+
+ cliOptions.clear();
+ cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME,
+ "Rust crate name (convention: snake_case).")
+ .defaultValue("openapi"));
+ cliOptions.add(new CliOption(CodegenConstants.PACKAGE_VERSION,
+ "Rust crate version."));
+
+ CliOption optDisableValidator = new CliOption("disableValidator", "Disable validating request-data (header, path, query, body) " +
+ "against OpenAPI Schema Specification.");
+ optDisableValidator.setType("bool");
+ optDisableValidator.defaultValue(disableValidator.toString());
+ cliOptions.add(optDisableValidator);
+
+ CliOption optAllowBlockingValidator = new CliOption("allowBlockingValidator", "By default, validation process, which might perform a lot of compute in a " +
+ "future without yielding, is executed on a blocking thread via tokio::task::spawn_blocking. Set this option to true will override this behaviour and allow blocking " +
+ "call to happen. It helps to improve the performance when validating request-data (header, path, query, body) is low cost.");
+ optAllowBlockingValidator.setType("bool");
+ optAllowBlockingValidator.defaultValue(allowBlockingValidator.toString());
+ cliOptions.add(optAllowBlockingValidator);
+
+ CliOption optAllowBlockingResponseSerialize = new CliOption("allowBlockingResponseSerialize", "By default, json/form-urlencoded response serialization, which might " +
+ "perform a lot of compute in a future without yielding, is executed on a blocking thread via tokio::task::spawn_blocking. Set this option to true will override this behaviour and allow blocking " +
+ "call to happen. It helps to improve the performance when response serialization (e.g. returns tiny data) is low cost.");
+ optAllowBlockingResponseSerialize.setType("bool");
+ optAllowBlockingResponseSerialize.defaultValue(allowBlockingResponseSerialize.toString());
+ cliOptions.add(optAllowBlockingResponseSerialize);
+
+ /*
+ * Additional Properties. These values can be passed to the templates and
+ * are available in models, apis, and supporting files
+ */
+ additionalProperties.put("apiVersion", apiVersion);
+ additionalProperties.put("apiPath", apiPath);
+
+ /*
+ * Supporting Files. You can write single files for the generator with the
+ * entire object tree available. If the input file has a suffix of `.mustache
+ * it will be processed by the template engine. Otherwise, it will be copied
+ */
+ supportingFiles.add(new SupportingFile("Cargo.mustache", "", "Cargo.toml"));
+ supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore"));
+ supportingFiles.add(new SupportingFile("lib.mustache", "src", "lib.rs"));
+ supportingFiles.add(new SupportingFile("models.mustache", "src", "models.rs"));
+ supportingFiles.add(new SupportingFile("types.mustache", "src", "types.rs"));
+ supportingFiles.add(new SupportingFile("header.mustache", "src", "header.rs"));
+ supportingFiles.add(new SupportingFile("server-mod.mustache", "src/server", "mod.rs"));
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
+ .doNotOverwrite());
+ }
+
+ public CodegenType getTag() {
+ return CodegenType.SERVER;
+ }
+
+ public String getName() {
+ return "rust-axum";
+ }
+
+ public String getHelp() {
+ return "Generates a Rust server library which bases on Axum.";
+ }
+
+ @Override
+ public Mustache.Compiler processCompiler(Mustache.Compiler compiler) {
+ return super.processCompiler(compiler).emptyStringIsFalse(true).zeroIsFalse(true);
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ if (StringUtils.isEmpty(System.getenv("RUST_POST_PROCESS_FILE"))) {
+ LOGGER.info("Environment variable RUST_POST_PROCESS_FILE not defined. rustfmt will be used" +
+ " by default. To choose a different tool, try" +
+ " 'export RUST_POST_PROCESS_FILE=\"/usr/local/bin/rustfmt\"' (Linux/Mac)");
+ LOGGER.info("NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` " +
+ " (--enable-post-process-file for CLI).");
+ }
+
+ if (!Boolean.TRUE.equals(ModelUtils.isGenerateAliasAsModel())) {
+ LOGGER.warn("generateAliasAsModel is set to false, which means array/map will be generated as model instead and the resulting code may have issues. Please enable `generateAliasAsModel` to address the issue.");
+ }
+
+ setPackageName((String) additionalProperties.getOrDefault(CodegenConstants.PACKAGE_NAME, "openapi"));
+
+ if (additionalProperties.containsKey(CodegenConstants.PACKAGE_VERSION)) {
+ setPackageVersion((String) additionalProperties.get(CodegenConstants.PACKAGE_VERSION));
+ }
+
+ additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
+ additionalProperties.put("externCrateName", externCrateName);
+
+ if (additionalProperties.containsKey("disableValidator")) {
+ disableValidator = convertPropertyToBooleanAndWriteBack("disableValidator");
+ } else {
+ additionalProperties.put("disableValidator", disableValidator);
+ }
+
+ if (additionalProperties.containsKey("allowBlockingValidator")) {
+ allowBlockingValidator = convertPropertyToBooleanAndWriteBack("allowBlockingValidator");
+ } else {
+ additionalProperties.put("allowBlockingValidator", allowBlockingValidator);
+ }
+
+ if (additionalProperties.containsKey("allowBlockingResponseSerialize")) {
+ allowBlockingValidator = convertPropertyToBooleanAndWriteBack("allowBlockingResponseSerialize");
+ } else {
+ additionalProperties.put("allowBlockingResponseSerialize", allowBlockingResponseSerialize);
+ }
+ }
+
+ public void setPackageName(String packageName) {
+ this.packageName = packageName;
+
+ // Also set the extern crate name, which has any '-' replace with a '_'.
+ this.externCrateName = packageName.replace('-', '_');
+ }
+
+ public void setPackageVersion(String packageVersion) {
+ this.packageVersion = packageVersion;
+ }
+
+ @Override
+ public String apiPackage() {
+ return apiPath;
+ }
+
+ @Override
+ public void preprocessOpenAPI(OpenAPI openAPI) {
+ Info info = openAPI.getInfo();
+
+ URL url = URLPathUtils.getServerURL(openAPI, serverVariableOverrides());
+ additionalProperties.put("serverHost", url.getHost());
+ additionalProperties.put("serverPort", URLPathUtils.getPort(url, serverPort));
+
+ if (packageVersion == null || packageVersion.isEmpty()) {
+ List versionComponents = new ArrayList<>(Arrays.asList(info.getVersion().split("[.]")));
+ if (versionComponents.isEmpty()) {
+ versionComponents.add("1");
+ }
+ while (versionComponents.size() < 3) {
+ versionComponents.add("0");
+ }
+
+ setPackageVersion(StringUtils.join(versionComponents, "."));
+ }
+
+ additionalProperties.put(CodegenConstants.PACKAGE_VERSION, packageVersion);
+ }
+
+ @Override
+ public String toApiName(String name) {
+ if (name.isEmpty()) {
+ return "default";
+ }
+ return sanitizeIdentifier(name, CasingType.SNAKE_CASE, "api", "API", true);
+ }
+
+ /**
+ * Location to write api files. You can use the apiPackage() as defined when the class is
+ * instantiated
+ */
+ @Override
+ public String apiFileFolder() {
+ return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
+ }
+
+ @Override
+ public String toOperationId(String operationId) {
+ // rust-axum uses camel case instead
+ return sanitizeIdentifier(operationId, CasingType.CAMEL_CASE, "call", "method", true);
+ }
+
+ @Override
+ public String toEnumValue(String value, String datatype) {
+ // rust-axum templates expect value to be in quotes
+ return "\"" + super.toEnumValue(value, datatype) + "\"";
+ }
+
+ private boolean isMimetypeXml(String mimetype) {
+ return mimetype.toLowerCase(Locale.ROOT).startsWith(xmlMimeType) ||
+ mimetype.toLowerCase(Locale.ROOT).startsWith(problemXmlMimeType) ||
+ mimetype.toLowerCase(Locale.ROOT).startsWith(textXmlMimeType);
+ }
+
+ private boolean isMimetypeJson(String mimetype) {
+ return mimetype.toLowerCase(Locale.ROOT).startsWith(jsonMimeType) ||
+ mimetype.toLowerCase(Locale.ROOT).startsWith(mergePatchJsonMimeType) ||
+ mimetype.toLowerCase(Locale.ROOT).startsWith(problemJsonMimeType);
+ }
+
+ private boolean isMimetypeWwwFormUrlEncoded(String mimetype) {
+ return mimetype.toLowerCase(Locale.ROOT).startsWith(formUrlEncodedMimeType);
+ }
+
+ private boolean isMimetypeMultipartFormData(String mimetype) {
+ return mimetype.toLowerCase(Locale.ROOT).startsWith("multipart/form-data");
+ }
+
+ private boolean isMimetypeMultipartRelated(String mimetype) {
+ return mimetype.toLowerCase(Locale.ROOT).startsWith("multipart/related");
+ }
+
+ private boolean isMimetypeUnknown(String mimetype) {
+ return "*/*".equals(mimetype);
+ }
+
+ boolean isMimetypePlain(String mimetype) {
+ return !(isMimetypeUnknown(mimetype) ||
+ isMimetypeJson(mimetype) ||
+ isMimetypeWwwFormUrlEncoded(mimetype) ||
+ isMimetypeMultipartFormData(mimetype) ||
+ isMimetypeMultipartRelated(mimetype));
+ }
+
+ @Override
+ public CodegenOperation fromOperation(String path, String httpMethod, Operation operation, List servers) {
+ CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
+
+ String pathFormatString = op.path;
+ for (CodegenParameter param : op.pathParams) {
+ // Replace {baseName} with {paramName} for format string
+ String paramSearch = "{" + param.baseName + "}";
+ String paramReplace = "{" + param.paramName + "}";
+
+ pathFormatString = pathFormatString.replace(paramSearch, paramReplace);
+ }
+ op.vendorExtensions.put("x-path-format-string", pathFormatString);
+
+ boolean hasPathParams = !op.pathParams.isEmpty();
+
+ // String for formatting the path for a client to make a request
+ String formatPath = op.path;
+
+ for (CodegenParameter param : op.pathParams) {
+ // Replace {baseName} with {paramName} for format string
+ String paramSearch = "{" + param.baseName + "}";
+ String paramReplace = "{" + param.paramName + "}";
+
+ formatPath = formatPath.replace(paramSearch, paramReplace);
+ }
+
+ String underscoredOperationId = underscore(op.operationId);
+ op.vendorExtensions.put("x-operation-id", underscoredOperationId);
+ op.vendorExtensions.put("x-uppercase-operation-id", underscoredOperationId.toUpperCase(Locale.ROOT));
+ String vendorExtensionPath = op.path.replace("{", ":").replace("}", "");
+ op.vendorExtensions.put("x-path", vendorExtensionPath);
+ op.vendorExtensions.put("x-has-path-params", hasPathParams);
+ op.vendorExtensions.put("x-path-format-string", formatPath);
+
+ if (!op.isCallbackRequest) {
+ // group route by path
+ String axumPath = op.path;
+ for (CodegenParameter param : op.pathParams) {
+ // Replace {baseName} with {paramName} for format string
+ String paramSearch = "{" + param.baseName + "}";
+ String paramReplace = ":" + param.paramName;
+
+ axumPath = axumPath.replace(paramSearch, paramReplace);
+ }
+ pathMethodOpMap
+ .computeIfAbsent(axumPath, (key) -> new ArrayList<>())
+ .add(new MethodOperation(op.httpMethod.toLowerCase(Locale.ROOT), underscoredOperationId));
+ }
+
+ String vendorExtensionHttpMethod = op.httpMethod.toUpperCase(Locale.ROOT);
+ op.vendorExtensions.put("x-http-method", vendorExtensionHttpMethod);
+
+ if (!op.vendorExtensions.containsKey("x-must-use-response")) {
+ // If there's more than one response, than by default the user must explicitly handle them
+ op.vendorExtensions.put("x-must-use-response", op.responses.size() > 1);
+ }
+
+ for (CodegenParameter param : op.allParams) {
+ processParam(param);
+ }
+
+ // Determine the types that this operation produces. `getProducesInfo`
+ // simply lists all the types, and then we add the correct imports to
+ // the generated library.
+ Set producesInfo = getProducesInfo(openAPI, operation);
+ boolean producesPlainText = false;
+ boolean producesFormUrlEncoded = false;
+ if (producesInfo != null && !producesInfo.isEmpty()) {
+ List produces = new ArrayList<>(producesInfo);
+
+ List