forked from loafle/openapi-generator-original
[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:
parent
4ea4ceb0ff
commit
b4154be8d0
8
bin/configs/rust-server-ping-bearer-auth-v3.yaml
Normal file
8
bin/configs/rust-server-ping-bearer-auth-v3.yaml
Normal 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
|
@ -196,7 +196,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|
|||||||
|BasicAuth|✓|OAS2,OAS3
|
|BasicAuth|✓|OAS2,OAS3
|
||||||
|ApiKey|✓|OAS2,OAS3
|
|ApiKey|✓|OAS2,OAS3
|
||||||
|OpenIDConnect|✗|OAS3
|
|OpenIDConnect|✗|OAS3
|
||||||
|BearerToken|✗|OAS3
|
|BearerToken|✓|OAS3
|
||||||
|OAuth2_Implicit|✓|OAS2,OAS3
|
|OAuth2_Implicit|✓|OAS2,OAS3
|
||||||
|OAuth2_Password|✗|OAS2,OAS3
|
|OAuth2_Password|✗|OAS2,OAS3
|
||||||
|OAuth2_ClientCredentials|✗|OAS2,OAS3
|
|OAuth2_ClientCredentials|✗|OAS2,OAS3
|
||||||
|
@ -94,6 +94,7 @@ public class RustServerCodegen extends DefaultCodegen implements CodegenConfig {
|
|||||||
.securityFeatures(EnumSet.of(
|
.securityFeatures(EnumSet.of(
|
||||||
SecurityFeature.ApiKey,
|
SecurityFeature.ApiKey,
|
||||||
SecurityFeature.BasicAuth,
|
SecurityFeature.BasicAuth,
|
||||||
|
SecurityFeature.BearerToken,
|
||||||
SecurityFeature.OAuth2_Implicit
|
SecurityFeature.OAuth2_Implicit
|
||||||
))
|
))
|
||||||
.excludeGlobalFeatures(
|
.excludeGlobalFeatures(
|
||||||
|
@ -129,13 +129,20 @@ Example
|
|||||||
{{! TODO: Add API Key example }}
|
{{! TODO: Add API Key example }}
|
||||||
```
|
```
|
||||||
{{/isApiKey}}
|
{{/isApiKey}}
|
||||||
{{#isBasic}}- **Type**: HTTP basic authentication
|
{{#isBasicBasic}}- **Type**: HTTP basic authentication
|
||||||
|
|
||||||
Example
|
Example
|
||||||
```
|
```
|
||||||
{{! TODO: Add HTTP basic authentication }}
|
{{! TODO: Add HTTP basic authentication }}
|
||||||
```
|
```
|
||||||
{{/isBasic}}
|
{{/isBasicBasic}}
|
||||||
|
{{#isBasicBearer}}- **Type**: Bearer token authentication
|
||||||
|
|
||||||
|
Example
|
||||||
|
```
|
||||||
|
{{! TODO: Add Bearer token authentication }}
|
||||||
|
```
|
||||||
|
{{/isBasicBearer}}
|
||||||
{{#isOAuth}}- **Type**: OAuth
|
{{#isOAuth}}- **Type**: OAuth
|
||||||
- **Flow**: {{{flow}}}
|
- **Flow**: {{{flow}}}
|
||||||
- **Authorization URL**: {{{authorizationUrl}}}
|
- **Authorization URL**: {{{authorizationUrl}}}
|
||||||
|
@ -106,6 +106,7 @@ impl<T, A, B, C, D, ReqBody> Service<Request<ReqBody>> for AddContext<T, A, B, C
|
|||||||
|
|
||||||
{{#authMethods}}
|
{{#authMethods}}
|
||||||
{{#isBasic}}
|
{{#isBasic}}
|
||||||
|
{{#isBasicBasic}}
|
||||||
{
|
{
|
||||||
use swagger::auth::Basic;
|
use swagger::auth::Basic;
|
||||||
use std::ops::Deref;
|
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))
|
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}}
|
{{/isBasic}}
|
||||||
{{#isOAuth}}
|
{{#isOAuth}}
|
||||||
{
|
{
|
||||||
|
@ -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: []
|
@ -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
|
||||||
|
]
|
2
samples/server/petstore/rust-server/output/ping-bearer-auth/.gitignore
vendored
Normal file
2
samples/server/petstore/rust-server/output/ping-bearer-auth/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
@ -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
|
@ -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
|
@ -0,0 +1 @@
|
|||||||
|
5.0.0-SNAPSHOT
|
@ -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"]
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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-----
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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-----
|
@ -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-----
|
@ -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;
|
||||||
|
}
|
@ -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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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),
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
@ -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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
@ -0,0 +1,6 @@
|
|||||||
|
#![allow(unused_qualifications)]
|
||||||
|
|
||||||
|
use crate::models;
|
||||||
|
#[cfg(any(feature = "client", feature = "server"))]
|
||||||
|
use crate::header;
|
||||||
|
|
@ -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(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user