2019-09-29 21:15:22 +02:00
|
|
|
use crate::{
|
|
|
|
resource::*,
|
|
|
|
result::*,
|
2019-10-01 00:23:34 +02:00
|
|
|
routing::*,
|
|
|
|
OpenapiType
|
2019-09-29 21:15:22 +02:00
|
|
|
};
|
2019-09-30 17:34:48 +02:00
|
|
|
use futures::future::ok;
|
2019-09-29 21:15:22 +02:00
|
|
|
use gotham::{
|
2019-09-30 17:34:48 +02:00
|
|
|
handler::{Handler, HandlerFuture, NewHandler},
|
2019-09-29 21:15:22 +02:00
|
|
|
helpers::http::response::create_response,
|
|
|
|
pipeline::chain::PipelineHandleChain,
|
|
|
|
router::builder::*,
|
|
|
|
state::State
|
|
|
|
};
|
|
|
|
use indexmap::IndexMap;
|
2019-09-30 17:34:48 +02:00
|
|
|
use log::error;
|
|
|
|
use mime::{APPLICATION_JSON, TEXT_PLAIN};
|
2019-09-30 20:58:15 +02:00
|
|
|
use openapiv3::{
|
2019-10-01 00:23:34 +02:00
|
|
|
Components, MediaType, OpenAPI, Operation, Parameter, ParameterData, ParameterSchemaOrContent, PathItem,
|
2019-10-01 00:34:58 +02:00
|
|
|
PathStyle, Paths, ReferenceOr, ReferenceOr::Item, ReferenceOr::Reference, RequestBody, Response, Responses,
|
|
|
|
Schema, SchemaData, Server, StatusCode
|
2019-09-30 20:58:15 +02:00
|
|
|
};
|
2019-09-29 21:15:22 +02:00
|
|
|
use serde::de::DeserializeOwned;
|
|
|
|
use std::panic::RefUnwindSafe;
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
pub struct OpenapiRouter(OpenAPI);
|
2019-09-29 21:15:22 +02:00
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
impl OpenapiRouter
|
2019-09-29 21:15:22 +02:00
|
|
|
{
|
2019-09-30 18:41:18 +02:00
|
|
|
pub fn new<Title : ToString, Version : ToString, Url : ToString>(title : Title, version : Version, server_url : Url) -> Self
|
2019-09-29 21:15:22 +02:00
|
|
|
{
|
2019-09-30 18:18:10 +02:00
|
|
|
Self(OpenAPI {
|
|
|
|
openapi: "3.0.2".to_string(),
|
|
|
|
info: openapiv3::Info {
|
|
|
|
title: title.to_string(),
|
|
|
|
description: None,
|
|
|
|
terms_of_service: None,
|
|
|
|
contact: None,
|
|
|
|
license: None,
|
|
|
|
version: version.to_string()
|
|
|
|
},
|
2019-09-30 18:41:18 +02:00
|
|
|
servers: vec![Server {
|
|
|
|
url: server_url.to_string(),
|
|
|
|
description: None,
|
|
|
|
variables: None
|
|
|
|
}],
|
2019-09-30 18:18:10 +02:00
|
|
|
paths: Paths::new(),
|
|
|
|
components: None,
|
|
|
|
security: Vec::new(),
|
|
|
|
tags: Vec::new(),
|
|
|
|
external_docs: None
|
|
|
|
})
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
{
|
2019-09-30 18:18:10 +02:00
|
|
|
if let Some(Item(item)) = self.0.paths.swap_remove(path)
|
2019-09-29 21:15:22 +02:00
|
|
|
{
|
|
|
|
return item;
|
|
|
|
}
|
2019-09-30 23:53:55 +02:00
|
|
|
return PathItem::default()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn add_path<Path : ToString>(&mut self, path : Path, item : PathItem)
|
|
|
|
{
|
2019-09-30 18:18:10 +02:00
|
|
|
self.0.paths.insert(path.to_string(), Item(item));
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
2019-09-30 23:53:55 +02:00
|
|
|
|
2019-10-01 00:23:34 +02:00
|
|
|
fn add_schema<T : ResourceResult>(&mut self, path : &str, method : &str, desc : &str) -> String
|
2019-09-30 23:53:55 +02:00
|
|
|
{
|
2019-10-01 00:23:34 +02:00
|
|
|
let name = T::schema_name().unwrap_or_else(|| format!("path_{}_{}_{}", path, method, desc));
|
|
|
|
let item = Schema {
|
|
|
|
schema_data: SchemaData {
|
|
|
|
nullable: false,
|
|
|
|
read_only: false,
|
|
|
|
write_only: false,
|
|
|
|
deprecated: false,
|
|
|
|
external_docs: None,
|
|
|
|
example: None,
|
|
|
|
title: Some(name.to_string()),
|
|
|
|
description: None,
|
|
|
|
discriminator: None,
|
|
|
|
default: None
|
|
|
|
},
|
|
|
|
schema_kind: T::to_schema()
|
|
|
|
};
|
2019-09-30 23:53:55 +02:00
|
|
|
match &mut self.0.components {
|
|
|
|
Some(comp) => {
|
|
|
|
comp.schemas.insert(name.to_string(), Item(item));
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
let mut comp = Components::default();
|
|
|
|
comp.schemas.insert(name.to_string(), Item(item));
|
|
|
|
self.0.components = Some(comp);
|
|
|
|
}
|
|
|
|
};
|
2019-10-01 00:23:34 +02:00
|
|
|
name
|
2019-09-30 23:53:55 +02:00
|
|
|
}
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
2019-09-30 17:34:48 +02:00
|
|
|
#[derive(Clone)]
|
|
|
|
struct OpenapiHandler(Result<String, String>);
|
|
|
|
|
|
|
|
// dunno what/why/whatever
|
|
|
|
impl RefUnwindSafe for OpenapiHandler {}
|
|
|
|
|
|
|
|
impl OpenapiHandler
|
|
|
|
{
|
2019-09-30 18:18:10 +02:00
|
|
|
fn new(openapi : &OpenapiRouter) -> Self
|
2019-09-30 17:34:48 +02:00
|
|
|
{
|
2019-09-30 18:18:10 +02:00
|
|
|
Self(serde_json::to_string(&openapi.0).map_err(|e| format!("{}", e)))
|
2019-09-30 17:34:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl NewHandler for OpenapiHandler
|
2019-09-29 21:15:22 +02:00
|
|
|
{
|
2019-09-30 17:34:48 +02:00
|
|
|
type Instance = Self;
|
|
|
|
|
|
|
|
fn new_handler(&self) -> gotham::error::Result<Self::Instance>
|
|
|
|
{
|
|
|
|
Ok(self.clone())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Handler for OpenapiHandler
|
|
|
|
{
|
|
|
|
fn handle(self, state : State) -> Box<HandlerFuture>
|
|
|
|
{
|
|
|
|
match self.0 {
|
|
|
|
Ok(body) => {
|
|
|
|
let res = create_response(&state, hyper::StatusCode::OK, APPLICATION_JSON, body);
|
|
|
|
Box::new(ok((state, res)))
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
error!("Unable to handle OpenAPI request due to error: {}", e);
|
|
|
|
let res = create_response(&state, hyper::StatusCode::INTERNAL_SERVER_ERROR, TEXT_PLAIN, "");
|
|
|
|
Box::new(ok((state, res)))
|
|
|
|
}
|
|
|
|
}
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
pub trait GetOpenapi
|
|
|
|
{
|
|
|
|
fn get_openapi(&mut self, path : &str);
|
|
|
|
}
|
|
|
|
|
2019-10-01 00:34:58 +02:00
|
|
|
fn schema_to_content(schema : &str) -> IndexMap<String, MediaType>
|
2019-10-01 00:23:34 +02:00
|
|
|
{
|
|
|
|
let mut content : IndexMap<String, MediaType> = IndexMap::new();
|
|
|
|
content.insert(APPLICATION_JSON.to_string(), MediaType {
|
|
|
|
schema: Some(Reference {
|
|
|
|
reference: format!("#/components/schemas/{}", schema)
|
|
|
|
}),
|
|
|
|
example: None,
|
|
|
|
examples: IndexMap::new(),
|
|
|
|
encoding: IndexMap::new()
|
|
|
|
});
|
2019-10-01 00:34:58 +02:00
|
|
|
content
|
|
|
|
}
|
|
|
|
|
|
|
|
fn new_operation(schema : &str, path_params : Vec<&str>, body_schema : Option<&str>) -> Operation
|
|
|
|
{
|
2019-10-01 00:23:34 +02:00
|
|
|
let mut responses : IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
|
|
|
|
responses.insert(StatusCode::Code(200), Item(Response {
|
|
|
|
description: "OK".to_string(),
|
|
|
|
headers: IndexMap::new(),
|
2019-10-01 00:34:58 +02:00
|
|
|
content: schema_to_content(schema),
|
2019-10-01 00:23:34 +02:00
|
|
|
links: IndexMap::new()
|
|
|
|
}));
|
|
|
|
|
|
|
|
let mut params : Vec<ReferenceOr<Parameter>> = Vec::new();
|
|
|
|
for param in path_params
|
|
|
|
{
|
|
|
|
params.push(Item(Parameter::Path {
|
|
|
|
parameter_data: ParameterData {
|
|
|
|
name: param.to_string(),
|
|
|
|
description: None,
|
|
|
|
required: true,
|
|
|
|
deprecated: None,
|
|
|
|
format: ParameterSchemaOrContent::Schema(Item(Schema {
|
|
|
|
schema_data: SchemaData::default(),
|
|
|
|
schema_kind: String::to_schema()
|
|
|
|
})),
|
|
|
|
example: None,
|
|
|
|
examples: IndexMap::new()
|
|
|
|
},
|
|
|
|
style: PathStyle::default(),
|
|
|
|
}));
|
|
|
|
}
|
2019-10-01 00:34:58 +02:00
|
|
|
|
|
|
|
let request_body = body_schema.map(|schema| Item(RequestBody {
|
|
|
|
description: None,
|
|
|
|
content: schema_to_content(schema),
|
|
|
|
required: true
|
|
|
|
}));
|
2019-10-01 00:23:34 +02:00
|
|
|
|
|
|
|
Operation {
|
|
|
|
tags: Vec::new(),
|
|
|
|
summary: None,
|
|
|
|
description: None,
|
|
|
|
external_documentation: None,
|
|
|
|
operation_id: None, // TODO
|
|
|
|
parameters: params,
|
2019-10-01 00:34:58 +02:00
|
|
|
request_body,
|
2019-10-01 00:23:34 +02:00
|
|
|
responses: Responses {
|
|
|
|
default: None,
|
|
|
|
responses
|
|
|
|
},
|
|
|
|
deprecated: false,
|
|
|
|
security: Vec::new(),
|
|
|
|
servers: Vec::new()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-29 21:15:22 +02:00
|
|
|
macro_rules! implOpenapiRouter {
|
|
|
|
($implType:ident) => {
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
impl<'a, C, P> GetOpenapi for (&mut $implType<'a, C, P>, &mut OpenapiRouter)
|
2019-09-29 21:15:22 +02:00
|
|
|
where
|
|
|
|
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
|
|
|
P : RefUnwindSafe + Send + Sync + 'static
|
|
|
|
{
|
2019-09-30 18:18:10 +02:00
|
|
|
fn get_openapi(&mut self, path : &str)
|
2019-09-29 21:15:22 +02:00
|
|
|
{
|
2019-09-30 18:18:10 +02:00
|
|
|
self.0.get(path).to_new_handler(OpenapiHandler::new(&self.1));
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
impl<'a, C, P> DrawResources for (&mut $implType<'a, C, P>, &mut OpenapiRouter)
|
2019-09-29 21:15:22 +02:00
|
|
|
where
|
|
|
|
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
|
|
|
P : RefUnwindSafe + Send + Sync + 'static
|
|
|
|
{
|
|
|
|
fn resource<R : Resource, T : ToString>(&mut self, path : T)
|
|
|
|
{
|
|
|
|
R::setup((self, path.to_string()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
impl<'a, C, P> DrawResourceRoutes for (&mut (&mut $implType<'a, C, P>, &mut OpenapiRouter), String)
|
2019-09-29 21:15:22 +02:00
|
|
|
where
|
|
|
|
C : PipelineHandleChain<P> + Copy + Send + Sync + 'static,
|
|
|
|
P : RefUnwindSafe + Send + Sync + 'static
|
|
|
|
{
|
|
|
|
fn read_all<Handler, Res>(&mut self)
|
|
|
|
where
|
|
|
|
Res : ResourceResult,
|
|
|
|
Handler : ResourceReadAll<Res>
|
|
|
|
{
|
2019-10-01 00:23:34 +02:00
|
|
|
let schema = (self.0).1.add_schema::<Res>(&self.1, "read_all", "result_body");
|
2019-09-30 23:53:55 +02:00
|
|
|
|
|
|
|
let path = format!("/{}", &self.1);
|
|
|
|
let mut item = (self.0).1.remove_path(&path);
|
2019-10-01 00:34:58 +02:00
|
|
|
item.get = Some(new_operation(&schema, vec![], None));
|
2019-09-30 18:18:10 +02:00
|
|
|
(self.0).1.add_path(path, item);
|
2019-09-29 21:15:22 +02:00
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
(&mut *(self.0).0, self.1.to_string()).read_all::<Handler, Res>()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn read<Handler, ID, Res>(&mut self)
|
|
|
|
where
|
|
|
|
ID : DeserializeOwned + Clone + RefUnwindSafe + Send + Sync + 'static,
|
|
|
|
Res : ResourceResult,
|
|
|
|
Handler : ResourceRead<ID, Res>
|
|
|
|
{
|
2019-10-01 00:23:34 +02:00
|
|
|
let schema = (self.0).1.add_schema::<Res>(&self.1, "read", "result_body");
|
|
|
|
|
|
|
|
let path = format!("/{}/{{id}}", &self.1);
|
|
|
|
let mut item = (self.0).1.remove_path(&path);
|
2019-10-01 00:34:58 +02:00
|
|
|
item.get = Some(new_operation(&schema, vec!["id"], None));
|
2019-10-01 00:23:34 +02:00
|
|
|
(self.0).1.add_path(path, item);
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
(&mut *(self.0).0, self.1.to_string()).read::<Handler, ID, Res>()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn create<Handler, Body, Res>(&mut self)
|
|
|
|
where
|
|
|
|
Body : DeserializeOwned,
|
|
|
|
Res : ResourceResult,
|
|
|
|
Handler : ResourceCreate<Body, Res>
|
|
|
|
{
|
2019-10-01 00:34:58 +02:00
|
|
|
let schema = (self.0).1.add_schema::<Res>(&self.1, "create", "result_body");
|
|
|
|
|
|
|
|
let path = format!("/{}", &self.1);
|
|
|
|
let mut item = (self.0).1.remove_path(&path);
|
|
|
|
item.post = Some(new_operation(&schema, vec![], None));
|
|
|
|
(self.0).1.add_path(path, item);
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
(&mut *(self.0).0, self.1.to_string()).create::<Handler, Body, Res>()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn update_all<Handler, Body, Res>(&mut self)
|
|
|
|
where
|
|
|
|
Body : DeserializeOwned,
|
|
|
|
Res : ResourceResult,
|
|
|
|
Handler : ResourceUpdateAll<Body, Res>
|
|
|
|
{
|
2019-10-01 00:34:58 +02:00
|
|
|
let schema = (self.0).1.add_schema::<Res>(&self.1, "update_all", "result_body");
|
|
|
|
|
|
|
|
let path = format!("/{}", &self.1);
|
|
|
|
let mut item = (self.0).1.remove_path(&path);
|
|
|
|
item.put = Some(new_operation(&schema, vec![], None));
|
|
|
|
(self.0).1.add_path(path, item);
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
(&mut *(self.0).0, self.1.to_string()).update_all::<Handler, Body, Res>()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn update<Handler, ID, Body, Res>(&mut self)
|
|
|
|
where
|
|
|
|
ID : DeserializeOwned + Clone + RefUnwindSafe + Send + Sync + 'static,
|
|
|
|
Body : DeserializeOwned,
|
|
|
|
Res : ResourceResult,
|
|
|
|
Handler : ResourceUpdate<ID, Body, Res>
|
|
|
|
{
|
2019-10-01 00:34:58 +02:00
|
|
|
let schema = (self.0).1.add_schema::<Res>(&self.1, "update", "result_body");
|
|
|
|
|
|
|
|
let path = format!("/{}/{{id}}", &self.1);
|
|
|
|
let mut item = (self.0).1.remove_path(&path);
|
|
|
|
item.put = Some(new_operation(&schema, vec!["id"], None));
|
|
|
|
(self.0).1.add_path(path, item);
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
(&mut *(self.0).0, self.1.to_string()).update::<Handler, ID, Body, Res>()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn delete_all<Handler, Res>(&mut self)
|
|
|
|
where
|
|
|
|
Res : ResourceResult,
|
|
|
|
Handler : ResourceDeleteAll<Res>
|
|
|
|
{
|
2019-10-01 00:34:58 +02:00
|
|
|
let schema = (self.0).1.add_schema::<Res>(&self.1, "delete_all", "result_body");
|
|
|
|
|
|
|
|
let path = format!("/{}", &self.1);
|
|
|
|
let mut item = (self.0).1.remove_path(&path);
|
|
|
|
item.delete = Some(new_operation(&schema, vec![], None));
|
|
|
|
(self.0).1.add_path(path, item);
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
(&mut *(self.0).0, self.1.to_string()).delete_all::<Handler, Res>()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn delete<Handler, ID, Res>(&mut self)
|
|
|
|
where
|
|
|
|
ID : DeserializeOwned + Clone + RefUnwindSafe + Send + Sync + 'static,
|
|
|
|
Res : ResourceResult,
|
|
|
|
Handler : ResourceDelete<ID, Res>
|
|
|
|
{
|
2019-10-01 00:34:58 +02:00
|
|
|
let schema = (self.0).1.add_schema::<Res>(&self.1, "delete", "result_body");
|
|
|
|
|
|
|
|
let path = format!("/{}/{{id}}", &self.1);
|
|
|
|
let mut item = (self.0).1.remove_path(&path);
|
|
|
|
item.delete = Some(new_operation(&schema, vec!["id"], None));
|
|
|
|
(self.0).1.add_path(path, item);
|
|
|
|
|
2019-09-30 18:18:10 +02:00
|
|
|
(&mut *(self.0).0, self.1.to_string()).delete::<Handler, ID, Res>()
|
2019-09-29 21:15:22 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
implOpenapiRouter!(RouterBuilder);
|
|
|
|
implOpenapiRouter!(ScopeBuilder);
|