1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-02-22 20:52:27 +00:00

split the openapi code into several files

This commit is contained in:
Dominic 2020-04-26 22:34:22 +02:00
parent b4eaeca01c
commit 01f818e268
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
7 changed files with 221 additions and 211 deletions

View file

@ -27,7 +27,7 @@ gotham_restful_derive = { version = "0.0.4-dev" }
indexmap = { version = "1.3.2", optional = true }
itertools = "0.9.0"
jsonwebtoken = { version = "7.1.0", optional = true }
log = { version = "0.4.8", optional = true }
log = "0.4.8"
mime = "0.3.16"
openapiv3 = { version = "0.3", optional = true }
serde = { version = "1.0.106", features = ["derive"] }
@ -44,7 +44,7 @@ default = []
auth = ["gotham_restful_derive/auth", "base64", "cookie", "jsonwebtoken"]
errorlog = []
database = ["gotham_restful_derive/database", "gotham_middleware_diesel"]
openapi = ["gotham_restful_derive/openapi", "indexmap", "log", "openapiv3"]
openapi = ["gotham_restful_derive/openapi", "indexmap", "openapiv3"]
[package.metadata.docs.rs]
all-features = true

View file

@ -110,6 +110,7 @@ Licensed under your option of:
extern crate self as gotham_restful;
#[macro_use] extern crate gotham_derive;
#[macro_use] extern crate log;
#[macro_use] extern crate serde;
#[doc(no_inline)]

View file

@ -0,0 +1,96 @@
use crate::{OpenapiType, OpenapiSchema};
use indexmap::IndexMap;
use openapiv3::{
Components, OpenAPI, PathItem, ReferenceOr, ReferenceOr::Item, ReferenceOr::Reference, Schema,
Server
};
use std::sync::{Arc, RwLock};
pub struct OpenapiBuilder
{
pub openapi : Arc<RwLock<OpenAPI>>
}
impl OpenapiBuilder
{
pub fn new(title : String, version : String, url : String) -> Self
{
Self {
openapi: Arc::new(RwLock::new(OpenAPI {
openapi: "3.0.2".to_string(),
info: openapiv3::Info {
title, version,
..Default::default()
},
servers: vec![Server {
url,
..Default::default()
}],
..Default::default()
}))
}
}
/// Remove path from the OpenAPI spec, or return an empty one if not included. This is handy if you need to
/// modify the path and add it back after the modification
pub fn remove_path(&mut self, path : &str) -> PathItem
{
let mut openapi = self.openapi.write().unwrap();
match openapi.paths.swap_remove(path) {
Some(Item(item)) => item,
_ => PathItem::default()
}
}
pub fn add_path<Path : ToString>(&mut self, path : Path, item : PathItem)
{
let mut openapi = self.openapi.write().unwrap();
openapi.paths.insert(path.to_string(), Item(item));
}
fn add_schema_impl(&mut self, name : String, mut schema : OpenapiSchema)
{
self.add_schema_dependencies(&mut schema.dependencies);
let mut openapi = self.openapi.write().unwrap();
match &mut openapi.components {
Some(comp) => {
comp.schemas.insert(name, Item(schema.into_schema()));
},
None => {
let mut comp = Components::default();
comp.schemas.insert(name, Item(schema.into_schema()));
openapi.components = Some(comp);
}
};
}
fn add_schema_dependencies(&mut self, dependencies : &mut IndexMap<String, OpenapiSchema>)
{
let keys : Vec<String> = dependencies.keys().map(|k| k.to_string()).collect();
for dep in keys
{
let dep_schema = dependencies.swap_remove(&dep);
if let Some(dep_schema) = dep_schema
{
self.add_schema_impl(dep, dep_schema);
}
}
}
pub fn add_schema<T : OpenapiType>(&mut self) -> ReferenceOr<Schema>
{
let mut schema = T::schema();
match schema.name.clone() {
Some(name) => {
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
self.add_schema_impl(name, schema);
reference
},
None => {
self.add_schema_dependencies(&mut schema.dependencies);
Item(schema.into_schema())
}
}
}
}

View file

