diff --git a/bin/configs/rust-server-ping-bearer-auth-v3.yaml b/bin/configs/rust-server-ping-bearer-auth-v3.yaml new file mode 100644 index 00000000000..5f02e791e1a --- /dev/null +++ b/bin/configs/rust-server-ping-bearer-auth-v3.yaml @@ -0,0 +1,8 @@ +generatorName: rust-server +outputDir: samples/server/petstore/rust-server/output/ping-bearer-auth +inputSpec: modules/openapi-generator/src/test/resources/3_0/rust-server/ping-bearer-auth.yaml +templateDir: modules/openapi-generator/src/main/resources/rust-server +generateAliasAsModel: true +additionalProperties: + hideGenerationTimestamp: "true" + packageName: ping-bearer-auth diff --git a/docs/generators/rust-server.md b/docs/generators/rust-server.md index cadccd0fbcc..1d13b032104 100644 --- a/docs/generators/rust-server.md +++ b/docs/generators/rust-server.md @@ -196,7 +196,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |BasicAuth|✓|OAS2,OAS3 |ApiKey|✓|OAS2,OAS3 |OpenIDConnect|✗|OAS3 -|BearerToken|✗|OAS3 +|BearerToken|✓|OAS3 |OAuth2_Implicit|✓|OAS2,OAS3 |OAuth2_Password|✗|OAS2,OAS3 |OAuth2_ClientCredentials|✗|OAS2,OAS3 diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java index 2080fca66a7..7a1b22b20c4 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/RustServerCodegen.java @@ -94,6 +94,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig { .securityFeatures(EnumSet.of( SecurityFeature.ApiKey, SecurityFeature.BasicAuth, + SecurityFeature.BearerToken, SecurityFeature.OAuth2_Implicit )) .excludeGlobalFeatures( diff --git a/modules/openapi-generator/src/main/resources/rust-server/README.mustache b/modules/openapi-generator/src/main/resources/rust-server/README.mustache index 7cb75705f5f..3a5a0299e9d 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/README.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/README.mustache @@ -129,13 +129,20 @@ Example {{! TODO: Add API Key example }} ``` {{/isApiKey}} -{{#isBasic}}- **Type**: HTTP basic authentication +{{#isBasicBasic}}- **Type**: HTTP basic authentication Example ``` {{! TODO: Add HTTP basic authentication }} ``` -{{/isBasic}} +{{/isBasicBasic}} +{{#isBasicBearer}}- **Type**: Bearer token authentication + +Example +``` + {{! TODO: Add Bearer token authentication }} +``` +{{/isBasicBearer}} {{#isOAuth}}- **Type**: OAuth - **Flow**: {{{flow}}} - **Authorization URL**: {{{authorizationUrl}}} diff --git a/modules/openapi-generator/src/main/resources/rust-server/context.mustache b/modules/openapi-generator/src/main/resources/rust-server/context.mustache index 34635451f5d..f3e1c5e130a 100644 --- a/modules/openapi-generator/src/main/resources/rust-server/context.mustache +++ b/modules/openapi-generator/src/main/resources/rust-server/context.mustache @@ -106,6 +106,7 @@ impl Service> for AddContext Service> for AddContext(&headers) { + let auth_data = AuthData::Bearer(bearer); + let context = context.push(Some(auth_data)); + let context = context.push(None::); + + return self.inner.call((request, context)) + } + } + {{/isBasicBearer}} {{/isBasic}} {{#isOAuth}} { diff --git a/modules/openapi-generator/src/test/resources/3_0/rust-server/ping-bearer-auth.yaml b/modules/openapi-generator/src/test/resources/3_0/rust-server/ping-bearer-auth.yaml new file mode 100644 index 00000000000..a503051829f --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/rust-server/ping-bearer-auth.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.1 +info: + title: ping test + version: '1.0' +servers: + - url: 'http://localhost:8080/' +paths: + /ping: + get: + operationId: pingGet + responses: + '201': + description: OK +components: + securitySchemes: + bearerAuth: + scheme: bearer + bearerFormat: token + type: http +security: + - bearerAuth: [] \ No newline at end of file diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/.cargo/config b/samples/server/petstore/rust-server/output/ping-bearer-auth/.cargo/config new file mode 100644 index 00000000000..b8acc9c00c8 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/.cargo/config @@ -0,0 +1,18 @@ +[build] +rustflags = [ + "-W", "missing_docs", # detects missing documentation for public members + + "-W", "trivial_casts", # detects trivial casts which could be removed + + "-W", "trivial_numeric_casts", # detects trivial casts of numeric types which could be removed + + "-W", "unsafe_code", # usage of `unsafe` code + + "-W", "unused_qualifications", # detects unnecessarily qualified names + + "-W", "unused_extern_crates", # extern crates that are never used + + "-W", "unused_import_braces", # unnecessary braces around an imported item + + "-D", "warnings", # all warnings should be denied +] diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/.gitignore b/samples/server/petstore/rust-server/output/ping-bearer-auth/.gitignore new file mode 100644 index 00000000000..a9d37c560c6 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator-ignore b/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator-ignore new file mode 100644 index 00000000000..7484ee590a3 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/.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-server/output/ping-bearer-auth/.openapi-generator/FILES b/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/FILES new file mode 100644 index 00000000000..e86946909bd --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/FILES @@ -0,0 +1,18 @@ +.cargo/config +.gitignore +Cargo.toml +README.md +api/openapi.yaml +docs/default_api.md +examples/ca.pem +examples/client/main.rs +examples/server-chain.pem +examples/server-key.pem +examples/server/main.rs +examples/server/server.rs +src/client/mod.rs +src/context.rs +src/header.rs +src/lib.rs +src/models.rs +src/server/mod.rs diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/VERSION b/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/VERSION new file mode 100644 index 00000000000..d99e7162d01 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/.openapi-generator/VERSION @@ -0,0 +1 @@ +5.0.0-SNAPSHOT \ No newline at end of file diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml b/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml new file mode 100644 index 00000000000..5f14f44c654 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "ping-bearer-auth" +version = "1.0.0" +authors = [] +description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)" +license = "Unlicense" +edition = "2018" + +[features] +default = ["client", "server"] +client = [ + "hyper", "hyper-openssl", "hyper-tls", "native-tls", "openssl", "url" +] +server = [ + "serde_ignored", "hyper", "regex", "percent-encoding", "url", "lazy_static" +] +conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-enum-derive"] + +[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies] +native-tls = { version = "0.2", optional = true } +hyper-tls = { version = "0.4", optional = true } + +[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies] +hyper-openssl = { version = "0.8", optional = true } +openssl = {version = "0.10", optional = true } + +[dependencies] +# Common +async-trait = "0.1.24" +chrono = { version = "0.4", features = ["serde"] } +futures = "0.3" +swagger = "5.0.0-alpha-1" +log = "0.4.0" +mime = "0.3" + +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" + +# Crates included if required by the API definition + +# Common between server and client features +hyper = {version = "0.13", optional = true} +serde_ignored = {version = "0.1.1", optional = true} +url = {version = "2.1", optional = true} + +# Client-specific + +# Server, and client callback-specific +lazy_static = { version = "1.4", optional = true } +percent-encoding = {version = "2.1.0", optional = true} +regex = {version = "1.3", optional = true} + +# Conversion +frunk = { version = "0.3.0", optional = true } +frunk_derives = { version = "0.3.0", optional = true } +frunk_core = { version = "0.3.0", optional = true } +frunk-enum-derive = { version = "0.2.0", optional = true } +frunk-enum-core = { version = "0.2.0", optional = true } + +[dev-dependencies] +clap = "2.25" +env_logger = "0.7" +tokio = { version = "0.2", features = ["rt-threaded", "macros", "stream"] } +native-tls = "0.2" +tokio-tls = "0.3" + +[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies] +tokio-openssl = "0.4" +openssl = "0.10" + +[[example]] +name = "client" +required-features = ["client"] + +[[example]] +name = "server" +required-features = ["server"] diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md b/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md new file mode 100644 index 00000000000..f39803cc2dc --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/README.md @@ -0,0 +1,117 @@ +# Rust API for ping-bearer-auth + +No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + +## Overview + +This client/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 + + + + +This autogenerated project defines an API crate `ping-bearer-auth` which contains: +* An `Api` trait defining the API in Rust. +* Data types representing the underlying data model. +* A `Client` type which implements `Api` and issues HTTP requests for each operation. +* A router which accepts HTTP requests and invokes the appropriate `Api` method for each operation. + +It also contains an example server and client which make use of `ping-bearer-auth`: + +* The example server starts up a web server using the `ping-bearer-auth` + router, and supplies a trivial implementation of `Api` which returns failure + for every operation. +* The example client provides a CLI which lets you invoke + any single operation on the `ping-bearer-auth` client by passing appropriate + arguments on the command line. + +You can use the example server and client as a basis for your own code. +See below for [more detail on implementing a server](#writing-a-server). + +## Examples + +Run examples with: + +``` +cargo run --example +``` + +To pass in arguments to the examples, put them after `--`, for example: + +``` +cargo run --example client -- --help +``` + +### Running the example server +To run the server, follow these simple steps: + +``` +cargo run --example server +``` + +### Running the example client +To run a client, follow one of the following simple steps: + +``` +cargo run --example client PingGet +``` + +### HTTPS +The examples can be run in HTTPS mode by passing in the flag `--https`, for example: + +``` +cargo run --example server -- --https +``` + +This will use the keys/certificates from the examples directory. Note that the +server chain is signed with `CN=localhost`. + +## 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 hyper + * To create the server stack you'll need to provide an implementation of the API trait to provide the server function. +* `client` + * This defaults to enabled and creates the basic skeleton of a client implementation based on hyper + * The constructed client implements the API trait by making remote API call. +* `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`. + +## Documentation for API Endpoints + +All URIs are relative to *http://localhost:8080* + +Method | HTTP request | Description +------------- | ------------- | ------------- +[**pingGet**](docs/default_api.md#pingGet) | **GET** /ping | + + +## Documentation For Models + + + +## Documentation For Authorization + +## bearerAuth +- **Type**: Bearer token authentication + +Example +``` +``` + +## Author + + + diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/api/openapi.yaml b/samples/server/petstore/rust-server/output/ping-bearer-auth/api/openapi.yaml new file mode 100644 index 00000000000..c33a45c7834 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/api/openapi.yaml @@ -0,0 +1,23 @@ +openapi: 3.0.1 +info: + title: ping test + version: "1.0" +servers: +- url: http://localhost:8080/ +security: +- bearerAuth: [] +paths: + /ping: + get: + operationId: pingGet + responses: + "201": + description: OK +components: + schemas: {} + securitySchemes: + bearerAuth: + bearerFormat: token + scheme: bearer + type: http + diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/docs/default_api.md b/samples/server/petstore/rust-server/output/ping-bearer-auth/docs/default_api.md new file mode 100644 index 00000000000..2aab8587315 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/docs/default_api.md @@ -0,0 +1,31 @@ +# default_api + +All URIs are relative to *http://localhost:8080* + +Method | HTTP request | Description +------------- | ------------- | ------------- +**pingGet**](default_api.md#pingGet) | **GET** /ping | + + +# **pingGet** +> pingGet(ctx, ) + + +### Required Parameters +This endpoint does not need any parameter. + +### Return type + + (empty response body) + +### Authorization + +[bearerAuth](../README.md#bearerAuth) + +### HTTP request headers + + - **Content-Type**: Not defined + - **Accept**: Not defined + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/ca.pem b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/ca.pem new file mode 100644 index 00000000000..d2317fb5db7 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/ca.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICtjCCAZ4CCQDpKecRERZ0xDANBgkqhkiG9w0BAQsFADAdMQswCQYDVQQGEwJV +UzEOMAwGA1UEAxMFTXkgQ0EwHhcNMTcwNTIzMTYwMDIzWhcNMTcwNjIyMTYwMDIz +WjAdMQswCQYDVQQGEwJVUzEOMAwGA1UEAxMFTXkgQ0EwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCt66py3x7sCSASRF2D05L5wkNDxAUjQKYx23W8Gbwv +GMGykk89BIdU5LX1JB1cKiUOkoIxfwAYuWc2V/wzTvVV7+11besnk3uX1c9KiqUF +LIX7kn/z5hzS4aelhKvH+MJlSZCSlp1ytpZbwo5GB5Pi2SGH56jDBiBoDRNBVdWL +z4wH7TdrQjqWwNxIZumD5OGMtcfJyuX08iPiEOaslOeoMqzObhvjc9aUgjVjhqyA +FkJGTXsi0oaD7oml+NE+mTNfEeZvEJQpLSjBY0OvQHzuHkyGBShBnfu/9x7/NRwd +WaqsLiF7/re9KDGYdJwP7Cu6uxYfKAyWarp6h2mG/GIdAgMBAAEwDQYJKoZIhvcN +AQELBQADggEBAGIl/VVIafeq/AJOQ9r7TzzB2ABJYr7NZa6bTu5O1jSp1Fonac15 +SZ8gvRxODgH22ZYSqghPG4xzq4J3hkytlQqm57ZEt2I2M3OqIp17Ndcc1xDYzpLl +tA0FrVn6crQTM8vQkTDtGesaCWX+7Fir5dK7HnYWzfpSmsOpST07PfbNisEXKOxG +Dj4lBL1OnhTjsJeymVS1pFvkKkrcEJO+IxFiHL3CDsWjcXB0Z+E1zBtPoYyYsNsO +rBrjUxcZewF4xqWZhpW90Mt61fY2nRgU0uUwHcvDQUqvmzKcsqYa4mPKzfBI5mxo +01Ta96cDD6pS5Y1hOflZ0g84f2g/7xBLLDA= +-----END CERTIFICATE----- diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/client/main.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/client/main.rs new file mode 100644 index 00000000000..9fbfcd4259f --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/client/main.rs @@ -0,0 +1,83 @@ +#![allow(missing_docs, unused_variables, trivial_casts)] + + +#[allow(unused_imports)] +use futures::{future, Stream, stream}; +#[allow(unused_imports)] +use ping_bearer_auth::{Api, ApiNoContext, Client, ContextWrapperExt, models, + PingGetResponse, + }; +use clap::{App, Arg}; + +#[allow(unused_imports)] +use log::info; + +// swagger::Has may be unused if there are no examples +#[allow(unused_imports)] +use swagger::{AuthData, ContextBuilder, EmptyContext, Has, Push, XSpanIdString}; + +type ClientContext = swagger::make_context_ty!(ContextBuilder, EmptyContext, Option, XSpanIdString); + +// rt may be unused if there are no examples +#[allow(unused_mut)] +fn main() { + env_logger::init(); + + let matches = App::new("client") + .arg(Arg::with_name("operation") + .help("Sets the operation to run") + .possible_values(&[ + "PingGet", + ]) + .required(true) + .index(1)) + .arg(Arg::with_name("https") + .long("https") + .help("Whether to use HTTPS or not")) + .arg(Arg::with_name("host") + .long("host") + .takes_value(true) + .default_value("localhost") + .help("Hostname to contact")) + .arg(Arg::with_name("port") + .long("port") + .takes_value(true) + .default_value("8080") + .help("Port to contact")) + .get_matches(); + + let is_https = matches.is_present("https"); + let base_url = format!("{}://{}:{}", + if is_https { "https" } else { "http" }, + matches.value_of("host").unwrap(), + matches.value_of("port").unwrap()); + + let context: ClientContext = + swagger::make_context!(ContextBuilder, EmptyContext, None as Option, XSpanIdString::default()); + + let mut client : Box> = if matches.is_present("https") { + // Using Simple HTTPS + let client = Box::new(Client::try_new_https(&base_url) + .expect("Failed to create HTTPS client")); + Box::new(client.with_context(context)) + } else { + // Using HTTP + let client = Box::new(Client::try_new_http( + &base_url) + .expect("Failed to create HTTP client")); + Box::new(client.with_context(context)) + }; + + let mut rt = tokio::runtime::Runtime::new().unwrap(); + + match matches.value_of("operation") { + Some("PingGet") => { + let result = rt.block_on(client.ping_get( + )); + info!("{:?} (X-Span-ID: {:?})", result, (client.context() as &dyn Has).get().clone()); + }, + _ => { + panic!("Invalid operation provided") + } + } +} diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server-chain.pem b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server-chain.pem new file mode 100644 index 00000000000..47d7e201404 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server-chain.pem @@ -0,0 +1,66 @@ +Certificate: + Data: + Version: 1 (0x0) + Serial Number: 4096 (0x1000) + Signature Algorithm: sha256WithRSAEncryption + Issuer: C=US, CN=My CA + Validity + Not Before: May 23 16:00:23 2017 GMT + Not After : Apr 29 16:00:23 2117 GMT + Subject: CN=localhost, C=US + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:c9:d4:43:60:50:fc:d6:0f:38:4d:5d:5e:aa:7c: + c0:5e:a9:ec:d9:93:78:d3:93:72:28:41:f5:08:a5: + ea:ac:67:07:d7:1f:f7:7d:74:69:7e:46:89:20:4b: + 7a:2d:9b:02:08:e7:6f:0f:1d:0c:0f:c7:60:69:19: + 4b:df:7e:ca:75:94:0b:49:71:e3:6d:f2:e8:79:fd: + ed:0a:94:67:55:f3:ca:6b:61:ba:58:b7:2e:dd:7b: + ca:b9:02:9f:24:36:ac:26:8f:04:8f:81:c8:35:10: + f4:aa:33:b2:24:16:f8:f7:1e:ea:f7:16:fe:fa:34: + c3:dd:bb:2c:ba:7a:df:4d:e2:da:1e:e5:d2:28:44: + 6e:c8:96:e0:fd:09:0c:14:0c:31:dc:e0:ca:c1:a7: + 9b:bf:16:8c:f7:36:3f:1b:2e:dd:90:eb:45:78:51: + bf:59:22:1e:c6:8c:0a:69:88:e5:03:5e:73:b7:fc: + 93:7f:1b:46:1b:97:68:c5:c0:8b:35:1f:bb:1e:67: + 7f:55:b7:3b:55:3f:ea:f2:ca:db:cc:52:cd:16:89: + db:15:47:bd:f2:cd:6c:7a:d7:b4:1a:ac:c8:15:6c: + 6a:fb:77:c4:e9:f2:30:e0:14:24:66:65:6f:2a:e5: + 2d:cc:f6:81:ae:57:c8:d1:9b:38:90:dc:60:93:02: + 5e:cb + Exponent: 65537 (0x10001) + Signature Algorithm: sha256WithRSAEncryption + 1c:7c:39:e8:3d:49:b2:09:1e:68:5a:2f:74:18:f4:63:b5:8c: + f6:e6:a1:e3:4d:95:90:99:ef:32:5c:34:40:e8:55:13:0e:e0: + 1c:be:cd:ab:3f:64:38:99:5e:2b:c1:81:53:a0:18:a8:f6:ee: + 6a:33:73:6c:9a:73:9d:86:08:5d:c7:11:38:46:4c:cd:a0:47: + 37:8f:fe:a6:50:a9:02:21:99:42:86:5e:47:fe:65:56:60:1d: + 16:53:86:bd:e4:63:c5:69:cf:fa:30:51:ab:a1:c3:50:53:cc: + 66:1c:4c:ff:3f:2a:39:4d:a2:8f:9d:d1:a7:8b:22:e4:78:69: + 24:06:83:4d:cc:0a:c0:87:69:9b:bc:80:a9:d2:b7:a5:23:84: + 7e:a2:32:26:7c:78:0e:bd:db:cd:3b:69:18:33:b8:44:ef:96: + b4:99:86:ee:06:bd:51:1c:c7:a1:a4:0c:c4:4c:51:a0:df:ac: + 14:07:88:8e:d7:39:45:fe:52:e0:a3:4c:db:5d:7a:ab:4d:e4: + ca:06:e8:bd:74:6f:46:e7:93:4a:4f:1b:67:e7:a5:9f:ef:9c: + 02:49:d1:f2:d5:e9:53:ee:09:21:ac:08:c8:15:f7:af:35:b9: + 4f:11:0f:43:ae:46:8e:fd:5b:8d:a3:4e:a7:2c:b7:25:ed:e4: + e5:94:1d:e3 +-----BEGIN CERTIFICATE----- +MIICtTCCAZ0CAhAAMA0GCSqGSIb3DQEBCwUAMB0xCzAJBgNVBAYTAlVTMQ4wDAYD +VQQDEwVNeSBDQTAgFw0xNzA1MjMxNjAwMjNaGA8yMTE3MDQyOTE2MDAyM1owITES +MBAGA1UEAxMJbG9jYWxob3N0MQswCQYDVQQGEwJVUzCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMnUQ2BQ/NYPOE1dXqp8wF6p7NmTeNOTcihB9Qil6qxn +B9cf9310aX5GiSBLei2bAgjnbw8dDA/HYGkZS99+ynWUC0lx423y6Hn97QqUZ1Xz +ymthuli3Lt17yrkCnyQ2rCaPBI+ByDUQ9KozsiQW+Pce6vcW/vo0w927LLp6303i +2h7l0ihEbsiW4P0JDBQMMdzgysGnm78WjPc2Pxsu3ZDrRXhRv1kiHsaMCmmI5QNe +c7f8k38bRhuXaMXAizUfux5nf1W3O1U/6vLK28xSzRaJ2xVHvfLNbHrXtBqsyBVs +avt3xOnyMOAUJGZlbyrlLcz2ga5XyNGbOJDcYJMCXssCAwEAATANBgkqhkiG9w0B +AQsFAAOCAQEAHHw56D1JsgkeaFovdBj0Y7WM9uah402VkJnvMlw0QOhVEw7gHL7N +qz9kOJleK8GBU6AYqPbuajNzbJpznYYIXccROEZMzaBHN4/+plCpAiGZQoZeR/5l +VmAdFlOGveRjxWnP+jBRq6HDUFPMZhxM/z8qOU2ij53Rp4si5HhpJAaDTcwKwIdp +m7yAqdK3pSOEfqIyJnx4Dr3bzTtpGDO4RO+WtJmG7ga9URzHoaQMxExRoN+sFAeI +jtc5Rf5S4KNM2116q03kygbovXRvRueTSk8bZ+eln++cAknR8tXpU+4JIawIyBX3 +rzW5TxEPQ65Gjv1bjaNOpyy3Je3k5ZQd4w== +-----END CERTIFICATE----- diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server-key.pem b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server-key.pem new file mode 100644 index 00000000000..29c00682922 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJ1ENgUPzWDzhN +XV6qfMBeqezZk3jTk3IoQfUIpeqsZwfXH/d9dGl+RokgS3otmwII528PHQwPx2Bp +GUvffsp1lAtJceNt8uh5/e0KlGdV88prYbpYty7de8q5Ap8kNqwmjwSPgcg1EPSq +M7IkFvj3Hur3Fv76NMPduyy6et9N4toe5dIoRG7IluD9CQwUDDHc4MrBp5u/Foz3 +Nj8bLt2Q60V4Ub9ZIh7GjAppiOUDXnO3/JN/G0Ybl2jFwIs1H7seZ39VtztVP+ry +ytvMUs0WidsVR73yzWx617QarMgVbGr7d8Tp8jDgFCRmZW8q5S3M9oGuV8jRmziQ +3GCTAl7LAgMBAAECggEBAKEd1q9j14KWYc64s6KLthGbutyxsinMMbxbct11fdIk +6YhdF3fJ35ETg9IJDr6rWEN9ZRX+jStncNpVfFEs6ThVd3Eo/nI+EEGaaIkikR93 +X2a7fEPn7/yVHu70XdBN6L1bPDvHUeiy4W2hmRrgT90OjGm1rNRWHOm7yugOwIZu +HclzbR9Ca7EInFnotUiDQm9sw9VKHbJHqWx6OORdZrxR2ytYs0Qkq0XpGMvti2HW +7WAmKTg5QM8myXW7+/4iqb/u68wVBR2BBalShKmIf7lim9O3W2a1RjDdsvm/wNe9 +I+D+Iq825vpqkKXcrxYlpVg7hYiaQaW/MNsEb7lQRjECgYEA/RJYby0POW+/k0Jn +jO8UmJVEMiuGa8WIUu/JJWMOmzRCukjSRNQOkt7niQrZPJYE8W6clM6RJTolWf9L +IL6mIb+mRaoudUk8SHGDq7ho1iMg9GK8lhYxvKh1Q6uv8EyVSkgLknAEY0NANKC1 +zNdU5Dhven9aRX2gq9vP4XwMz2MCgYEAzCogQ7IFk+gkp3k491dOZnrGRoRCfuzo +4CJtyKFgOSd7BjmpcKkj0IPfVBjw6GjMIxfQRMTQmxAjjWevH45vG8l0Iiwz/gSp +81b5nsDEX5uv2Olcmcz5zxRFy36jOZ9ihMWinxcIlT2oDbyCdbruDKZq9ieJ9S8g +4qGx0OkwE3kCgYEA7CmAiU89U9YqqttfEq/RQoqY91CSwmO10d+ej9seuEtOsdRf +FIfnibulycdr7hP5TOxyBpO1802NqayJiWcgVYIpQf2MGTtcnCYCP+95NcvWZvj1 +EAJqK6nwtFO1fcOZ1ZXh5qfOEGujsPkAbsXLnKXlsiTCMvMHSxl3pu5Cbg0CgYBf +JjbZNctRrjv+7Qj2hPLd4dQsIxGWc7ToWENP4J2mpVa5hQAJqFovoHXhjKohtk2F +AWEn243Y5oGbMjo0e74edhmwn2cvuF64MM2vBem/ISCn98IXT6cQskMA3qkVfsl8 +VVs/x41ReGWs2TD3y0GMFbb9t1mdMfSiincDhNnKCQKBgGfeT4jKyYeCoCw4OLI1 +G75Gd0METt/IkppwODPpNwj3Rp9I5jctWZFA/3wCX/zk0HgBeou5AFNS4nQZ/X/L +L9axbSdR7UJTGkT1r4gu3rLkPV4Tk+8XM03/JT2cofMlzQBuhvl1Pn4SgKowz7hl +lS76ECw4Av3T0S34VW9Z5oye +-----END PRIVATE KEY----- diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server/main.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server/main.rs new file mode 100644 index 00000000000..17cee7267f7 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server/main.rs @@ -0,0 +1,25 @@ +//! Main binary entry point for ping_bearer_auth implementation. + +#![allow(missing_docs)] + +use clap::{App, Arg}; + +mod server; + + +/// Create custom server, wire it to the autogenerated router, +/// and pass it to the web server. +#[tokio::main] +async fn main() { + env_logger::init(); + + let matches = App::new("server") + .arg(Arg::with_name("https") + .long("https") + .help("Whether to use HTTPS or not")) + .get_matches(); + + let addr = "127.0.0.1:8080"; + + server::create(addr, matches.is_present("https")).await; +} diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server/server.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server/server.rs new file mode 100644 index 00000000000..9bb9de4523c --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/examples/server/server.rs @@ -0,0 +1,117 @@ +//! Main library entry point for ping_bearer_auth implementation. + +#![allow(unused_imports)] + +use async_trait::async_trait; +use futures::{future, Stream, StreamExt, TryFutureExt, TryStreamExt}; +use hyper::server::conn::Http; +use hyper::service::Service; +use log::info; +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +use openssl::ssl::SslAcceptorBuilder; +use std::future::Future; +use std::marker::PhantomData; +use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; +use swagger::{Has, XSpanIdString}; +use swagger::auth::MakeAllowAllAuthenticator; +use swagger::EmptyContext; +use tokio::net::TcpListener; + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + +use ping_bearer_auth::models; + +/// Builds an SSL implementation for Simple HTTPS from some hard-coded file names +pub async fn create(addr: &str, https: bool) { + let addr = addr.parse().expect("Failed to parse bind address"); + + let server = Server::new(); + + let service = MakeService::new(server); + + let service = MakeAllowAllAuthenticator::new(service, "cosmo"); + + let mut service = + ping_bearer_auth::server::context::MakeAddContext::<_, EmptyContext>::new( + service + ); + + if https { + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + { + unimplemented!("SSL is not implemented for the examples on MacOS, Windows or iOS"); + } + + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + { + let mut ssl = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).expect("Failed to create SSL Acceptor"); + + // Server authentication + ssl.set_private_key_file("examples/server-key.pem", SslFiletype::PEM).expect("Failed to set private key"); + ssl.set_certificate_chain_file("examples/server-chain.pem").expect("Failed to set cerificate chain"); + ssl.check_private_key().expect("Failed to check private key"); + + let tls_acceptor = Arc::new(ssl.build()); + let mut tcp_listener = TcpListener::bind(&addr).await.unwrap(); + let mut incoming = tcp_listener.incoming(); + + while let (Some(tcp), rest) = incoming.into_future().await { + if let Ok(tcp) = tcp { + let addr = tcp.peer_addr().expect("Unable to get remote address"); + let service = service.call(addr); + let tls_acceptor = Arc::clone(&tls_acceptor); + + tokio::spawn(async move { + let tls = tokio_openssl::accept(&*tls_acceptor, tcp).await.map_err(|_| ())?; + + let service = service.await.map_err(|_| ())?; + + Http::new().serve_connection(tls, service).await.map_err(|_| ()) + }); + } + + incoming = rest; + } + } + } else { + // Using HTTP + hyper::server::Server::bind(&addr).serve(service).await.unwrap() + } +} + +#[derive(Copy, Clone)] +pub struct Server { + marker: PhantomData, +} + +impl Server { + pub fn new() -> Self { + Server{marker: PhantomData} + } +} + + +use ping_bearer_auth::{ + Api, + PingGetResponse, +}; +use ping_bearer_auth::server::MakeService; +use std::error::Error; +use swagger::ApiError; + +#[async_trait] +impl Api for Server where C: Has + Send + Sync +{ + async fn ping_get( + &self, + context: &C) -> Result + { + let context = context.clone(); + info!("ping_get() - X-Span-ID: {:?}", context.get().0.clone()); + Err("Generic failuare".into()) + } + +} diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/src/client/mod.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/client/mod.rs new file mode 100644 index 00000000000..e97a83595c8 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/client/mod.rs @@ -0,0 +1,469 @@ +use async_trait::async_trait; +use futures::{Stream, future, future::BoxFuture, stream, future::TryFutureExt, future::FutureExt, stream::StreamExt}; +use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE}; +use hyper::{Body, Request, Response, service::Service, Uri}; +use percent_encoding::{utf8_percent_encode, AsciiSet}; +use std::borrow::Cow; +use std::convert::TryInto; +use std::io::{ErrorKind, Read}; +use std::error::Error; +use std::future::Future; +use std::fmt; +use std::marker::PhantomData; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use std::str; +use std::str::FromStr; +use std::string::ToString; +use std::task::{Context, Poll}; +use swagger::{ApiError, AuthData, BodyExt, Connector, DropContextService, Has, XSpanIdString}; +use url::form_urlencoded; + + +use crate::models; +use crate::header; + +/// https://url.spec.whatwg.org/#fragment-percent-encode-set +#[allow(dead_code)] +const FRAGMENT_ENCODE_SET: &AsciiSet = &percent_encoding::CONTROLS + .add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); + +/// This encode set is used for object IDs +/// +/// Aside from the special characters defined in the `PATH_SEGMENT_ENCODE_SET`, +/// the vertical bar (|) is encoded. +#[allow(dead_code)] +const ID_ENCODE_SET: &AsciiSet = &FRAGMENT_ENCODE_SET.add(b'|'); + +use crate::{Api, + PingGetResponse + }; + +/// Convert input into a base path, e.g. "http://example:123". Also checks the scheme as it goes. +fn into_base_path(input: impl TryInto, correct_scheme: Option<&'static str>) -> Result { + // First convert to Uri, since a base path is a subset of Uri. + let uri = input.try_into()?; + + let scheme = uri.scheme_str().ok_or(ClientInitError::InvalidScheme)?; + + // Check the scheme if necessary + if let Some(correct_scheme) = correct_scheme { + if scheme != correct_scheme { + return Err(ClientInitError::InvalidScheme); + } + } + + let host = uri.host().ok_or_else(|| ClientInitError::MissingHost)?; + let port = uri.port_u16().map(|x| format!(":{}", x)).unwrap_or_default(); + Ok(format!("{}://{}{}{}", scheme, host, port, uri.path().trim_end_matches('/'))) +} + +/// A client that implements the API by making HTTP calls out to a server. +pub struct Client where + S: Service< + (Request, C), + Response=Response> + Clone + Sync + Send + 'static, + S::Future: Send + 'static, + S::Error: Into + fmt::Display, + C: Clone + Send + Sync + 'static +{ + /// Inner service + client_service: S, + + /// Base path of the API + base_path: String, + + /// Marker + marker: PhantomData, +} + +impl fmt::Debug for Client where + S: Service< + (Request, C), + Response=Response> + Clone + Sync + Send + 'static, + S::Future: Send + 'static, + S::Error: Into + fmt::Display, + C: Clone + Send + Sync + 'static +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Client {{ base_path: {} }}", self.base_path) + } +} + +impl Clone for Client where + S: Service< + (Request, C), + Response=Response> + Clone + Sync + Send + 'static, + S::Future: Send + 'static, + S::Error: Into + fmt::Display, + C: Clone + Send + Sync + 'static +{ + fn clone(&self) -> Self { + Self { + client_service: self.client_service.clone(), + base_path: self.base_path.clone(), + marker: PhantomData, + } + } +} + +impl Client, C>, C> where + Connector: hyper::client::connect::Connect + Clone + Send + Sync + 'static, + C: Clone + Send + Sync + 'static, +{ + /// Create a client with a custom implementation of hyper::client::Connect. + /// + /// Intended for use with custom implementations of connect for e.g. protocol logging + /// or similar functionality which requires wrapping the transport layer. When wrapping a TCP connection, + /// this function should be used in conjunction with `swagger::Connector::builder()`. + /// + /// For ordinary tcp connections, prefer the use of `try_new_http`, `try_new_https` + /// and `try_new_https_mutual`, to avoid introducing a dependency on the underlying transport layer. + /// + /// # Arguments + /// + /// * `base_path` - base path of the client API, i.e. "http://www.my-api-implementation.com" + /// * `protocol` - Which protocol to use when constructing the request url, e.g. `Some("http")` + /// * `connector` - Implementation of `hyper::client::Connect` to use for the client + pub fn try_new_with_connector( + base_path: &str, + protocol: Option<&'static str>, + connector: Connector, + ) -> Result + { + let client_service = hyper::client::Client::builder().build(connector); + let client_service = DropContextService::new(client_service); + + Ok(Self { + client_service, + base_path: into_base_path(base_path, protocol)?, + marker: PhantomData, + }) + } +} + +#[derive(Debug, Clone)] +pub enum HyperClient { + Http(hyper::client::Client), + Https(hyper::client::Client), +} + +impl Service> for HyperClient { + type Response = Response; + type Error = hyper::Error; + type Future = hyper::client::ResponseFuture; + + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + match self { + HyperClient::Http(client) => client.poll_ready(cx), + HyperClient::Https(client) => client.poll_ready(cx), + } + } + + fn call(&mut self, req: Request) -> Self::Future { + match self { + HyperClient::Http(client) => client.call(req), + HyperClient::Https(client) => client.call(req) + } + } +} + +impl Client, C> where + C: Clone + Send + Sync + 'static, +{ + /// Create an HTTP client. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "http://www.my-api-implementation.com" + pub fn try_new( + base_path: &str, + ) -> Result { + let uri = Uri::from_str(base_path)?; + + let scheme = uri.scheme_str().ok_or(ClientInitError::InvalidScheme)?; + let scheme = scheme.to_ascii_lowercase(); + + let connector = Connector::builder(); + + let client_service = match scheme.as_str() { + "http" => { + HyperClient::Http(hyper::client::Client::builder().build(connector.build())) + }, + "https" => { + let connector = connector.https() + .build() + .map_err(|e| ClientInitError::SslError(e))?; + HyperClient::Https(hyper::client::Client::builder().build(connector)) + }, + _ => { + return Err(ClientInitError::InvalidScheme); + } + }; + + let client_service = DropContextService::new(client_service); + + Ok(Self { + client_service, + base_path: into_base_path(base_path, None)?, + marker: PhantomData, + }) + } +} + +impl Client, C>, C> where + C: Clone + Send + Sync + 'static +{ + /// Create an HTTP client. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "http://www.my-api-implementation.com" + pub fn try_new_http( + base_path: &str, + ) -> Result { + let http_connector = Connector::builder().build(); + + Self::try_new_with_connector(base_path, Some("http"), http_connector) + } +} + +#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] +type HttpsConnector = hyper_tls::HttpsConnector; + +#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] +type HttpsConnector = hyper_openssl::HttpsConnector; + +impl Client, C>, C> where + C: Clone + Send + Sync + 'static +{ + /// Create a client with a TLS connection to the server + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "https://www.my-api-implementation.com" + pub fn try_new_https(base_path: &str) -> Result + { + let https_connector = Connector::builder() + .https() + .build() + .map_err(|e| ClientInitError::SslError(e))?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a TLS connection to the server using a pinned certificate + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "https://www.my-api-implementation.com" + /// * `ca_certificate` - Path to CA certificate used to authenticate the server + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + pub fn try_new_https_pinned( + base_path: &str, + ca_certificate: CA, + ) -> Result + where + CA: AsRef, + { + let https_connector = Connector::builder() + .https() + .pin_server_certificate(ca_certificate) + .build() + .map_err(|e| ClientInitError::SslError(e))?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } + + /// Create a client with a mutually authenticated TLS connection to the server. + /// + /// # Arguments + /// * `base_path` - base path of the client API, i.e. "https://www.my-api-implementation.com" + /// * `ca_certificate` - Path to CA certificate used to authenticate the server + /// * `client_key` - Path to the client private key + /// * `client_certificate` - Path to the client's public certificate associated with the private key + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + pub fn try_new_https_mutual( + base_path: &str, + ca_certificate: CA, + client_key: K, + client_certificate: D, + ) -> Result + where + CA: AsRef, + K: AsRef, + D: AsRef, + { + let https_connector = Connector::builder() + .https() + .pin_server_certificate(ca_certificate) + .client_authentication(client_key, client_certificate) + .build() + .map_err(|e| ClientInitError::SslError(e))?; + Self::try_new_with_connector(base_path, Some("https"), https_connector) + } +} + +impl Client where + S: Service< + (Request, C), + Response=Response> + Clone + Sync + Send + 'static, + S::Future: Send + 'static, + S::Error: Into + fmt::Display, + C: Clone + Send + Sync + 'static +{ + /// Constructor for creating a `Client` by passing in a pre-made `hyper::service::Service` / + /// `tower::Service` + /// + /// This allows adding custom wrappers around the underlying transport, for example for logging. + pub fn try_new_with_client_service( + client_service: S, + base_path: &str, + ) -> Result + { + Ok(Self { + client_service, + base_path: into_base_path(base_path, None)?, + marker: PhantomData, + }) + } +} + +/// Error type failing to create a Client +#[derive(Debug)] +pub enum ClientInitError { + /// Invalid URL Scheme + InvalidScheme, + + /// Invalid URI + InvalidUri(hyper::http::uri::InvalidUri), + + /// Missing Hostname + MissingHost, + + /// SSL Connection Error + #[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))] + SslError(native_tls::Error), + + /// SSL Connection Error + #[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))] + SslError(openssl::error::ErrorStack), +} + +impl From for ClientInitError { + fn from(err: hyper::http::uri::InvalidUri) -> ClientInitError { + ClientInitError::InvalidUri(err) + } +} + +impl fmt::Display for ClientInitError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let s: &dyn fmt::Debug = self; + s.fmt(f) + } +} + +impl Error for ClientInitError { + fn description(&self) -> &str { + "Failed to produce a hyper client." + } +} + +#[async_trait] +impl Api for Client where + S: Service< + (Request, C), + Response=Response> + Clone + Sync + Send + 'static, + S::Future: Send + 'static, + S::Error: Into + fmt::Display, + C: Has + Has> + Clone + Send + Sync + 'static, +{ + fn poll_ready(&self, cx: &mut Context) -> Poll> { + match self.client_service.clone().poll_ready(cx) { + Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())), + Poll::Ready(Ok(o)) => Poll::Ready(Ok(o)), + Poll::Pending => Poll::Pending, + } + } + + async fn ping_get( + &self, + context: &C) -> Result + { + let mut client_service = self.client_service.clone(); + let mut uri = format!( + "{}/ping", + self.base_path + ); + + // Query parameters + let query_string = { + let mut query_string = form_urlencoded::Serializer::new("".to_owned()); + query_string.finish() + }; + if !query_string.is_empty() { + uri += "?"; + uri += &query_string; + } + + let uri = match Uri::from_str(&uri) { + Ok(uri) => uri, + Err(err) => return Err(ApiError(format!("Unable to build URI: {}", err))), + }; + + let mut request = match Request::builder() + .method("GET") + .uri(uri) + .body(Body::empty()) { + Ok(req) => req, + Err(e) => return Err(ApiError(format!("Unable to create request: {}", e))) + }; + + let header = HeaderValue::from_str(Has::::get(context).0.clone().to_string().as_str()); + request.headers_mut().insert(HeaderName::from_static("x-span-id"), match header { + Ok(h) => h, + Err(e) => return Err(ApiError(format!("Unable to create X-Span ID header value: {}", e))) + }); + + if let Some(auth_data) = Has::>::get(context).as_ref() { + // Currently only authentication with Basic and Bearer are supported + match auth_data { + &AuthData::Bearer(ref bearer_header) => { + let auth = swagger::auth::Header(bearer_header.clone()); + let header = match HeaderValue::from_str(&format!("{}", auth)) { + Ok(h) => h, + Err(e) => return Err(ApiError(format!("Unable to create Authorization header: {}", e))) + }; + request.headers_mut().insert( + hyper::header::AUTHORIZATION, + header); + }, + _ => {} + } + } + + let mut response = client_service.call((request, context.clone())) + .map_err(|e| ApiError(format!("No response received: {}", e))).await?; + + match response.status().as_u16() { + 201 => { + let body = response.into_body(); + Ok( + PingGetResponse::OK + ) + } + code => { + let headers = response.headers().clone(); + let body = response.into_body() + .take(100) + .to_raw().await; + Err(ApiError(format!("Unexpected response code {}:\n{:?}\n\n{}", + code, + headers, + match body { + Ok(body) => match String::from_utf8(body) { + Ok(body) => body, + Err(e) => format!("", e), + }, + Err(e) => format!("", e), + } + ))) + } + } + } + +} diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/src/context.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/context.rs new file mode 100644 index 00000000000..6f030053252 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/context.rs @@ -0,0 +1,124 @@ +use futures::future::BoxFuture; +use hyper::header::HeaderName; +use hyper::{Error, Request, Response, StatusCode, service::Service}; +use url::form_urlencoded; +use std::default::Default; +use std::io; +use std::marker::PhantomData; +use std::task::{Poll, Context}; +use swagger::auth::{AuthData, Authorization, Bearer, Scopes}; +use swagger::{EmptyContext, Has, Pop, Push, XSpanIdString}; +use crate::Api; + +pub struct MakeAddContext { + inner: T, + marker: PhantomData, +} + +impl MakeAddContext +where + A: Default + Push, + B: Push, Result = C>, + C: Push, Result = D>, +{ + pub fn new(inner: T) -> MakeAddContext { + MakeAddContext { + inner, + marker: PhantomData, + } + } +} + +// Make a service that adds context. +impl Service for + MakeAddContext +where + Target: Send, + A: Default + Push + Send, + B: Push, Result = C>, + C: Push, Result = D>, + D: Send + 'static, + T: Service + Send, + T::Future: Send + 'static +{ + type Error = T::Error; + type Response = AddContext; + type Future = BoxFuture<'static, Result>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, target: Target) -> Self::Future { + let service = self.inner.call(target); + + Box::pin(async move { + Ok(AddContext::new(service.await?)) + }) + } +} + +/// Middleware to add context data from the request +pub struct AddContext +where + A: Default + Push, + B: Push, Result = C>, + C: Push, Result = D> +{ + inner: T, + marker: PhantomData, +} + +impl AddContext +where + A: Default + Push, + B: Push, Result = C>, + C: Push, Result = D>, +{ + pub fn new(inner: T) -> Self { + AddContext { + inner, + marker: PhantomData, + } + } +} + +impl Service> for AddContext + where + A: Default + Push, + B: Push, Result=C>, + C: Push, Result=D>, + D: Send + 'static, + T: Service<(Request, D)> +{ + type Error = T::Error; + type Future = T::Future; + type Response = T::Response; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + + fn call(&mut self, request: Request) -> Self::Future { + let context = A::default().push(XSpanIdString::get_or_generate(&request)); + let headers = request.headers(); + + { + use swagger::auth::Bearer; + use std::ops::Deref; + if let Some(bearer) = swagger::auth::from_headers::(&headers) { + let auth_data = AuthData::Bearer(bearer); + let context = context.push(Some(auth_data)); + let context = context.push(None::); + + return self.inner.call((request, context)) + } + } + + let context = context.push(None::); + let context = context.push(None::); + + self.inner.call((request, context)) + } +} diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/src/header.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/header.rs new file mode 100644 index 00000000000..5bc6ebe929b --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/header.rs @@ -0,0 +1,180 @@ +use chrono::{DateTime, Utc}; +use hyper::header::HeaderValue; +use std::convert::TryFrom; +use std::fmt; +use std::ops::Deref; + +/// 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 hyper::header::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-server/output/ping-bearer-auth/src/lib.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/lib.rs new file mode 100644 index 00000000000..f92689462e1 --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/lib.rs @@ -0,0 +1,101 @@ +#![allow(missing_docs, trivial_casts, unused_variables, unused_mut, unused_imports, unused_extern_crates, non_camel_case_types)] + +use async_trait::async_trait; +use futures::Stream; +use std::error::Error; +use std::task::{Poll, Context}; +use swagger::{ApiError, ContextWrapper}; + +type ServiceError = Box; + +pub const BASE_PATH: &'static str = ""; +pub const API_VERSION: &'static str = "1.0"; + +#[derive(Debug, PartialEq)] +pub enum PingGetResponse { + /// OK + OK +} + +/// API +#[async_trait] +pub trait Api { + fn poll_ready(&self, _cx: &mut Context) -> Poll>> { + Poll::Ready(Ok(())) + } + + async fn ping_get( + &self, + context: &C) -> Result; + +} + +/// API where `Context` isn't passed on every API call +#[async_trait] +pub trait ApiNoContext { + + fn poll_ready(&self, _cx: &mut Context) -> Poll>>; + + fn context(&self) -> &C; + + async fn ping_get( + &self, + ) -> Result; + +} + +/// Trait to extend an API to make it easy to bind it to a context. +pub trait ContextWrapperExt where Self: Sized +{ + /// Binds this API to a context. + fn with_context(self: Self, context: C) -> ContextWrapper; +} + +impl + Send + Sync, C: Clone + Send + Sync> ContextWrapperExt for T { + fn with_context(self: T, context: C) -> ContextWrapper { + ContextWrapper::::new(self, context) + } +} + +#[async_trait] +impl + Send + Sync, C: Clone + Send + Sync> ApiNoContext for ContextWrapper { + fn poll_ready(&self, cx: &mut Context) -> Poll> { + self.api().poll_ready(cx) + } + + fn context(&self) -> &C { + ContextWrapper::context(self) + } + + async fn ping_get( + &self, + ) -> Result + { + let context = self.context().clone(); + self.api().ping_get(&context).await + } + +} + + +#[cfg(feature = "client")] +pub mod client; + +// Re-export Client as a top-level name +#[cfg(feature = "client")] +pub use client::Client; + +#[cfg(feature = "server")] +pub mod server; + +// Re-export router() as a top-level name +#[cfg(feature = "server")] +pub use self::server::Service; + +#[cfg(feature = "server")] +pub mod context; + +pub mod models; + +#[cfg(any(feature = "client", feature = "server"))] +pub(crate) mod header; diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/src/models.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/models.rs new file mode 100644 index 00000000000..aaf4182a33e --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/models.rs @@ -0,0 +1,6 @@ +#![allow(unused_qualifications)] + +use crate::models; +#[cfg(any(feature = "client", feature = "server"))] +use crate::header; + diff --git a/samples/server/petstore/rust-server/output/ping-bearer-auth/src/server/mod.rs b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/server/mod.rs new file mode 100644 index 00000000000..700d88538bb --- /dev/null +++ b/samples/server/petstore/rust-server/output/ping-bearer-auth/src/server/mod.rs @@ -0,0 +1,200 @@ +use futures::{future, future::BoxFuture, Stream, stream, future::FutureExt, stream::TryStreamExt}; +use hyper::{Request, Response, StatusCode, Body, HeaderMap}; +use hyper::header::{HeaderName, HeaderValue, CONTENT_TYPE}; +use log::warn; +#[allow(unused_imports)] +use std::convert::{TryFrom, TryInto}; +use std::error::Error; +use std::future::Future; +use std::marker::PhantomData; +use std::task::{Context, Poll}; +use swagger::{ApiError, BodyExt, Has, RequestParser, XSpanIdString}; +pub use swagger::auth::Authorization; +use swagger::auth::Scopes; +use url::form_urlencoded; + +#[allow(unused_imports)] +use crate::models; +use crate::header; + +pub use crate::context; + +type ServiceFuture = BoxFuture<'static, Result, crate::ServiceError>>; + +use crate::{Api, + PingGetResponse +}; + +mod paths { + use lazy_static::lazy_static; + + lazy_static! { + pub static ref GLOBAL_REGEX_SET: regex::RegexSet = regex::RegexSet::new(vec![ + r"^/ping$" + ]) + .expect("Unable to create global regex set"); + } + pub(crate) static ID_PING: usize = 0; +} + +pub struct MakeService where + T: Api + Clone + Send + 'static, + C: Has + Has> + Send + Sync + 'static +{ + api_impl: T, + marker: PhantomData, +} + +impl MakeService where + T: Api + Clone + Send + 'static, + C: Has + Has> + Send + Sync + 'static +{ + pub fn new(api_impl: T) -> Self { + MakeService { + api_impl, + marker: PhantomData + } + } +} + +impl hyper::service::Service for MakeService where + T: Api + Clone + Send + 'static, + C: Has + Has> + Send + Sync + 'static +{ + type Response = Service; + type Error = crate::ServiceError; + type Future = future::Ready>; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, target: Target) -> Self::Future { + futures::future::ok(Service::new( + self.api_impl.clone(), + )) + } +} + +fn method_not_allowed() -> Result, crate::ServiceError> { + Ok( + Response::builder().status(StatusCode::METHOD_NOT_ALLOWED) + .body(Body::empty()) + .expect("Unable to create Method Not Allowed response") + ) +} + +pub struct Service where + T: Api + Clone + Send + 'static, + C: Has + Has> + Send + Sync + 'static +{ + api_impl: T, + marker: PhantomData, +} + +impl Service where + T: Api + Clone + Send + 'static, + C: Has + Has> + Send + Sync + 'static +{ + pub fn new(api_impl: T) -> Self { + Service { + api_impl: api_impl, + marker: PhantomData + } + } +} + +impl Clone for Service where + T: Api + Clone + Send + 'static, + C: Has + Has> + Send + Sync + 'static +{ + fn clone(&self) -> Self { + Service { + api_impl: self.api_impl.clone(), + marker: self.marker.clone(), + } + } +} + +impl hyper::service::Service<(Request, C)> for Service where + T: Api + Clone + Send + Sync + 'static, + C: Has + Has> + Send + Sync + 'static +{ + type Response = Response; + type Error = crate::ServiceError; + type Future = ServiceFuture; + + fn poll_ready(&mut self, cx: &mut Context) -> Poll> { + self.api_impl.poll_ready(cx) + } + + fn call(&mut self, req: (Request, C)) -> Self::Future { async fn run(mut api_impl: T, req: (Request, C)) -> Result, crate::ServiceError> where + T: Api + Clone + Send + 'static, + C: Has + Has> + Send + Sync + 'static + { + let (request, context) = req; + let (parts, body) = request.into_parts(); + let (method, uri, headers) = (parts.method, parts.uri, parts.headers); + let path = paths::GLOBAL_REGEX_SET.matches(uri.path()); + + match &method { + + // PingGet - GET /ping + &hyper::Method::GET if path.matched(paths::ID_PING) => { + { + let authorization = match (&context as &dyn Has>).get() { + &Some(ref authorization) => authorization, + &None => return Ok(Response::builder() + .status(StatusCode::FORBIDDEN) + .body(Body::from("Unauthenticated")) + .expect("Unable to create Authentication Forbidden response")), + }; + } + + let result = api_impl.ping_get( + &context + ).await; + let mut response = Response::new(Body::empty()); + response.headers_mut().insert( + HeaderName::from_static("x-span-id"), + HeaderValue::from_str((&context as &dyn Has).get().0.clone().to_string().as_str()) + .expect("Unable to create X-Span-ID header value")); + + match result { + Ok(rsp) => match rsp { + PingGetResponse::OK + => { + *response.status_mut() = StatusCode::from_u16(201).expect("Unable to turn 201 into a StatusCode"); + }, + }, + Err(_) => { + // Application code returned an error. This should not happen, as the implementation should + // return a valid response. + *response.status_mut() = StatusCode::INTERNAL_SERVER_ERROR; + *response.body_mut() = Body::from("An internal error occurred"); + }, + } + + Ok(response) + }, + + _ if path.matched(paths::ID_PING) => method_not_allowed(), + _ => Ok(Response::builder().status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .expect("Unable to create Not Found response")) + } + } Box::pin(run(self.api_impl.clone(), req)) } +} + +/// Request parser for `Api`. +pub struct ApiRequestParser; +impl RequestParser for ApiRequestParser { + fn parse_operation_id(request: &Request) -> Result<&'static str, ()> { + let path = paths::GLOBAL_REGEX_SET.matches(request.uri().path()); + match request.method() { + // PingGet - GET /ping + &hyper::Method::GET if path.matched(paths::ID_PING) => Ok("PingGet"), + _ => Err(()), + } + } +}