[Rust Server] Add support for Bearer token authentication (#7840)

* [rust-server] Add support for Bearer auth

- Added bearer auth to the security features for rust server
- Supplemented the basic auth condition in the context template to handled basic auth and bearer auth separately.
- Repurpose an exising sample to confirm the code generation works as expected.

* Update docs

* Update readme for bearer tokens
This commit is contained in:
Matthew Dowdell 2021-01-30 12:07:46 +00:00 committed by GitHub
parent 4ea4ceb0ff
commit b4154be8d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 1781 additions and 3 deletions

View File

@ -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

View File

@ -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

View File

@ -94,6 +94,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
.securityFeatures(EnumSet.of(
SecurityFeature.ApiKey,
SecurityFeature.BasicAuth,
SecurityFeature.BearerToken,
SecurityFeature.OAuth2_Implicit
))
.excludeGlobalFeatures(

View File

@ -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}}}

View File

@ -106,6 +106,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
{{#authMethods}}
{{#isBasic}}
{{#isBasicBasic}}
{
use swagger::auth::Basic;
use std::ops::Deref;
@ -117,6 +118,20 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
return self.inner.call((request, context))
}
}
{{/isBasicBasic}}
{{#isBasicBearer}}
{
use swagger::auth::Bearer;
use std::ops::Deref;
if let Some(bearer) = swagger::auth::from_headers::<Bearer>(&headers) {
let auth_data = AuthData::Bearer(bearer);
let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>);
return self.inner.call((request, context))
}
}
{{/isBasicBearer}}
{{/isBasic}}
{{#isOAuth}}
{

View File

@ -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: []

View File

@ -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
]

View File

@ -0,0 +1,2 @@
target
Cargo.lock

View File

@ -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

View File

@ -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

View File

@ -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"]

View File

@ -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 <example-name>
```
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

View File

@ -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

View File

@ -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)

View File

@ -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-----

View File

@ -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<AuthData>, 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<AuthData>, XSpanIdString::default());
let mut client : Box<dyn ApiNoContext<ClientContext>> = 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<XSpanIdString>).get().clone());
},
_ => {
panic!("Invalid operation provided")
}
}
}

View File

@ -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-----

View File

@ -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-----

View File

@ -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;
}

View File

@ -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<C> {
marker: PhantomData<C>,
}
impl<C> Server<C> {
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<C> Api<C> for Server<C> where C: Has<XSpanIdString> + Send + Sync
{
async fn ping_get(
&self,
context: &C) -> Result<PingGetResponse, ApiError>
{
let context = context.clone();
info!("ping_get() - X-Span-ID: {:?}", context.get().0.clone());
Err("Generic failuare".into())
}
}

View File

@ -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<Uri, Error=hyper::http::uri::InvalidUri>, correct_scheme: Option<&'static str>) -> Result<String, ClientInitError> {
// 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<S, C> where
S: Service<
(Request<Body>, C),
Response=Response<Body>> + Clone + Sync + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<crate::ServiceError> + fmt::Display,
C: Clone + Send + Sync + 'static
{
/// Inner service
client_service: S,
/// Base path of the API
base_path: String,
/// Marker
marker: PhantomData<fn(C)>,
}
impl<S, C> fmt::Debug for Client<S, C> where
S: Service<
(Request<Body>, C),
Response=Response<Body>> + Clone + Sync + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<crate::ServiceError> + 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<S, C> Clone for Client<S, C> where
S: Service<
(Request<Body>, C),
Response=Response<Body>> + Clone + Sync + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<crate::ServiceError> + 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<Connector, C> Client<DropContextService<hyper::client::Client<Connector, Body>, 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<Self, ClientInitError>
{
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<hyper::client::HttpConnector, Body>),
Https(hyper::client::Client<HttpsConnector, Body>),
}
impl Service<Request<Body>> for HyperClient {
type Response = Response<Body>;
type Error = hyper::Error;
type Future = hyper::client::ResponseFuture;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
match self {
HyperClient::Http(client) => client.poll_ready(cx),
HyperClient::Https(client) => client.poll_ready(cx),
}
}
fn call(&mut self, req: Request<Body>) -> Self::Future {
match self {
HyperClient::Http(client) => client.call(req),
HyperClient::Https(client) => client.call(req)
}
}
}
impl<C> Client<DropContextService<HyperClient, 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(
base_path: &str,
) -> Result<Self, ClientInitError> {
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<C> Client<DropContextService<hyper::client::Client<hyper::client::HttpConnector, Body>, 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<Self, ClientInitError> {
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<hyper::client::HttpConnector>;
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
type HttpsConnector = hyper_openssl::HttpsConnector<hyper::client::HttpConnector>;
impl<C> Client<DropContextService<hyper::client::Client<HttpsConnector, Body>, 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<Self, ClientInitError>
{
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<CA>(
base_path: &str,
ca_certificate: CA,
) -> Result<Self, ClientInitError>
where
CA: AsRef<Path>,
{
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<CA, K, D>(
base_path: &str,
ca_certificate: CA,
client_key: K,
client_certificate: D,
) -> Result<Self, ClientInitError>
where
CA: AsRef<Path>,
K: AsRef<Path>,
D: AsRef<Path>,
{
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<S, C> Client<S, C> where
S: Service<
(Request<Body>, C),
Response=Response<Body>> + Clone + Sync + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<crate::ServiceError> + 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<Self, ClientInitError>
{
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<hyper::http::uri::InvalidUri> 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<S, C> Api<C> for Client<S, C> where
S: Service<
(Request<Body>, C),
Response=Response<Body>> + Clone + Sync + Send + 'static,
S::Future: Send + 'static,
S::Error: Into<crate::ServiceError> + fmt::Display,
C: Has<XSpanIdString> + Has<Option<AuthData>> + Clone + Send + Sync + 'static,
{
fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), crate::ServiceError>> {
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<PingGetResponse, ApiError>
{
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::<XSpanIdString>::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::<Option<AuthData>>::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!("<Body was not UTF8: {:?}>", e),
},
Err(e) => format!("<Failed to read body: {}>", e),
}
)))
}
}
}
}

View File

@ -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<T, A> {
inner: T,
marker: PhantomData<A>,
}
impl<T, A, B, C, D> MakeAddContext<T, A>
where
A: Default + Push<XSpanIdString, Result = B>,
B: Push<Option<AuthData>, Result = C>,
C: Push<Option<Authorization>, Result = D>,
{
pub fn new(inner: T) -> MakeAddContext<T, A> {
MakeAddContext {
inner,
marker: PhantomData,
}
}
}
// Make a service that adds context.
impl<Target, T, A, B, C, D> Service<Target> for
MakeAddContext<T, A>
where
Target: Send,
A: Default + Push<XSpanIdString, Result = B> + Send,
B: Push<Option<AuthData>, Result = C>,
C: Push<Option<Authorization>, Result = D>,
D: Send + 'static,
T: Service<Target> + Send,
T::Future: Send + 'static
{
type Error = T::Error;
type Response = AddContext<T::Response, A, B, C, D>;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
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<T, A, B, C, D>
where
A: Default + Push<XSpanIdString, Result = B>,
B: Push<Option<AuthData>, Result = C>,
C: Push<Option<Authorization>, Result = D>
{
inner: T,
marker: PhantomData<A>,
}
impl<T, A, B, C, D> AddContext<T, A, B, C, D>
where
A: Default + Push<XSpanIdString, Result = B>,
B: Push<Option<AuthData>, Result = C>,
C: Push<Option<Authorization>, Result = D>,
{
pub fn new(inner: T) -> Self {
AddContext {
inner,
marker: PhantomData,
}
}
}
impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C, D>
where
A: Default + Push<XSpanIdString, Result=B>,
B: Push<Option<AuthData>, Result=C>,
C: Push<Option<Authorization>, Result=D>,
D: Send + 'static,
T: Service<(Request<ReqBody>, D)>
{
type Error = T::Error;
type Future = T::Future;
type Response = T::Response;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request<ReqBody>) -> 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::<Bearer>(&headers) {
let auth_data = AuthData::Bearer(bearer);
let context = context.push(Some(auth_data));
let context = context.push(None::<Authorization>);
return self.inner.call((request, context))
}
}
let context = context.push(None::<AuthData>);
let context = context.push(None::<Authorization>);
self.inner.call((request, context))
}
}

View File

@ -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<T>(pub T);
// Generic implementations
impl<T> Deref for IntoHeaderValue<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
// Derive for each TryFrom<T> in hyper::header::HeaderValue
macro_rules! ihv_generate {
($t:ident) => {
impl TryFrom<HeaderValue> for IntoHeaderValue<$t> {
type Error = String;
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
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<IntoHeaderValue<$t>> for HeaderValue {
type Error = String;
fn try_from(hdr_value: IntoHeaderValue<$t>) -> Result<Self, Self::Error> {
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<String>
impl TryFrom<HeaderValue> for IntoHeaderValue<Vec<String>> {
type Error = String;
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
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<IntoHeaderValue<Vec<String>>> for HeaderValue {
type Error = String;
fn try_from(hdr_value: IntoHeaderValue<Vec<String>>) -> Result<Self, Self::Error> {
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<HeaderValue> for IntoHeaderValue<String> {
type Error = String;
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
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<IntoHeaderValue<String>> for HeaderValue {
type Error = String;
fn try_from(hdr_value: IntoHeaderValue<String>) -> Result<Self, Self::Error> {
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<HeaderValue> for IntoHeaderValue<bool> {
type Error = String;
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
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<IntoHeaderValue<bool>> for HeaderValue {
type Error = String;
fn try_from(hdr_value: IntoHeaderValue<bool>) -> Result<Self, Self::Error> {
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<HeaderValue> for IntoHeaderValue<DateTime<Utc>> {
type Error = String;
fn try_from(hdr_value: HeaderValue) -> Result<Self, Self::Error> {
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<IntoHeaderValue<DateTime<Utc>>> for HeaderValue {
type Error = String;
fn try_from(hdr_value: IntoHeaderValue<DateTime<Utc>>) -> Result<Self, Self::Error> {
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)),
}
}
}

View File

@ -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<dyn Error + Send + Sync + 'static>;
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<C: Send + Sync> {
fn poll_ready(&self, _cx: &mut Context) -> Poll<Result<(), Box<dyn Error + Send + Sync + 'static>>> {
Poll::Ready(Ok(()))
}
async fn ping_get(
&self,
context: &C) -> Result<PingGetResponse, ApiError>;
}
/// API where `Context` isn't passed on every API call
#[async_trait]
pub trait ApiNoContext<C: Send + Sync> {
fn poll_ready(&self, _cx: &mut Context) -> Poll<Result<(), Box<dyn Error + Send + Sync + 'static>>>;
fn context(&self) -> &C;
async fn ping_get(
&self,
) -> Result<PingGetResponse, ApiError>;
}
/// Trait to extend an API to make it easy to bind it to a context.
pub trait ContextWrapperExt<C: Send + Sync> where Self: Sized
{
/// Binds this API to a context.
fn with_context(self: Self, context: C) -> ContextWrapper<Self, C>;
}
impl<T: Api<C> + Send + Sync, C: Clone + Send + Sync> ContextWrapperExt<C> for T {
fn with_context(self: T, context: C) -> ContextWrapper<T, C> {
ContextWrapper::<T, C>::new(self, context)
}
}
#[async_trait]
impl<T: Api<C> + Send + Sync, C: Clone + Send + Sync> ApiNoContext<C> for ContextWrapper<T, C> {
fn poll_ready(&self, cx: &mut Context) -> Poll<Result<(), ServiceError>> {
self.api().poll_ready(cx)
}
fn context(&self) -> &C {
ContextWrapper::context(self)
}
async fn ping_get(
&self,
) -> Result<PingGetResponse, ApiError>
{
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;

View File

@ -0,0 +1,6 @@
#![allow(unused_qualifications)]
use crate::models;
#[cfg(any(feature = "client", feature = "server"))]
use crate::header;

View File

@ -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<Response<Body>, 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<T, C> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
{
api_impl: T,
marker: PhantomData<C>,
}
impl<T, C> MakeService<T, C> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
{
pub fn new(api_impl: T) -> Self {
MakeService {
api_impl,
marker: PhantomData
}
}
}
impl<T, C, Target> hyper::service::Service<Target> for MakeService<T, C> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
{
type Response = Service<T, C>;
type Error = crate::ServiceError;
type Future = future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
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<Response<Body>, crate::ServiceError> {
Ok(
Response::builder().status(StatusCode::METHOD_NOT_ALLOWED)
.body(Body::empty())
.expect("Unable to create Method Not Allowed response")
)
}
pub struct Service<T, C> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
{
api_impl: T,
marker: PhantomData<C>,
}
impl<T, C> Service<T, C> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
{
pub fn new(api_impl: T) -> Self {
Service {
api_impl: api_impl,
marker: PhantomData
}
}
}
impl<T, C> Clone for Service<T, C> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
{
fn clone(&self) -> Self {
Service {
api_impl: self.api_impl.clone(),
marker: self.marker.clone(),
}
}
}
impl<T, C> hyper::service::Service<(Request<Body>, C)> for Service<T, C> where
T: Api<C> + Clone + Send + Sync + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + Send + Sync + 'static
{
type Response = Response<Body>;
type Error = crate::ServiceError;
type Future = ServiceFuture;
fn poll_ready(&mut self, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
self.api_impl.poll_ready(cx)
}
fn call(&mut self, req: (Request<Body>, C)) -> Self::Future { async fn run<T, C>(mut api_impl: T, req: (Request<Body>, C)) -> Result<Response<Body>, crate::ServiceError> where
T: Api<C> + Clone + Send + 'static,
C: Has<XSpanIdString> + Has<Option<Authorization>> + 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<Option<Authorization>>).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<XSpanIdString>).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<T> RequestParser<T> for ApiRequestParser {
fn parse_operation_id(request: &Request<T>) -> 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(()),
}
}
}