@ -0,0 +1,110 @@
use super::SECURITY_NAME;
use futures_util::{future, future::FutureExt};
use gotham::{
error::Result,
handler::{Handler, HandlerFuture, NewHandler},
helpers::http::response::create_response,
state::State
};
use indexmap::IndexMap;
use mime::{APPLICATION_JSON, TEXT_PLAIN};
use openapiv3::{APIKeyLocation, OpenAPI, ReferenceOr, SecurityScheme};
use std::{
pin::Pin,
sync::{Arc, RwLock}
};
#[derive(Clone)]
pub struct OpenapiHandler
{
openapi : Arc<RwLock<OpenAPI>>
}
impl OpenapiHandler
{
pub fn new(openapi : Arc<RwLock<OpenAPI>>) -> Self
{
Self { openapi }
}
}
impl NewHandler for OpenapiHandler
{
type Instance = Self;
fn new_handler(&self) -> Result<Self>
{
Ok(self.clone())
}
}
#[cfg(feature = "auth")]
fn get_security(state : &mut State) -> IndexMap<String, ReferenceOr<SecurityScheme>>
{
use crate::AuthSource;
use gotham::state::FromState;
let source = match AuthSource::try_borrow_from(state) {
Some(source) => source,
None => return Default::default()
};
let security_scheme = match source {
AuthSource::Cookie(name) => SecurityScheme::APIKey {
location: APIKeyLocation::Cookie,
name: name.to_string()
},
AuthSource::Header(name) => SecurityScheme::APIKey {
location: APIKeyLocation::Header,
name: name.to_string()
},
AuthSource::AuthorizationHeader => SecurityScheme::HTTP {
scheme: "bearer".to_owned(),
bearer_format: Some("JWT".to_owned())
}
};
let mut security_schemes : IndexMap<String, ReferenceOr<SecurityScheme>> = Default::default();
security_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme));
security_schemes
}
#[cfg(not(feature = "auth"))]
fn get_security(state : &mut State) -> (Vec<SecurityRequirement>, IndexMap<String, ReferenceOr<SecurityScheme>>)
{
Default::default()
}
impl Handler for OpenapiHandler
{
fn handle(self, mut state : State) -> Pin<Box<HandlerFuture>>
{
let openapi = match self.openapi.read() {
Ok(openapi) => openapi,
Err(e) => {
error!("Unable to acquire read lock for the OpenAPI specification: {}", e);
let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
return future::ok((state, res)).boxed()
}
};
let mut openapi = openapi.clone();
let security_schemes = get_security(&mut state);
let mut components = openapi.components.unwrap_or_default();
components.security_schemes = security_schemes;
openapi.components = Some(components);
match serde_json::to_string(&openapi) {
Ok(body) => {
let res = create_response(&state, crate::StatusCode::OK, APPLICATION_JSON, body);
future::ok((state, res)).boxed()
},
Err(e) => {
error!("Unable to handle OpenAPI request due to error: {}", e);
let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
future::ok((state, res)).boxed()
}
}
}
}

View file

@ -1,3 +1,7 @@
const SECURITY_NAME : &str = "authToken";
pub mod builder;
pub mod handler;
pub mod router;
pub mod types;

View file

