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:
parent
b4eaeca01c
commit
01f818e268
7 changed files with 221 additions and 211 deletions
|
@ -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
|
||||
|
|
|
@ -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)]
|
||||
|
|
96
gotham_restful/src/openapi/builder.rs
Normal file
96
gotham_restful/src/openapi/builder.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
gotham_restful/src/openapi/handler.rs
Normal file
110
gotham_restful/src/openapi/handler.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
|
||||
const SECURITY_NAME : &str = "authToken";
|
||||
|
||||
pub mod builder;
|
||||
pub mod handler;
|
||||
pub mod router;
|
||||
pub mod types;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::{
|
||||
|
|
Loading…
Add table
Reference in a new issue