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

move openapi operation extraction code into its own mod

This commit is contained in:
Dominic 2020-04-27 02:12:51 +02:00
parent 01f818e268
commit 9fd0bceaf4
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
3 changed files with 227 additions and 235 deletions

View file

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

View file

@ -0,0 +1,217 @@
use crate::{
resource::*,
result::*,
OpenapiSchema,
OpenapiType,
RequestBody
};
use super::SECURITY_NAME;
use indexmap::IndexMap;
use mime::{Mime, STAR_STAR};
use openapiv3::{
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,
StatusCode, Type
};
#[derive(Default)]
struct OperationParams<'a>
{
path_params : Vec<&'a str>,
query_params : Option<OpenapiSchema>
}
impl<'a> OperationParams<'a>
{
fn add_path_params(&self, params : &mut Vec<ReferenceOr<Parameter>>)
{
for param in &self.path_params
{
params.push(Item(Parameter::Path {
parameter_data: ParameterData {
name: (*param).to_string(),
description: None,
required: true,
deprecated: None,
format: ParameterSchemaOrContent::Schema(Item(String::schema().into_schema())),
example: None,
examples: IndexMap::new()
},
style: Default::default(),
}));
}
}
fn add_query_params(self, params : &mut Vec<ReferenceOr<Parameter>>)
{
let query_params = match self.query_params {
Some(qp) => qp.schema,
None => return
};
let query_params = match query_params {
SchemaKind::Type(Type::Object(ty)) => ty,
_ => panic!("Query Parameters needs to be a plain struct")
};
for (name, schema) in query_params.properties
{
let required = query_params.required.contains(&name);
params.push(Item(Parameter::Query {
parameter_data: ParameterData {
name,
description: None,
required,
deprecated: None,
format: ParameterSchemaOrContent::Schema(schema.unbox()),
example: None,
examples: IndexMap::new()
},
allow_reserved: false,
style: Default::default(),
allow_empty_value: None
}))
}
}
fn into_params(self) -> Vec<ReferenceOr<Parameter>>
{
let mut params : Vec<ReferenceOr<Parameter>> = Vec::new();
self.add_path_params(&mut params);
self.add_query_params(&mut params);
params
}
}
pub struct OperationDescription<'a>
{
operation_id : Option<String>,
default_status : crate::StatusCode,
accepted_types : Option<Vec<Mime>>,
schema : ReferenceOr<Schema>,
params : OperationParams<'a>,
body_schema : Option<ReferenceOr<Schema>>,
supported_types : Option<Vec<Mime>>,
requires_auth : bool
}
impl<'a> OperationDescription<'a>
{
pub fn new<Handler : ResourceMethod>(schema : ReferenceOr<Schema>) -> Self
{
Self {
operation_id: Handler::operation_id(),
default_status: Handler::Res::default_status(),
accepted_types: Handler::Res::accepted_types(),
schema,
params: Default::default(),
body_schema: None,
supported_types: None,
requires_auth: Handler::wants_auth()
}
}
pub fn with_path_params(mut self, params : Vec<&'a str>) -> Self
{
self.params.path_params = params;
self
}
pub fn with_query_params(mut self, params : OpenapiSchema) -> Self
{
self.params.query_params = Some(params);
self
}
pub fn with_body<Body : RequestBody>(mut self, schema : ReferenceOr<Schema>) -> Self
{
self.body_schema = Some(schema);
self.supported_types = Body::supported_types();
self
}
fn schema_to_content(types : Vec<Mime>, schema : ReferenceOr<Schema>) -> IndexMap<String, MediaType>
{
let mut content : IndexMap<String, MediaType> = IndexMap::new();
for ty in types
{
content.insert(ty.to_string(), MediaType {
schema: Some(schema.clone()),
..Default::default()
});
}
content
}
pub fn into_operation(self) -> Operation
{
// this is unfortunately neccessary to prevent rust from complaining about partially moving self
let (operation_id, default_status, accepted_types, schema, params, body_schema, supported_types, requires_auth) = (
self.operation_id, self.default_status, self.accepted_types, self.schema, self.params, self.body_schema, self.supported_types, self.requires_auth);
let content = Self::schema_to_content(accepted_types.unwrap_or_else(|| vec![STAR_STAR]), schema);
let mut responses : IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
responses.insert(StatusCode::Code(default_status.as_u16()), Item(Response {
description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(),
content,
..Default::default()
}));
let request_body = body_schema.map(|schema| Item(OARequestBody {
description: None,
content: Self::schema_to_content(supported_types.unwrap_or_else(|| vec![STAR_STAR]), schema),
required: true
}));
let mut security = Vec::new();
if requires_auth
{
let mut sec = IndexMap::new();
sec.insert(SECURITY_NAME.to_owned(), Vec::new());
security.push(sec);
}
Operation {
tags: Vec::new(),
operation_id,
parameters: params.into_params(),
request_body,
responses: Responses {
default: None,
responses
},
deprecated: false,
security,
..Default::default()
}
}
}
#[cfg(test)]
mod test
{
use crate::ResourceResult;
use super::*;
#[test]
fn no_content_schema_to_content()
{
let types = NoContent::accepted_types();
let schema = <NoContent as OpenapiType>::schema();
let content = OperationDescription::schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
assert!(content.is_empty());
}
#[test]
fn raw_schema_to_content()
{
let types = Raw::<&str>::accepted_types();
let schema = <Raw<&str> as OpenapiType>::schema();
let content = OperationDescription::schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
assert_eq!(content.len(), 1);
let json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();
assert_eq!(json, r#"{"schema":{"type":"string","format":"binary"}}"#);
}
}

View file

@ -1,23 +1,13 @@
use crate::{
resource::*,
result::*,
routing::*,
OpenapiSchema,
OpenapiType,
RequestBody
};
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, SECURITY_NAME};
use super::{builder::OpenapiBuilder, handler::OpenapiHandler, operation::OperationDescription};
use gotham::{
pipeline::chain::PipelineHandleChain,
router::builder::*
};
use indexmap::IndexMap;
use mime::{Mime, STAR_STAR};
use openapiv3::{
MediaType, Operation, Parameter, ParameterData, ParameterSchemaOrContent, ReferenceOr,
ReferenceOr::Item, RequestBody as OARequestBody, Response, Responses, Schema, SchemaKind,
StatusCode, Type
};
use std::panic::RefUnwindSafe;
/// This trait adds the `get_openapi` method to an OpenAPI-aware router.
@ -26,150 +16,6 @@ pub trait GetOpenapi
fn get_openapi(&mut self, path : &str);
}
fn schema_to_content(types : Vec<Mime>, schema : ReferenceOr<Schema>) -> IndexMap<String, MediaType>
{
let mut content : IndexMap<String, MediaType> = IndexMap::new();
for ty in types
{
content.insert(ty.to_string(), MediaType {
schema: Some(schema.clone()),
..Default::default()
});
}
content
}
#[derive(Default)]
struct OperationParams<'a>
{
path_params : Vec<&'a str>,
query_params : Option<OpenapiSchema>
}
impl<'a> OperationParams<'a>
{
fn new(path_params : Vec<&'a str>, query_params : Option<OpenapiSchema>) -> Self
{
Self { path_params, query_params }
}
fn from_path_params(path_params : Vec<&'a str>) -> Self
{
Self::new(path_params, None)
}
fn from_query_params(query_params : OpenapiSchema) -> Self
{
Self::new(Vec::new(), Some(query_params))
}
fn add_path_params(&self, params : &mut Vec<ReferenceOr<Parameter>>)
{
for param in &self.path_params
{
params.push(Item(Parameter::Path {
parameter_data: ParameterData {
name: (*param).to_string(),
description: None,
required: true,
deprecated: None,
format: ParameterSchemaOrContent::Schema(Item(String::schema().into_schema())),
example: None,
examples: IndexMap::new()
},
style: Default::default(),
}));
}
}
fn add_query_params(self, params : &mut Vec<ReferenceOr<Parameter>>)
{
let query_params = match self.query_params {
Some(qp) => qp.schema,
None => return
};
let query_params = match query_params {
SchemaKind::Type(Type::Object(ty)) => ty,
_ => panic!("Query Parameters needs to be a plain struct")
};
for (name, schema) in query_params.properties
{
let required = query_params.required.contains(&name);
params.push(Item(Parameter::Query {
parameter_data: ParameterData {
name,
description: None,
required,
deprecated: None,
format: ParameterSchemaOrContent::Schema(schema.unbox()),
example: None,
examples: IndexMap::new()
},
allow_reserved: false,
style: Default::default(),
allow_empty_value: None
}))
}
}
fn into_params(self) -> Vec<ReferenceOr<Parameter>>
{
let mut params : Vec<ReferenceOr<Parameter>> = Vec::new();
self.add_path_params(&mut params);
self.add_query_params(&mut params);
params
}
}
fn new_operation(
operation_id : Option<String>,
default_status : crate::StatusCode,
accepted_types : Option<Vec<Mime>>,
schema : ReferenceOr<Schema>,
params : OperationParams,
body_schema : Option<ReferenceOr<Schema>>,
supported_types : Option<Vec<Mime>>,
requires_auth : bool
) -> Operation
{
let content = schema_to_content(accepted_types.unwrap_or_else(|| vec![STAR_STAR]), schema);
let mut responses : IndexMap<StatusCode, ReferenceOr<Response>> = IndexMap::new();
responses.insert(StatusCode::Code(default_status.as_u16()), Item(Response {
description: default_status.canonical_reason().map(|d| d.to_string()).unwrap_or_default(),
content,
..Default::default()
}));
let request_body = body_schema.map(|schema| Item(OARequestBody {
description: None,
content: schema_to_content(supported_types.unwrap_or_else(|| vec![STAR_STAR]), schema),
required: true
}));
let mut security = Vec::new();
if requires_auth
{
let mut sec = IndexMap::new();
sec.insert(SECURITY_NAME.to_owned(), Vec::new());
security.push(sec);
}
Operation {
tags: Vec::new(),
operation_id,
parameters: params.into_params(),
request_body,
responses: Responses {
default: None,
responses
},
deprecated: false,
security,
..Default::default()
}
}
macro_rules! implOpenapiRouter {
($implType:ident) => {
@ -206,7 +52,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}", &self.1);
let mut item = (self.0).1.remove_path(&path);
item.get = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), None, None, Handler::wants_auth()));
item.get = Some(OperationDescription::new::<Handler>(schema).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).read_all::<Handler>()
@ -218,7 +64,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}/{{id}}", &self.1);
let mut item = (self.0).1.remove_path(&path);
item.get = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_path_params(vec!["id"]), None, None, Handler::wants_auth()));
item.get = Some(OperationDescription::new::<Handler>(schema).with_path_params(vec!["id"]).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).read::<Handler>()
@ -230,7 +76,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}/search", &self.1);
let mut item = (self.0).1.remove_path(&self.1);
item.get = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_query_params(Handler::Query::schema()), None, None, Handler::wants_auth()));
item.get = Some(OperationDescription::new::<Handler>(schema).with_query_params(Handler::Query::schema()).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).search::<Handler>()
@ -246,7 +92,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}", &self.1);
let mut item = (self.0).1.remove_path(&path);
item.post = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), Some(body_schema), Handler::Body::supported_types(), Handler::wants_auth()));
item.post = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).create::<Handler>()
@ -262,7 +108,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}", &self.1);
let mut item = (self.0).1.remove_path(&path);
item.put = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), Some(body_schema), Handler::Body::supported_types(), Handler::wants_auth()));
item.put = Some(OperationDescription::new::<Handler>(schema).with_body::<Handler::Body>(body_schema).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).update_all::<Handler>()
@ -278,7 +124,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}/{{id}}", &self.1);
let mut item = (self.0).1.remove_path(&path);
item.put = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_path_params(vec!["id"]), Some(body_schema), Handler::Body::supported_types(), Handler::wants_auth()));
item.put = Some(OperationDescription::new::<Handler>(schema).with_path_params(vec!["id"]).with_body::<Handler::Body>(body_schema).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).update::<Handler>()
@ -290,7 +136,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}", &self.1);
let mut item = (self.0).1.remove_path(&path);
item.delete = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::default(), None, None, Handler::wants_auth()));
item.delete = Some(OperationDescription::new::<Handler>(schema).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).delete_all::<Handler>()
@ -302,7 +148,7 @@ macro_rules! implOpenapiRouter {
let path = format!("/{}/{{id}}", &self.1);
let mut item = (self.0).1.remove_path(&path);
item.delete = Some(new_operation(Handler::operation_id(), Handler::Res::default_status(), Handler::Res::accepted_types(), schema, OperationParams::from_path_params(vec!["id"]), None, None, Handler::wants_auth()));
item.delete = Some(OperationDescription::new::<Handler>(schema).with_path_params(vec!["id"]).into_operation());
(self.0).1.add_path(path, item);
(&mut *(self.0).0, self.1).delete::<Handler>()
@ -314,75 +160,3 @@ macro_rules! implOpenapiRouter {
implOpenapiRouter!(RouterBuilder);
implOpenapiRouter!(ScopeBuilder);
#[cfg(test)]
mod test
{
use crate::ResourceResult;
use super::*;
#[derive(OpenapiType)]
#[allow(dead_code)]
struct QueryParams
{
id : isize
}
#[test]
fn params_empty()
{
let op_params = OperationParams::default();
let params = op_params.into_params();
assert!(params.is_empty());
}
#[test]
fn params_from_path_params()
{
let name = "id";
let op_params = OperationParams::from_path_params(vec![name]);
let params = op_params.into_params();
let json = serde_json::to_string(&params).unwrap();
assert_eq!(json, format!(r#"[{{"in":"path","name":"{}","required":true,"schema":{{"type":"string"}},"style":"simple"}}]"#, name));
}
#[test]
fn params_from_query_params()
{
let op_params = OperationParams::from_query_params(QueryParams::schema());
let params = op_params.into_params();
let json = serde_json::to_string(&params).unwrap();
assert_eq!(json, r#"[{"in":"query","name":"id","required":true,"schema":{"type":"integer"},"style":"form"}]"#);
}
#[test]
fn params_both()
{
let name = "id";
let op_params = OperationParams::new(vec![name], Some(QueryParams::schema()));
let params = op_params.into_params();
let json = serde_json::to_string(&params).unwrap();
assert_eq!(json, format!(r#"[{{"in":"path","name":"{}","required":true,"schema":{{"type":"string"}},"style":"simple"}},{{"in":"query","name":"id","required":true,"schema":{{"type":"integer"}},"style":"form"}}]"#, name));
}
#[test]
fn no_content_schema_to_content()
{
let types = NoContent::accepted_types();
let schema = <NoContent as OpenapiType>::schema();
let content = schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
assert!(content.is_empty());
}
#[test]
fn raw_schema_to_content()
{
let types = Raw::<&str>::accepted_types();
let schema = <Raw<&str> as OpenapiType>::schema();
let content = schema_to_content(types.unwrap_or_else(|| vec![STAR_STAR]), Item(schema.into_schema()));
assert_eq!(content.len(), 1);
let json = serde_json::to_string(&content.values().nth(0).unwrap()).unwrap();
assert_eq!(json, r#"{"schema":{"type":"string","format":"binary"}}"#);
}
}