@ -6,220 +6,19 @@ use crate::{
OpenapiType,
RequestBody
};
use futures_util::{future, future::FutureExt};
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, SECURITY_NAME};
use gotham::{
handler::{Handler, HandlerFuture, NewHandler},
helpers::http::response::create_response,
pipeline::chain::PipelineHandleChain,
router::builder::*,
state::State
router::builder::*
};
use indexmap::IndexMap;
use log::error;
use mime::{Mime, APPLICATION_JSON, STAR_STAR, TEXT_PLAIN};
use mime::{Mime, STAR_STAR};
use openapiv3::{
APIKeyLocation, Components, MediaType, OpenAPI, Operation, Parameter, ParameterData, ParameterSchemaOrContent, PathItem,
ReferenceOr, ReferenceOr::Item, ReferenceOr::Reference, RequestBody as OARequestBody, Response, Responses, Schema,
SchemaKind, SecurityScheme, Server, StatusCode, Type
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,
StatusCode, Type
};
use std::{
panic::RefUnwindSafe,
pin::Pin,
sync::{Arc, RwLock}
};
/**
This type is required to build routes while adding them to the generated OpenAPI Spec at the
same time. There is no need to use this type directly. See [`WithOpenapi`] on how to do this.
[`WithOpenapi`]: trait.WithOpenapi.html
*/
pub struct OpenapiBuilder
{
openapi : Arc<RwLock<OpenAPI>>
}
impl OpenapiBuilder
{
pub fn new(title : String, version : String, url : String) -> Self
{
Self {
openapi: Arc::new(RwLock::new(OpenAPI {
openapi: "3.0.2".to_string(),
info: openapiv3::Info {
title, version,
..Default::default()
},
servers: vec![Server {
url,
..Default::default()
}],
..Default::default()
}))
}
}
/// Remove path from the OpenAPI spec, or return an empty one if not included. This is handy if you need to
/// modify the path and add it back after the modification
fn remove_path(&mut self, path : &str) -> PathItem
{
let mut openapi = self.openapi.write().unwrap();
match openapi.paths.swap_remove(path) {
Some(Item(item)) => item,
_ => PathItem::default()
}
}
fn add_path<Path : ToString>(&mut self, path : Path, item : PathItem)
{
let mut openapi = self.openapi.write().unwrap();
openapi.paths.insert(path.to_string(), Item(item));
}
fn add_schema_impl(&mut self, name : String, mut schema : OpenapiSchema)
{
self.add_schema_dependencies(&mut schema.dependencies);
let mut openapi = self.openapi.write().unwrap();
match &mut openapi.components {
Some(comp) => {
comp.schemas.insert(name, Item(schema.into_schema()));
},
None => {
let mut comp = Components::default();
comp.schemas.insert(name, Item(schema.into_schema()));
openapi.components = Some(comp);
}
};
}
fn add_schema_dependencies(&mut self, dependencies : &mut IndexMap<String, OpenapiSchema>)
{
let keys : Vec<String> = dependencies.keys().map(|k| k.to_string()).collect();
for dep in keys
{
let dep_schema = dependencies.swap_remove(&dep);
if let Some(dep_schema) = dep_schema
{
self.add_schema_impl(dep, dep_schema);
}
}
}
fn add_schema<T : OpenapiType>(&mut self) -> ReferenceOr<Schema>
{
let mut schema = T::schema();
match schema.name.clone() {
Some(name) => {
let reference = Reference { reference: format!("#/components/schemas/{}", name) };
self.add_schema_impl(name, schema);
reference
},
None => {
self.add_schema_dependencies(&mut schema.dependencies);
Item(schema.into_schema())
}
}
}
}
#[derive(Clone)]
struct OpenapiHandler
{
openapi : Arc<RwLock<OpenAPI>>
}
impl OpenapiHandler
{
fn new(openapi : Arc<RwLock<OpenAPI>>) -> Self
{
Self { openapi }
}
}
impl NewHandler for OpenapiHandler
{
type Instance = Self;
fn new_handler(&self) -> gotham::error::Result<Self::Instance>
{
Ok(self.clone())
}
}
#[cfg(feature = "auth")]
const SECURITY_NAME : &str = "authToken";
#[cfg(feature = "auth")]
fn get_security(state : &mut State) -> IndexMap<String, ReferenceOr<SecurityScheme>>
{
use crate::AuthSource;
use gotham::state::FromState;
let source = match AuthSource::try_borrow_from(state) {
Some(source) => source,
None => return Default::default()
};
let security_scheme = match source {
AuthSource::Cookie(name) => SecurityScheme::APIKey {
location: APIKeyLocation::Cookie,
name: name.to_string()
},
AuthSource::Header(name) => SecurityScheme::APIKey {
location: APIKeyLocation::Header,
name: name.to_string()
},
AuthSource::AuthorizationHeader => SecurityScheme::HTTP {
scheme: "bearer".to_owned(),
bearer_format: Some("JWT".to_owned())
}
};
let mut security_schemes : IndexMap<String, ReferenceOr<SecurityScheme>> = Default::default();
security_schemes.insert(SECURITY_NAME.to_owned(), ReferenceOr::Item(security_scheme));
security_schemes
}
#[cfg(not(feature = "auth"))]
fn get_security(state : &mut State) -> (Vec<SecurityRequirement>, IndexMap<String, ReferenceOr<SecurityScheme>>)
{
Default::default()
}
impl Handler for OpenapiHandler
{
fn handle(self, mut state : State) -> Pin<Box<HandlerFuture>>
{
let openapi = match self.openapi.read() {
Ok(openapi) => openapi,
Err(e) => {
error!("Unable to acquire read lock for the OpenAPI specification: {}", e);
let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
return future::ok((state, res)).boxed()
}
};
let mut openapi = openapi.clone();
let security_schemes = get_security(&mut state);
let mut components = openapi.components.unwrap_or_default();
components.security_schemes = security_schemes;
openapi.components = Some(components);
match serde_json::to_string(&openapi) {
Ok(body) => {
let res = create_response(&state, crate::StatusCode::OK, APPLICATION_JSON, body);
future::ok((state, res)).boxed()
},
Err(e) => {
error!("Unable to handle OpenAPI request due to error: {}", e);
let res = create_response(&state, crate::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
future::ok((state, res)).boxed()
}
}
}
}
use std::panic::RefUnwindSafe;
/// This trait adds the `get_openapi` method to an OpenAPI-aware router.
pub trait GetOpenapi

View file

@ -6,7 +6,7 @@ use crate::{
StatusCode
};
#[cfg(feature = "openapi")]
use crate::openapi::router::OpenapiBuilder;
use crate::openapi::builder::OpenapiBuilder;
use futures_util::{future, future::FutureExt};
use gotham::{