diff --git a/bin/configs/manual/rust-axum-apikey-auths.yaml b/bin/configs/manual/rust-axum-apikey-auths.yaml new file mode 100644 index 00000000000..0b51d6a3b0c --- /dev/null +++ b/bin/configs/manual/rust-axum-apikey-auths.yaml @@ -0,0 +1,11 @@ +generatorName: rust-axum +outputDir: samples/server/petstore/rust-axum/output/apikey-auths +inputSpec: modules/openapi-generator/src/test/resources/3_0/jetbrains/CheckoutBasicBearerCookieQueryHeaderBasicBearer.yaml +templateDir: modules/openapi-generator/src/main/resources/rust-axum +generateAliasAsModel: true +additionalProperties: + hideGenerationTimestamp: "true" + packageName: apikey-auths +globalProperties: + skipFormModel: false +enablePostProcessFile: true diff --git a/docs/generators/rust-axum.md b/docs/generators/rust-axum.md index 188ecaf27ad..a84f3fc1f6d 100644 --- a/docs/generators/rust-axum.md +++ b/docs/generators/rust-axum.md @@ -198,7 +198,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |Body|✓|OAS2 |FormUnencoded|✓|OAS2 |FormMultipart|✓|OAS2 -|Cookie|✗|OAS3 +|Cookie|✓|OAS3 ### Schema Support Feature | Name | Supported | Defined By | @@ -215,14 +215,14 @@ These options may be applied as additional-properties (cli) or configOptions (pl ### Security Feature | Name | Supported | Defined By | | ---- | --------- | ---------- | -|BasicAuth|✓|OAS2,OAS3 +|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 +|BearerToken|✗|OAS3 +|OAuth2_Implicit|✗|OAS2,OAS3 +|OAuth2_Password|✗|OAS2,OAS3 +|OAuth2_ClientCredentials|✗|OAS2,OAS3 +|OAuth2_AuthorizationCode|✗|OAS2,OAS3 |SignatureAuth|✗|OAS3 |AWSV4Signature|✗|ToolingExtension 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 440ee6e64f2..aca34febec8 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 @@ -25,15 +25,13 @@ 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 io.swagger.v3.oas.models.tags.Tag; 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.meta.features.*; import org.openapitools.codegen.model.ModelMap; import org.openapitools.codegen.model.ModelsMap; import org.openapitools.codegen.model.OperationMap; @@ -53,7 +51,6 @@ import static org.openapitools.codegen.utils.StringUtils.underscore; public class RustAxumServerCodegen extends AbstractRustCodegen implements CodegenConfig { public static final String PROJECT_NAME = "openapi-server"; - private static final String apiPath = "rust-axum"; private String packageName; private String packageVersion; @@ -99,6 +96,9 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege WireFormatFeature.JSON, WireFormatFeature.Custom )) + .securityFeatures(EnumSet.of( + SecurityFeature.ApiKey + )) .excludeGlobalFeatures( GlobalFeature.Info, GlobalFeature.ExternalDocumentation, @@ -113,9 +113,6 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege .excludeSchemaSupportFeatures( SchemaSupportFeature.Polymorphism ) - .excludeParameterFeatures( - ParameterFeature.Cookie - ) ); generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata) @@ -436,7 +433,10 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege } pathMethodOpMap .computeIfAbsent(axumPath, (key) -> new ArrayList<>()) - .add(new MethodOperation(op.httpMethod.toLowerCase(Locale.ROOT), underscoredOperationId)); + .add(new MethodOperation( + op.httpMethod.toLowerCase(Locale.ROOT), + underscoredOperationId, + op.vendorExtensions)); } // Determine the types that this operation produces. `getProducesInfo` @@ -488,7 +488,7 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege ); rsp.vendorExtensions.put("x-response-id", responseId); } - + if (rsp.dataType != null) { // Get the mimetype which is produced by this response. Note // that although in general responses produces a set of @@ -562,19 +562,39 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege } } } + + for (CodegenProperty header : rsp.headers) { + if (uuidType.equals(header.dataType)) { + additionalProperties.put("apiUsesUuid", true); + } + header.nameInPascalCase = toModelName(header.baseName); + header.nameInLowerCase = header.baseName.toLowerCase(Locale.ROOT); + } } - // Include renderUuidConversionImpl exactly once in the vendorExtensions map when - // at least one `uuid::Uuid` converted from a header value in the resulting Rust code. - final Boolean renderUuidConversionImpl = op.headerParams.stream().anyMatch(h -> h.getDataType().equals(uuidType)); - if (renderUuidConversionImpl) { + for (CodegenParameter header : op.headerParams) { + header.nameInLowerCase = header.baseName.toLowerCase(Locale.ROOT); + } + + for (CodegenProperty header : op.responseHeaders) { + if (uuidType.equals(header.dataType)) { + additionalProperties.put("apiUsesUuid", true); + } + header.nameInPascalCase = toModelName(header.baseName); + header.nameInLowerCase = header.baseName.toLowerCase(Locale.ROOT); + } + + // Include renderUuidConversionImpl exactly once in the vendorExtensions map when + // at least one `uuid::Uuid` converted from a header value in the resulting Rust code. + final boolean renderUuidConversionImpl = op.headerParams.stream().anyMatch(h -> h.getDataType().equals(uuidType)); + if (renderUuidConversionImpl) additionalProperties.put("renderUuidConversionImpl", "true"); - } + return op; } @Override - public OperationsMap postProcessOperationsWithModels(OperationsMap operationsMap, List allModels) { + public OperationsMap postProcessOperationsWithModels(final OperationsMap operationsMap, List allModels) { OperationMap operations = operationsMap.getOperations(); operations.put("classnamePascalCase", camelize(operations.getClassname())); List operationList = operations.getOperation(); @@ -586,7 +606,7 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege return operationsMap; } - private void postProcessOperationWithModels(CodegenOperation op) { + private void postProcessOperationWithModels(final CodegenOperation op) { boolean consumesJson = false; boolean consumesPlainText = false; boolean consumesFormUrlEncoded = false; @@ -641,6 +661,22 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege param.vendorExtensions.put("x-consumes-json", true); } } + + if (op.authMethods != null) { + for (CodegenSecurity s : op.authMethods) { + if (s.isApiKey && (s.isKeyInCookie || s.isKeyInHeader)) { + if (s.isKeyInCookie) { + op.vendorExtensions.put("x-has-cookie-auth-methods", "true"); + op.vendorExtensions.put("x-api-key-cookie-name", toModelName(s.keyParamName)); + } else { + op.vendorExtensions.put("x-has-header-auth-methods", "true"); + op.vendorExtensions.put("x-api-key-header-name", toModelName(s.keyParamName)); + } + + op.vendorExtensions.put("x-has-auth-methods", "true"); + } + } + } } @Override @@ -671,14 +707,16 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege return; } - // Get all tags sorted by name - final List tags = op.tags.stream().map(t -> t.getName()).sorted().collect(Collectors.toList()); - // Combine into a single group - final String combinedTag = tags.stream().collect(Collectors.joining("-")); + // Get all tags sorted by name & Combine into a single group + final String combinedTag = op.tags.stream() + .map(Tag::getName).sorted() + .collect(Collectors.joining("-")); // Add to group super.addOperationToGroup(combinedTag, resourcePath, operation, op, operations); + return; } + super.addOperationToGroup(tag, resourcePath, operation, op, operations); } @@ -692,7 +730,7 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege // restore things to sensible values. @Override public CodegenParameter fromRequestBody(RequestBody body, Set imports, String bodyParameterName) { - Schema original_schema = ModelUtils.getSchemaFromRequestBody(body); + final Schema original_schema = ModelUtils.getSchemaFromRequestBody(body); CodegenParameter codegenParameter = super.fromRequestBody(body, imports, bodyParameterName); if (StringUtils.isNotBlank(original_schema.get$ref())) { @@ -709,12 +747,12 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege } @Override - public String toInstantiationType(Schema p) { + public String toInstantiationType(final Schema p) { if (ModelUtils.isArraySchema(p)) { - Schema inner = ModelUtils.getSchemaItems(p); + final Schema inner = ModelUtils.getSchemaItems(p); return instantiationTypes.get("array") + "<" + getSchemaType(inner) + ">"; } else if (ModelUtils.isMapSchema(p)) { - Schema inner = ModelUtils.getAdditionalProperties(p); + final Schema inner = ModelUtils.getAdditionalProperties(p); return instantiationTypes.get("map") + "<" + typeMapping.get("string") + ", " + getSchemaType(inner) + ">"; } else { return null; @@ -727,7 +765,7 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege final List pathMethodOps = pathMethodOpMap.entrySet().stream() .map(entry -> { - ArrayList methodOps = entry.getValue(); + final ArrayList methodOps = entry.getValue(); methodOps.sort(Comparator.comparing(a -> a.method)); return new PathMethodOperations(entry.getKey(), methodOps); }) @@ -739,7 +777,7 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege } @Override - public String toDefaultValue(Schema p) { + public String toDefaultValue(final Schema p) { String defaultValue = null; if ((ModelUtils.isNullable(p)) && (p.getDefault() != null) && ("null".equalsIgnoreCase(p.getDefault().toString()))) return "Nullable::Null"; @@ -899,7 +937,7 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege } @Override - protected void updatePropertyForAnyType(CodegenProperty property, Schema p) { + protected void updatePropertyForAnyType(final CodegenProperty property, final Schema p) { // The 'null' value is allowed when the OAS schema is 'any type'. // See https://github.com/OAI/OpenAPI-Specification/issues/1389 if (Boolean.FALSE.equals(p.getNullable())) { @@ -917,7 +955,7 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege } @Override - protected String getParameterDataType(Parameter parameter, Schema schema) { + protected String getParameterDataType(final Parameter parameter, final Schema schema) { if (parameter.get$ref() != null) { String refName = ModelUtils.getSimpleRef(parameter.get$ref()); return toModelName(refName); @@ -938,10 +976,12 @@ public class RustAxumServerCodegen extends AbstractRustCodegen implements Codege static class MethodOperation { public String method; public String operationID; + public Map vendorExtensions; - MethodOperation(String method, String operationID) { + MethodOperation(String method, String operationID, Map vendorExtensions) { this.method = method; this.operationID = operationID; + this.vendorExtensions = vendorExtensions; } } } diff --git a/modules/openapi-generator/src/main/resources/rust-axum/apis-mod.mustache b/modules/openapi-generator/src/main/resources/rust-axum/apis-mod.mustache index c8d342c2528..d7fe525d964 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/apis-mod.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/apis-mod.mustache @@ -4,3 +4,19 @@ pub mod {{classFilename}}; {{/apis}} {{/apiInfo}} +{{#authMethods}} +{{#isApiKey}} +{{#isKeyInCookie}} +/// Cookie Authentication. +pub trait CookieAuthentication { + fn extract_token_from_cookie(&self, cookies: &axum_extra::extract::CookieJar, key: &str) -> Option; +} +{{/isKeyInCookie}} +{{#isKeyInHeader}} +/// API Key Authentication - Header. +pub trait ApiKeyAuthHeader { + fn extract_token_from_header(&self, headers: &axum::http::header::HeaderMap, key: &str) -> Option; +} +{{/isKeyInHeader}} +{{/isApiKey}} +{{/authMethods}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/rust-axum/apis.mustache b/modules/openapi-generator/src/main/resources/rust-axum/apis.mustache index 5f3253ac5c4..a44f21379bf 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/apis.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/apis.mustache @@ -30,6 +30,14 @@ pub trait {{classnamePascalCase}} { method: Method, host: Host, cookies: CookieJar, + {{#vendorExtensions}} + {{#x-has-cookie-auth-methods}} + token_in_cookie: Option, + {{/x-has-cookie-auth-methods}} + {{#x-has-header-auth-methods}} + token_in_header: Option, + {{/x-has-header-auth-methods}} + {{/vendorExtensions}} {{#headerParams.size}} header_params: models::{{{operationIdCamelCase}}}HeaderParams, {{/headerParams.size}} diff --git a/modules/openapi-generator/src/main/resources/rust-axum/server-operation.mustache b/modules/openapi-generator/src/main/resources/rust-axum/server-operation.mustache index 959f09f174f..48532ff6ecb 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/server-operation.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/server-operation.mustache @@ -7,6 +7,13 @@ async fn {{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}( {{#headerParams.size}} headers: HeaderMap, {{/headerParams.size}} +{{^headerParams.size}} +{{#vendorExtensions}} +{{#x-has-header-auth-methods}} + headers: HeaderMap, +{{/x-has-header-auth-methods}} +{{/vendorExtensions}} +{{/headerParams.size}} {{#pathParams.size}} Path(path_params): Path, {{/pathParams.size}} @@ -47,14 +54,34 @@ async fn {{#vendorExtensions}}{{{x-operation-id}}}{{/vendorExtensions}}( ) -> Result where I: AsRef + Send + Sync, - A: apis::{{classFilename}}::{{classnamePascalCase}}, + A: apis::{{classFilename}}::{{classnamePascalCase}}{{#vendorExtensions}}{{#x-has-cookie-auth-methods}}+ apis::CookieAuthentication{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}+ apis::ApiKeyAuthHeader{{/x-has-header-auth-methods}}{{/vendorExtensions}}, { +{{#vendorExtensions}} +{{#x-has-auth-methods}} + // Authentication +{{/x-has-auth-methods}} +{{#x-has-cookie-auth-methods}} + let token_in_cookie = api_impl.as_ref().extract_token_from_cookie(&cookies, "{{x-api-key-cookie-name}}"); +{{/x-has-cookie-auth-methods}} +{{#x-has-header-auth-methods}} + let token_in_header = api_impl.as_ref().extract_token_from_header(&headers, "{{x-api-key-header-name}}"); +{{/x-has-header-auth-methods}} +{{#x-has-auth-methods}} + if let ({{#x-has-cookie-auth-methods}}None,{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}None,{{/x-has-header-auth-methods}}) = ({{#x-has-cookie-auth-methods}}&token_in_cookie,{{/x-has-cookie-auth-methods}}{{#x-has-header-auth-methods}}&token_in_header,{{/x-has-header-auth-methods}}) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } +{{/x-has-auth-methods}} +{{/vendorExtensions}} + {{#headerParams}} {{#-first}} // Header parameters let header_params = { {{/-first}} - let header_{{{paramName}}} = headers.get(HeaderName::from_static("{{{nameInLowerCase}}}")); + let header_{{{paramName}}} = headers.get(HeaderName::from_static("{{{baseName}}}")); let header_{{{paramName}}} = match header_{{{paramName}}} { Some(v) => match header::IntoHeaderValue::<{{{dataType}}}>::try_from((*v).clone()) { @@ -154,6 +181,14 @@ where method, host, cookies, + {{#vendorExtensions}} + {{#x-has-cookie-auth-methods}} + token_in_cookie, + {{/x-has-cookie-auth-methods}} + {{#x-has-header-auth-methods}} + token_in_header, + {{/x-has-header-auth-methods}} + {{/vendorExtensions}} {{#headerParams.size}} header_params, {{/headerParams.size}} @@ -232,7 +267,7 @@ where { let mut response_headers = response.headers_mut().unwrap(); response_headers.insert( - HeaderName::from_static("{{{nameInLowerCase}}}"), + HeaderName::from_static("{{{baseName}}}"), {{name}} ); } diff --git a/modules/openapi-generator/src/main/resources/rust-axum/server-route.mustache b/modules/openapi-generator/src/main/resources/rust-axum/server-route.mustache index ab5204cc4ff..ca87c99b8fd 100644 --- a/modules/openapi-generator/src/main/resources/rust-axum/server-route.mustache +++ b/modules/openapi-generator/src/main/resources/rust-axum/server-route.mustache @@ -2,7 +2,7 @@ pub fn new(api_impl: I) -> Router where I: AsRef + Clone + Send + Sync + 'static, - A: {{#apiInfo}}{{#apis}}{{#operations}}apis::{{classFilename}}::{{classnamePascalCase}} + {{/operations}}{{/apis}}{{/apiInfo}}'static, + A: {{#apiInfo}}{{#apis}}{{#operations}}apis::{{classFilename}}::{{classnamePascalCase}} + {{/operations}}{{/apis}}{{/apiInfo}}{{#authMethods}}{{#isApiKey}}{{#isKeyInCookie}}apis::CookieAuthentication + {{/isKeyInCookie}}{{#isKeyInHeader}}apis::ApiKeyAuthHeader + {{/isKeyInHeader}}{{/isApiKey}}{{/authMethods}}'static, { // build our application with a route Router::new() diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/.gitignore b/samples/server/petstore/rust-axum/output/apikey-auths/.gitignore new file mode 100644 index 00000000000..a9d37c560c6 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator-ignore b/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/.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/apikey-auths/.openapi-generator/FILES b/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/FILES new file mode 100644 index 00000000000..282f205d116 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/FILES @@ -0,0 +1,10 @@ +.gitignore +Cargo.toml +README.md +src/apis/mod.rs +src/apis/payments.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/apikey-auths/.openapi-generator/VERSION b/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/VERSION new file mode 100644 index 00000000000..6935482704c --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/.openapi-generator/VERSION @@ -0,0 +1 @@ +7.10.0-SNAPSHOT diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml b/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml new file mode 100644 index 00000000000..65fec448b2b --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "apikey-auths" +version = "1.0.0" +authors = ["OpenAPI Generator team and contributors"] +description = "Checkout Basic" +edition = "2021" + +[features] +default = ["server"] +server = [] +conversion = [ + "frunk", + "frunk_derives", + "frunk_core", + "frunk-enum-core", + "frunk-enum-derive", +] + +[dependencies] +async-trait = "0.1" +axum = { version = "0.7" } +axum-extra = { version = "0.9", features = ["cookie", "multipart"] } +base64 = "0.22" +bytes = "1" +chrono = { version = "0.4", features = ["serde"] } +frunk = { version = "0.4", optional = true } +frunk-enum-core = { version = "0.3", optional = true } +frunk-enum-derive = { version = "0.3", optional = true } +frunk_core = { version = "0.4", optional = true } +frunk_derives = { version = "0.4", optional = true } +http = "1" +lazy_static = "1" +regex = "1" +serde = { version = "1", features = ["derive"] } +serde_json = { version = "1", features = ["raw_value"] } +serde_urlencoded = "0.7" +tokio = { version = "1", default-features = false, features = [ + "signal", + "rt-multi-thread", +] } +tracing = { version = "0.1", features = ["attributes"] } +uuid = { version = "1", features = ["serde"] } +validator = { version = "0.18", features = ["derive"] } + +[dev-dependencies] +tracing-subscriber = "0.3" diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/README.md b/samples/server/petstore/rust-axum/output/apikey-auths/README.md new file mode 100644 index 00000000000..3e600343194 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/README.md @@ -0,0 +1,91 @@ +# Rust API for apikey-auths + +Checkout Basic + +## 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.10.0-SNAPSHOT + + + +This autogenerated project defines an API crate `apikey-auths` 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 apikey-auths::Api for ServerImpl { + // API implementation goes here +} + +pub async fn start_server(addr: &str) { + // initialize tracing + tracing_subscriber::fmt::init(); + + // Init Axum router + let app = apikey-auths::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/apikey-auths/src/apis/mod.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/apis/mod.rs new file mode 100644 index 00000000000..62bb105ea9e --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/src/apis/mod.rs @@ -0,0 +1,18 @@ +pub mod payments; + +/// API Key Authentication - Header. +pub trait ApiKeyAuthHeader { + fn extract_token_from_header( + &self, + headers: &axum::http::header::HeaderMap, + key: &str, + ) -> Option; +} +/// Cookie Authentication. +pub trait CookieAuthentication { + fn extract_token_from_cookie( + &self, + cookies: &axum_extra::extract::CookieJar, + key: &str, + ) -> Option; +} diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/src/apis/payments.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/apis/payments.rs new file mode 100644 index 00000000000..4d824e3a9c1 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/src/apis/payments.rs @@ -0,0 +1,74 @@ +use async_trait::async_trait; +use axum::extract::*; +use axum_extra::extract::{CookieJar, Multipart}; +use bytes::Bytes; +use http::Method; +use serde::{Deserialize, Serialize}; + +use crate::{models, types::*}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[must_use] +#[allow(clippy::large_enum_variant)] +pub enum GetPaymentMethodByIdResponse { + /// OK - the request has succeeded. + Status200_OK(models::PaymentMethod), + /// Unprocessable Entity - a request validation error. + Status422_UnprocessableEntity(models::CheckoutError), +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[must_use] +#[allow(clippy::large_enum_variant)] +pub enum GetPaymentMethodsResponse { + /// OK - the request has succeeded. + Status200_OK(Vec), +} + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +#[must_use] +#[allow(clippy::large_enum_variant)] +pub enum PostMakePaymentResponse { + /// OK - the request has succeeded. + Status200_OK(models::PaymentResult), + /// Unprocessable Entity - a request validation error. + Status422_UnprocessableEntity(models::CheckoutError), +} + +/// Payments +#[async_trait] +#[allow(clippy::ptr_arg)] +pub trait Payments { + /// Get payment method by id. + /// + /// GetPaymentMethodById - GET /v71/paymentMethods/{id} + async fn get_payment_method_by_id( + &self, + method: Method, + host: Host, + cookies: CookieJar, + path_params: models::GetPaymentMethodByIdPathParams, + ) -> Result; + + /// Get payment methods. + /// + /// GetPaymentMethods - GET /v71/paymentMethods + async fn get_payment_methods( + &self, + method: Method, + host: Host, + cookies: CookieJar, + ) -> Result; + + /// Make a payment. + /// + /// PostMakePayment - POST /v71/payments + async fn post_make_payment( + &self, + method: Method, + host: Host, + cookies: CookieJar, + token_in_cookie: Option, + body: Option, + ) -> Result; +} diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/src/header.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/header.rs new file mode 100644 index 00000000000..7c530892fbf --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/src/header.rs @@ -0,0 +1,197 @@ +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!( + "Unable to parse {} as a string: {}", + stringify!($t), + e + )), + }, + Err(e) => Err(format!( + "Unable to parse header {:?} as a string - {}", + hdr_value, e + )), + } + } + } + + impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue<$t>) -> Result { + Ok(hdr_value.0.into()) + } + } + }; +} + +ihv_generate!(u64); +ihv_generate!(i64); +ihv_generate!(i16); +ihv_generate!(u16); +ihv_generate!(u32); +ihv_generate!(usize); +ihv_generate!(isize); +ihv_generate!(i32); + +// Custom derivations + +// Vec + +impl TryFrom for IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => Ok(IntoHeaderValue( + hdr_value + .split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y.to_string()), + }) + .collect(), + )), + Err(e) => Err(format!( + "Unable to parse header: {:?} as a string - {}", + hdr_value, e + )), + } + } +} + +impl TryFrom>> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue>) -> Result { + match HeaderValue::from_str(&hdr_value.0.join(", ")) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} into a header - {}", + hdr_value, e + )), + } + } +} + +// String + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value.to_string())), + Err(e) => Err(format!("Unable to convert header {:?} to {}", hdr_value, e)), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_str(&hdr_value.0) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} from a header {}", + hdr_value, e + )), + } + } +} + +// Bool + +impl TryFrom for IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match hdr_value.parse() { + Ok(hdr_value) => Ok(IntoHeaderValue(hdr_value)), + Err(e) => Err(format!("Unable to parse bool from {} - {}", hdr_value, e)), + }, + Err(e) => Err(format!( + "Unable to convert {:?} from a header {}", + hdr_value, e + )), + } + } +} + +impl TryFrom> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue) -> Result { + match HeaderValue::from_str(&hdr_value.0.to_string()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert: {:?} into a header: {}", + hdr_value, e + )), + } + } +} + +// DateTime + +impl TryFrom for IntoHeaderValue> { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> Result { + match hdr_value.to_str() { + Ok(hdr_value) => match DateTime::parse_from_rfc3339(hdr_value) { + Ok(date) => Ok(IntoHeaderValue(date.with_timezone(&Utc))), + Err(e) => Err(format!("Unable to parse: {} as date - {}", hdr_value, e)), + }, + Err(e) => Err(format!( + "Unable to convert header {:?} to string {}", + hdr_value, e + )), + } + } +} + +impl TryFrom>> for HeaderValue { + type Error = String; + + fn try_from(hdr_value: IntoHeaderValue>) -> Result { + match HeaderValue::from_str(hdr_value.0.to_rfc3339().as_str()) { + Ok(hdr_value) => Ok(hdr_value), + Err(e) => Err(format!( + "Unable to convert {:?} to a header: {}", + hdr_value, e + )), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/src/lib.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/lib.rs new file mode 100644 index 00000000000..d461afebe02 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/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 = "/v71"; +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/apikey-auths/src/models.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/models.rs new file mode 100644 index 00000000000..e85e099e305 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/src/models.rs @@ -0,0 +1,841 @@ +#![allow(unused_qualifications)] + +use http::HeaderValue; +use validator::Validate; + +#[cfg(feature = "server")] +use crate::header; +use crate::{models, types::*}; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct GetPaymentMethodByIdPathParams { + /// Id of the payment method + pub id: String, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct Amount { + /// The three-character [ISO currency code](https://docs.adyen.com/development-resources/currency-codes). + #[serde(rename = "currency")] + #[validate(length(min = 3, max = 3))] + pub currency: String, + + /// The amount of the transaction, in [minor units](https://docs.adyen.com/development-resources/currency-codes). + #[serde(rename = "value")] + pub value: i64, +} + +impl Amount { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(currency: String, value: i64) -> Amount { + Amount { currency, value } + } +} + +/// Converts the Amount 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 Amount { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("currency".to_string()), + Some(self.currency.to_string()), + Some("value".to_string()), + Some(self.value.to_string()), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a Amount value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for Amount { + 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 currency: Vec, + pub value: 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 Amount".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "currency" => intermediate_rep.currency.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "value" => intermediate_rep.value.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing Amount".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(Amount { + currency: intermediate_rep + .currency + .into_iter() + .next() + .ok_or_else(|| "currency missing in Amount".to_string())?, + value: intermediate_rep + .value + .into_iter() + .next() + .ok_or_else(|| "value missing in Amount".to_string())?, + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for Amount - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into Amount - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct CheckoutError { + /// Error code + #[serde(rename = "code")] + pub code: String, + + /// User-friendly message + #[serde(rename = "message")] + pub message: String, +} + +impl CheckoutError { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(code: String, message: String) -> CheckoutError { + CheckoutError { code, message } + } +} + +/// Converts the CheckoutError 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 CheckoutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("code".to_string()), + Some(self.code.to_string()), + Some("message".to_string()), + Some(self.message.to_string()), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a CheckoutError value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for CheckoutError { + 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 code: Vec, + pub message: 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 CheckoutError".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "code" => intermediate_rep.code.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "message" => intermediate_rep.message.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing CheckoutError".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(CheckoutError { + code: intermediate_rep + .code + .into_iter() + .next() + .ok_or_else(|| "code missing in CheckoutError".to_string())?, + message: intermediate_rep + .message + .into_iter() + .next() + .ok_or_else(|| "message missing in CheckoutError".to_string())?, + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for CheckoutError - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into CheckoutError - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct Payment { + #[serde(rename = "paymentMethod")] + pub payment_method: models::PaymentMethod, + + #[serde(rename = "amount")] + pub amount: models::Amount, + + #[serde(rename = "merchantAccount")] + pub merchant_account: String, + + #[serde(rename = "reference")] + #[serde(skip_serializing_if = "Option::is_none")] + pub reference: Option, + + /// Note: inline enums are not fully supported by openapi-generator + #[serde(rename = "channel")] + #[serde(skip_serializing_if = "Option::is_none")] + pub channel: Option, +} + +impl Payment { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new( + payment_method: models::PaymentMethod, + amount: models::Amount, + merchant_account: String, + ) -> Payment { + Payment { + payment_method, + amount, + merchant_account, + reference: None, + channel: None, + } + } +} + +/// Converts the Payment 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 Payment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + // Skipping paymentMethod in query parameter serialization + + // Skipping amount in query parameter serialization + Some("merchantAccount".to_string()), + Some(self.merchant_account.to_string()), + self.reference + .as_ref() + .map(|reference| ["reference".to_string(), reference.to_string()].join(",")), + self.channel + .as_ref() + .map(|channel| ["channel".to_string(), channel.to_string()].join(",")), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a Payment value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for Payment { + 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 payment_method: Vec, + pub amount: Vec, + pub merchant_account: Vec, + pub reference: Vec, + pub channel: 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 Payment".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "paymentMethod" => intermediate_rep.payment_method.push( + ::from_str(val) + .map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "amount" => intermediate_rep.amount.push( + ::from_str(val) + .map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "merchantAccount" => intermediate_rep.merchant_account.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "reference" => intermediate_rep.reference.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "channel" => intermediate_rep.channel.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing Payment".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(Payment { + payment_method: intermediate_rep + .payment_method + .into_iter() + .next() + .ok_or_else(|| "paymentMethod missing in Payment".to_string())?, + amount: intermediate_rep + .amount + .into_iter() + .next() + .ok_or_else(|| "amount missing in Payment".to_string())?, + merchant_account: intermediate_rep + .merchant_account + .into_iter() + .next() + .ok_or_else(|| "merchantAccount missing in Payment".to_string())?, + reference: intermediate_rep.reference.into_iter().next(), + channel: intermediate_rep.channel.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for Payment - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into Payment - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct PaymentMethod { + /// Name of the payment method + /// Note: inline enums are not fully supported by openapi-generator + #[serde(rename = "name")] + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + + /// Type of the payment method + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub r#type: Option, +} + +impl PaymentMethod { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new() -> PaymentMethod { + PaymentMethod { + name: None, + r#type: None, + } + } +} + +/// Converts the PaymentMethod 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 PaymentMethod { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + self.name + .as_ref() + .map(|name| ["name".to_string(), name.to_string()].join(",")), + self.r#type + .as_ref() + .map(|r#type| ["type".to_string(), r#type.to_string()].join(",")), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a PaymentMethod value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for PaymentMethod { + type Err = String; + + fn from_str(s: &str) -> std::result::Result { + /// An intermediate representation of the struct to use for parsing. + #[derive(Default)] + #[allow(dead_code)] + struct IntermediateRep { + pub name: Vec, + pub r#type: 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 PaymentMethod".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "name" => intermediate_rep.name.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "type" => intermediate_rep.r#type.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing PaymentMethod".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(PaymentMethod { + name: intermediate_rep.name.into_iter().next(), + r#type: intermediate_rep.r#type.into_iter().next(), + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for PaymentMethod - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into PaymentMethod - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, validator::Validate)] +#[cfg_attr(feature = "conversion", derive(frunk::LabelledGeneric))] +pub struct PaymentResult { + /// PSP ref + #[serde(rename = "pspReference")] + pub psp_reference: String, + + /// Result code + /// Note: inline enums are not fully supported by openapi-generator + #[serde(rename = "resultCode")] + pub result_code: String, +} + +impl PaymentResult { + #[allow(clippy::new_without_default, clippy::too_many_arguments)] + pub fn new(psp_reference: String, result_code: String) -> PaymentResult { + PaymentResult { + psp_reference, + result_code, + } + } +} + +/// Converts the PaymentResult 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 PaymentResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let params: Vec> = vec![ + Some("pspReference".to_string()), + Some(self.psp_reference.to_string()), + Some("resultCode".to_string()), + Some(self.result_code.to_string()), + ]; + + write!( + f, + "{}", + params.into_iter().flatten().collect::>().join(",") + ) + } +} + +/// Converts Query Parameters representation (style=form, explode=false) to a PaymentResult value +/// as specified in https://swagger.io/docs/specification/serialization/ +/// Should be implemented in a serde deserializer +impl std::str::FromStr for PaymentResult { + 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 psp_reference: Vec, + pub result_code: 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 PaymentResult".to_string(), + ) + } + }; + + if let Some(key) = key_result { + #[allow(clippy::match_single_binding)] + match key { + #[allow(clippy::redundant_clone)] + "pspReference" => intermediate_rep.psp_reference.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + #[allow(clippy::redundant_clone)] + "resultCode" => intermediate_rep.result_code.push( + ::from_str(val).map_err(|x| x.to_string())?, + ), + _ => { + return std::result::Result::Err( + "Unexpected key while parsing PaymentResult".to_string(), + ) + } + } + } + + // Get the next key + key_result = string_iter.next(); + } + + // Use the intermediate representation to return the struct + std::result::Result::Ok(PaymentResult { + psp_reference: intermediate_rep + .psp_reference + .into_iter() + .next() + .ok_or_else(|| "pspReference missing in PaymentResult".to_string())?, + result_code: intermediate_rep + .result_code + .into_iter() + .next() + .ok_or_else(|| "resultCode missing in PaymentResult".to_string())?, + }) + } +} + +// Methods for converting between header::IntoHeaderValue and HeaderValue + +#[cfg(feature = "server")] +impl std::convert::TryFrom> for HeaderValue { + type Error = String; + + fn try_from( + hdr_value: header::IntoHeaderValue, + ) -> std::result::Result { + let hdr_value = hdr_value.to_string(); + match HeaderValue::from_str(&hdr_value) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Invalid header value for PaymentResult - value: {} is invalid {}", + hdr_value, e + )), + } + } +} + +#[cfg(feature = "server")] +impl std::convert::TryFrom for header::IntoHeaderValue { + type Error = String; + + fn try_from(hdr_value: HeaderValue) -> std::result::Result { + match hdr_value.to_str() { + std::result::Result::Ok(value) => { + match ::from_str(value) { + std::result::Result::Ok(value) => { + std::result::Result::Ok(header::IntoHeaderValue(value)) + } + std::result::Result::Err(err) => std::result::Result::Err(format!( + "Unable to convert header value '{}' into PaymentResult - {}", + value, err + )), + } + } + std::result::Result::Err(e) => std::result::Result::Err(format!( + "Unable to convert header: {:?} to string: {}", + hdr_value, e + )), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/src/server/mod.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/server/mod.rs new file mode 100644 index 00000000000..43085046baf --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/src/server/mod.rs @@ -0,0 +1,330 @@ +use std::collections::HashMap; + +use axum::{body::Body, extract::*, response::Response, routing::*}; +use axum_extra::extract::{CookieJar, Multipart}; +use bytes::Bytes; +use http::{header::CONTENT_TYPE, HeaderMap, HeaderName, HeaderValue, Method, StatusCode}; +use tracing::error; +use validator::{Validate, ValidationErrors}; + +use crate::{header, types::*}; + +#[allow(unused_imports)] +use crate::{apis, models}; + +/// Setup API Server. +pub fn new(api_impl: I) -> Router +where + I: AsRef + Clone + Send + Sync + 'static, + A: apis::payments::Payments + apis::ApiKeyAuthHeader + apis::CookieAuthentication + 'static, +{ + // build our application with a route + Router::new() + .route("/v71/paymentMethods", get(get_payment_methods::)) + .route( + "/v71/paymentMethods/:id", + get(get_payment_method_by_id::), + ) + .route("/v71/payments", post(post_make_payment::)) + .with_state(api_impl) +} + +#[tracing::instrument(skip_all)] +fn get_payment_method_by_id_validation( + path_params: models::GetPaymentMethodByIdPathParams, +) -> std::result::Result<(models::GetPaymentMethodByIdPathParams,), ValidationErrors> { + path_params.validate()?; + + Ok((path_params,)) +} +/// GetPaymentMethodById - GET /v71/paymentMethods/{id} +#[tracing::instrument(skip_all)] +async fn get_payment_method_by_id( + method: Method, + host: Host, + cookies: CookieJar, + Path(path_params): Path, + State(api_impl): State, +) -> Result +where + I: AsRef + Send + Sync, + A: apis::payments::Payments, +{ + #[allow(clippy::redundant_closure)] + let validation = + tokio::task::spawn_blocking(move || get_payment_method_by_id_validation(path_params)) + .await + .unwrap(); + + let Ok((path_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_payment_method_by_id(method, host, cookies, path_params) + .await; + + let mut response = Response::builder(); + + let resp = match result { + Ok(rsp) => match rsp { + apis::payments::GetPaymentMethodByIdResponse::Status200_OK(body) => { + let mut response = response.status(200); + { + let mut response_headers = response.headers_mut().unwrap(); + response_headers.insert( + CONTENT_TYPE, + HeaderValue::from_str("application/json").map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + })?, + ); + } + + let body_content = tokio::task::spawn_blocking(move || { + serde_json::to_vec(&body).map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) + }) + .await + .unwrap()?; + response.body(Body::from(body_content)) + } + apis::payments::GetPaymentMethodByIdResponse::Status422_UnprocessableEntity(body) => { + let mut response = response.status(422); + { + let mut response_headers = response.headers_mut().unwrap(); + response_headers.insert( + CONTENT_TYPE, + HeaderValue::from_str("application/json").map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + })?, + ); + } + + let body_content = tokio::task::spawn_blocking(move || { + serde_json::to_vec(&body).map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) + }) + .await + .unwrap()?; + response.body(Body::from(body_content)) + } + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + response.status(500).body(Body::empty()) + } + }; + + resp.map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + +#[tracing::instrument(skip_all)] +fn get_payment_methods_validation() -> std::result::Result<(), ValidationErrors> { + Ok(()) +} +/// GetPaymentMethods - GET /v71/paymentMethods +#[tracing::instrument(skip_all)] +async fn get_payment_methods( + method: Method, + host: Host, + cookies: CookieJar, + State(api_impl): State, +) -> Result +where + I: AsRef + Send + Sync, + A: apis::payments::Payments, +{ + #[allow(clippy::redundant_closure)] + let validation = tokio::task::spawn_blocking(move || get_payment_methods_validation()) + .await + .unwrap(); + + let Ok(()) = 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_payment_methods(method, host, cookies) + .await; + + let mut response = Response::builder(); + + let resp = match result { + Ok(rsp) => match rsp { + apis::payments::GetPaymentMethodsResponse::Status200_OK(body) => { + let mut response = response.status(200); + { + let mut response_headers = response.headers_mut().unwrap(); + response_headers.insert( + CONTENT_TYPE, + HeaderValue::from_str("application/json").map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + })?, + ); + } + + let body_content = tokio::task::spawn_blocking(move || { + serde_json::to_vec(&body).map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) + }) + .await + .unwrap()?; + response.body(Body::from(body_content)) + } + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + response.status(500).body(Body::empty()) + } + }; + + resp.map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) +} + +#[derive(validator::Validate)] +#[allow(dead_code)] +struct PostMakePaymentBodyValidator<'a> { + #[validate(nested)] + body: &'a models::Payment, +} + +#[tracing::instrument(skip_all)] +fn post_make_payment_validation( + body: Option, +) -> std::result::Result<(Option,), ValidationErrors> { + if let Some(body) = &body { + let b = PostMakePaymentBodyValidator { body }; + b.validate()?; + } + + Ok((body,)) +} +/// PostMakePayment - POST /v71/payments +#[tracing::instrument(skip_all)] +async fn post_make_payment( + method: Method, + host: Host, + cookies: CookieJar, + State(api_impl): State, + Json(body): Json>, +) -> Result +where + I: AsRef + Send + Sync, + A: apis::payments::Payments + apis::CookieAuthentication, +{ + // Authentication + let token_in_cookie = api_impl + .as_ref() + .extract_token_from_cookie(&cookies, "XApiKey"); + if let (None,) = (&token_in_cookie,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + + #[allow(clippy::redundant_closure)] + let validation = tokio::task::spawn_blocking(move || post_make_payment_validation(body)) + .await + .unwrap(); + + let Ok((body,)) = 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() + .post_make_payment(method, host, cookies, token_in_cookie, body) + .await; + + let mut response = Response::builder(); + + let resp = match result { + Ok(rsp) => match rsp { + apis::payments::PostMakePaymentResponse::Status200_OK(body) => { + let mut response = response.status(200); + { + let mut response_headers = response.headers_mut().unwrap(); + response_headers.insert( + CONTENT_TYPE, + HeaderValue::from_str("application/json").map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + })?, + ); + } + + let body_content = tokio::task::spawn_blocking(move || { + serde_json::to_vec(&body).map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) + }) + .await + .unwrap()?; + response.body(Body::from(body_content)) + } + apis::payments::PostMakePaymentResponse::Status422_UnprocessableEntity(body) => { + let mut response = response.status(422); + { + let mut response_headers = response.headers_mut().unwrap(); + response_headers.insert( + CONTENT_TYPE, + HeaderValue::from_str("application/json").map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + })?, + ); + } + + let body_content = tokio::task::spawn_blocking(move || { + serde_json::to_vec(&body).map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) + }) + .await + .unwrap()?; + response.body(Body::from(body_content)) + } + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + response.status(500).body(Body::empty()) + } + }; + + resp.map_err(|e| { + error!(error = ?e); + StatusCode::INTERNAL_SERVER_ERROR + }) +} diff --git a/samples/server/petstore/rust-axum/output/apikey-auths/src/types.rs b/samples/server/petstore/rust-axum/output/apikey-auths/src/types.rs new file mode 100644 index 00000000000..80757d42000 --- /dev/null +++ b/samples/server/petstore/rust-axum/output/apikey-auths/src/types.rs @@ -0,0 +1,665 @@ +use std::{mem, str::FromStr}; + +use base64::{engine::general_purpose, Engine}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[allow(dead_code)] +pub struct Object(serde_json::Value); + +impl validator::Validate for Object { + fn validate(&self) -> Result<(), validator::ValidationErrors> { + Ok(()) + } +} + +impl FromStr for Object { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(Self(serde_json::Value::String(s.to_owned()))) + } +} + +/// Serde helper function to create a default `Option>` while +/// deserializing +pub fn default_optional_nullable() -> Option> { + None +} + +/// Serde helper function to deserialize into an `Option>` +pub fn deserialize_optional_nullable<'de, D, T>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de>, +{ + Option::::deserialize(deserializer).map(|val| match val { + Some(inner) => Some(Nullable::Present(inner)), + None => Some(Nullable::Null), + }) +} + +/// The Nullable type. Represents a value which may be specified as null on an API. +/// Note that this is distinct from a value that is optional and not present! +/// +/// Nullable implements many of the same methods as the Option type (map, unwrap, etc). +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] +pub enum Nullable { + /// Null value + Null, + /// Value is present + Present(T), +} + +impl Nullable { + ///////////////////////////////////////////////////////////////////////// + // Querying the contained values + ///////////////////////////////////////////////////////////////////////// + + /// Returns `true` if the Nullable is a `Present` value. + /// + /// # Examples + /// + /// ``` + /// # use apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::types::Nullable; + /// + /// let x = Nullable::Present("value"); + /// assert_eq!(x.expect("the world is ending"), "value"); + /// ``` + /// + /// ```should_panic + /// # use apikey_auths::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 apikey_auths::types::Nullable; + /// + /// let x = Nullable::Present("air"); + /// assert_eq!(x.unwrap(), "air"); + /// ``` + /// + /// ```should_panic + /// # use apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::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 apikey_auths::types::Nullable; + /// + /// let x = Nullable::Present(42); + /// assert_eq!(42, x.unwrap_or_default()); + /// + /// let y: Nullable = Nullable::Null; + /// assert_eq!(0, y.unwrap_or_default()); + /// ``` + #[inline] + pub fn unwrap_or_default(self) -> T { + match self { + Nullable::Present(x) => x, + Nullable::Null => Default::default(), + } + } +} + +impl Default for Nullable { + /// Returns None. + #[inline] + fn default() -> Nullable { + Nullable::Null + } +} + +impl From for Nullable { + fn from(val: T) -> Nullable { + Nullable::Present(val) + } +} + +impl Serialize for Nullable +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match *self { + Nullable::Present(ref inner) => serializer.serialize_some(&inner), + Nullable::Null => serializer.serialize_none(), + } + } +} + +impl<'de, T> Deserialize<'de> for Nullable +where + T: serde::de::DeserializeOwned, +{ + fn deserialize(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + // In order to deserialize a required, but nullable, value, we first have to check whether + // the value is present at all. To do this, we deserialize to a serde_json::Value, which + // fails if the value is missing, or gives serde_json::Value::Null if the value is present. + // If that succeeds as null, we can easily return a Null. + // If that succeeds as some value, we deserialize that value and return a Present. + // If that errors, we return the error. + let presence: Result<::serde_json::Value, _> = + serde::Deserialize::deserialize(deserializer); + match presence { + Ok(serde_json::Value::Null) => Ok(Nullable::Null), + Ok(some_value) => serde_json::from_value(some_value) + .map(Nullable::Present) + .map_err(serde::de::Error::custom), + Err(x) => Err(x), + } + } +} + +#[inline(never)] +#[cold] +fn expect_failed(msg: &str) -> ! { + panic!("{}", msg) +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +/// Base64-encoded byte array +pub struct ByteArray(pub Vec); + +impl Serialize for ByteArray { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&general_purpose::STANDARD.encode(&self.0)) + } +} + +impl<'de> Deserialize<'de> for ByteArray { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + match general_purpose::STANDARD.decode(s) { + Ok(bin) => Ok(ByteArray(bin)), + _ => Err(serde::de::Error::custom("invalid base64")), + } + } +} diff --git a/samples/server/petstore/rust-axum/output/openapi-v3/src/server/mod.rs b/samples/server/petstore/rust-axum/output/openapi-v3/src/server/mod.rs index ee704dda76f..8b31ce9b9e6 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 @@ -602,7 +602,7 @@ where { // Header parameters let header_params = { - let header_x_header = headers.get(HeaderName::from_static("x_header")); + let header_x_header = headers.get(HeaderName::from_static("X-Header")); let header_x_header = match header_x_header { Some(v) => match header::IntoHeaderValue::::try_from((*v).clone()) { @@ -1471,7 +1471,7 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), success_info); + response_headers.insert(HeaderName::from_static("Success-Info"), success_info); } if let Some(bool_header) = bool_header { let bool_header = match header::IntoHeaderValue(bool_header).try_into() { @@ -1485,7 +1485,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), bool_header); + response_headers + .insert(HeaderName::from_static("Bool-Header"), bool_header); } } if let Some(object_header) = object_header { @@ -1500,7 +1501,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), object_header); + response_headers + .insert(HeaderName::from_static("Object-Header"), object_header); } } let mut response = response.status(200); @@ -1541,7 +1543,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), further_info); + response_headers + .insert(HeaderName::from_static("Further-Info"), further_info); } } if let Some(failure_info) = failure_info { @@ -1556,7 +1559,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), failure_info); + response_headers + .insert(HeaderName::from_static("Failure-Info"), failure_info); } } let mut response = response.status(412); @@ -1706,7 +1710,7 @@ where { // Header parameters let header_params = { - let header_x_header_one = headers.get(HeaderName::from_static("x_header_one")); + let header_x_header_one = headers.get(HeaderName::from_static("x-header-one")); let header_x_header_one = match header_x_header_one { Some(v) => match header::IntoHeaderValue::::try_from((*v).clone()) { @@ -1723,7 +1727,7 @@ where }, None => None, }; - let header_x_header_two = headers.get(HeaderName::from_static("x_header_two")); + let header_x_header_two = headers.get(HeaderName::from_static("x-header-two")); let header_x_header_two = match header_x_header_two { Some(v) => match header::IntoHeaderValue::::try_from((*v).clone()) { diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/mod.rs b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/mod.rs index fa714e56bee..bd4f93f4286 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/mod.rs +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/mod.rs @@ -4,3 +4,12 @@ pub mod fake_classname_tags123; pub mod pet; pub mod store; pub mod user; + +/// API Key Authentication - Header. +pub trait ApiKeyAuthHeader { + fn extract_token_from_header( + &self, + headers: &axum::http::header::HeaderMap, + key: &str, + ) -> Option; +} diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/pet.rs b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/pet.rs index f2c36cd51af..169b621ede0 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/pet.rs +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/pet.rs @@ -140,6 +140,7 @@ pub trait Pet { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, path_params: models::GetPetByIdPathParams, ) -> Result; diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/store.rs b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/store.rs index 607bf6f568f..3616cc511a3 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/store.rs +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/apis/store.rs @@ -70,6 +70,7 @@ pub trait Store { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, ) -> Result; /// Find purchase order by ID. diff --git a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs index 64726e87b25..32c2871a4a7 100644 --- a/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs +++ b/samples/server/petstore/rust-axum/output/petstore-with-fake-endpoints-models-for-testing/src/server/mod.rs @@ -22,6 +22,7 @@ where + apis::pet::Pet + apis::store::Store + apis::user::User + + apis::ApiKeyAuthHeader + 'static, { // build our application with a route @@ -1646,13 +1647,25 @@ async fn get_pet_by_id( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, Path(path_params): Path, State(api_impl): State, ) -> Result where I: AsRef + Send + Sync, - A: apis::pet::Pet, + A: apis::pet::Pet + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || get_pet_by_id_validation(path_params)) .await @@ -1667,7 +1680,7 @@ where let result = api_impl .as_ref() - .get_pet_by_id(method, host, cookies, path_params) + .get_pet_by_id(method, host, cookies, token_in_header, path_params) .await; let mut response = Response::builder(); @@ -2022,12 +2035,24 @@ async fn get_inventory( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, State(api_impl): State, ) -> Result where I: AsRef + Send + Sync, - A: apis::store::Store, + A: apis::store::Store + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || get_inventory_validation()) .await @@ -2040,7 +2065,10 @@ where .map_err(|_| StatusCode::BAD_REQUEST); }; - let result = api_impl.as_ref().get_inventory(method, host, cookies).await; + let result = api_impl + .as_ref() + .get_inventory(method, host, cookies, token_in_header) + .await; let mut response = Response::builder(); @@ -2656,7 +2684,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), x_rate_limit); + response_headers + .insert(HeaderName::from_static("X-Rate-Limit"), x_rate_limit); } } if let Some(x_expires_after) = x_expires_after { @@ -2672,7 +2701,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), x_expires_after); + response_headers + .insert(HeaderName::from_static("X-Expires-After"), x_expires_after); } } let mut response = response.status(200); diff --git a/samples/server/petstore/rust-axum/output/petstore/src/apis/mod.rs b/samples/server/petstore/rust-axum/output/petstore/src/apis/mod.rs index cdc670909a4..f7e2882bf19 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/apis/mod.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/apis/mod.rs @@ -1,3 +1,12 @@ pub mod pet; pub mod store; pub mod user; + +/// API Key Authentication - Header. +pub trait ApiKeyAuthHeader { + fn extract_token_from_header( + &self, + headers: &axum::http::header::HeaderMap, + key: &str, + ) -> Option; +} diff --git a/samples/server/petstore/rust-axum/output/petstore/src/apis/pet.rs b/samples/server/petstore/rust-axum/output/petstore/src/apis/pet.rs index 057eafbec18..92335fa4400 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/apis/pet.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/apis/pet.rs @@ -144,6 +144,7 @@ pub trait Pet { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, path_params: models::GetPetByIdPathParams, ) -> Result; diff --git a/samples/server/petstore/rust-axum/output/petstore/src/apis/store.rs b/samples/server/petstore/rust-axum/output/petstore/src/apis/store.rs index d584d653580..85093df815b 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/apis/store.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/apis/store.rs @@ -70,6 +70,7 @@ pub trait Store { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, ) -> Result; /// Find purchase order by ID. diff --git a/samples/server/petstore/rust-axum/output/petstore/src/apis/user.rs b/samples/server/petstore/rust-axum/output/petstore/src/apis/user.rs index e786750bc83..3d884649569 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/apis/user.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/apis/user.rs @@ -98,6 +98,7 @@ pub trait User { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, body: models::User, ) -> Result; @@ -109,6 +110,7 @@ pub trait User { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, body: Vec, ) -> Result; @@ -120,6 +122,7 @@ pub trait User { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, body: Vec, ) -> Result; @@ -131,6 +134,7 @@ pub trait User { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, path_params: models::DeleteUserPathParams, ) -> Result; @@ -164,6 +168,7 @@ pub trait User { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, ) -> Result; /// Updated user. @@ -174,6 +179,7 @@ pub trait User { method: Method, host: Host, cookies: CookieJar, + token_in_header: Option, path_params: models::UpdateUserPathParams, body: models::User, ) -> Result; diff --git a/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs b/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs index ea7383de79f..d1fb8a9f252 100644 --- a/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs +++ b/samples/server/petstore/rust-axum/output/petstore/src/server/mod.rs @@ -16,7 +16,7 @@ use crate::{apis, models}; pub fn new(api_impl: I) -> Router where I: AsRef + Clone + Send + Sync + 'static, - A: apis::pet::Pet + apis::store::Store + apis::user::User + 'static, + A: apis::pet::Pet + apis::store::Store + apis::user::User + apis::ApiKeyAuthHeader + 'static, { // build our application with a route Router::new() @@ -395,13 +395,25 @@ async fn get_pet_by_id( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, Path(path_params): Path, State(api_impl): State, ) -> Result where I: AsRef + Send + Sync, - A: apis::pet::Pet, + A: apis::pet::Pet + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || get_pet_by_id_validation(path_params)) .await @@ -416,7 +428,7 @@ where let result = api_impl .as_ref() - .get_pet_by_id(method, host, cookies, path_params) + .get_pet_by_id(method, host, cookies, token_in_header, path_params) .await; let mut response = Response::builder(); @@ -787,12 +799,24 @@ async fn get_inventory( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, State(api_impl): State, ) -> Result where I: AsRef + Send + Sync, - A: apis::store::Store, + A: apis::store::Store + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || get_inventory_validation()) .await @@ -805,7 +829,10 @@ where .map_err(|_| StatusCode::BAD_REQUEST); }; - let result = api_impl.as_ref().get_inventory(method, host, cookies).await; + let result = api_impl + .as_ref() + .get_inventory(method, host, cookies, token_in_header) + .await; let mut response = Response::builder(); @@ -1034,13 +1061,25 @@ async fn create_user( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, State(api_impl): State, Json(body): Json, ) -> Result where I: AsRef + Send + Sync, - A: apis::user::User, + A: apis::user::User + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || create_user_validation(body)) .await @@ -1055,7 +1094,7 @@ where let result = api_impl .as_ref() - .create_user(method, host, cookies, body) + .create_user(method, host, cookies, token_in_header, body) .await; let mut response = Response::builder(); @@ -1102,13 +1141,25 @@ async fn create_users_with_array_input( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, State(api_impl): State, Json(body): Json>, ) -> Result where I: AsRef + Send + Sync, - A: apis::user::User, + A: apis::user::User + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || create_users_with_array_input_validation(body)) @@ -1124,7 +1175,7 @@ where let result = api_impl .as_ref() - .create_users_with_array_input(method, host, cookies, body) + .create_users_with_array_input(method, host, cookies, token_in_header, body) .await; let mut response = Response::builder(); @@ -1171,13 +1222,25 @@ async fn create_users_with_list_input( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, State(api_impl): State, Json(body): Json>, ) -> Result where I: AsRef + Send + Sync, - A: apis::user::User, + A: apis::user::User + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || create_users_with_list_input_validation(body)) @@ -1193,7 +1256,7 @@ where let result = api_impl .as_ref() - .create_users_with_list_input(method, host, cookies, body) + .create_users_with_list_input(method, host, cookies, token_in_header, body) .await; let mut response = Response::builder(); @@ -1232,13 +1295,25 @@ async fn delete_user( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, Path(path_params): Path, State(api_impl): State, ) -> Result where I: AsRef + Send + Sync, - A: apis::user::User, + A: apis::user::User + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || delete_user_validation(path_params)) .await @@ -1253,7 +1328,7 @@ where let result = api_impl .as_ref() - .delete_user(method, host, cookies, path_params) + .delete_user(method, host, cookies, token_in_header, path_params) .await; let mut response = Response::builder(); @@ -1422,7 +1497,7 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), set_cookie); + response_headers.insert(HeaderName::from_static("Set-Cookie"), set_cookie); } } if let Some(x_rate_limit) = x_rate_limit { @@ -1437,7 +1512,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), x_rate_limit); + response_headers + .insert(HeaderName::from_static("X-Rate-Limit"), x_rate_limit); } } if let Some(x_expires_after) = x_expires_after { @@ -1453,7 +1529,8 @@ where { let mut response_headers = response.headers_mut().unwrap(); - response_headers.insert(HeaderName::from_static(""), x_expires_after); + response_headers + .insert(HeaderName::from_static("X-Expires-After"), x_expires_after); } } let mut response = response.status(200); @@ -1499,12 +1576,24 @@ async fn logout_user( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, State(api_impl): State, ) -> Result where I: AsRef + Send + Sync, - A: apis::user::User, + A: apis::user::User + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || logout_user_validation()) .await @@ -1517,7 +1606,10 @@ where .map_err(|_| StatusCode::BAD_REQUEST); }; - let result = api_impl.as_ref().logout_user(method, host, cookies).await; + let result = api_impl + .as_ref() + .logout_user(method, host, cookies, token_in_header) + .await; let mut response = Response::builder(); @@ -1565,14 +1657,26 @@ async fn update_user( method: Method, host: Host, cookies: CookieJar, + headers: HeaderMap, Path(path_params): Path, State(api_impl): State, Json(body): Json, ) -> Result where I: AsRef + Send + Sync, - A: apis::user::User, + A: apis::user::User + apis::ApiKeyAuthHeader, { + // Authentication + let token_in_header = api_impl + .as_ref() + .extract_token_from_header(&headers, "ApiKey"); + if let (None,) = (&token_in_header,) { + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::empty()) + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR); + } + #[allow(clippy::redundant_closure)] let validation = tokio::task::spawn_blocking(move || update_user_validation(path_params, body)) .await @@ -1587,7 +1691,7 @@ where let result = api_impl .as_ref() - .update_user(method, host, cookies, path_params, body) + .update_user(method, host, cookies, token_in_header, path_params, body) .await; let mut response = Response::builder();