diff --git a/src/helper.rs b/src/helper.rs index 44d606b..24cf9a9 100644 --- a/src/helper.rs +++ b/src/helper.rs @@ -1,4 +1,11 @@ +#[cfg(feature = "openapi")] +pub mod openapi +{ + pub use indexmap::IndexMap; + pub use openapiv3::{ObjectType, ReferenceOr, Schema, SchemaData, SchemaKind, Type}; +} +#[cfg(not(feature = "openapi"))] #[macro_export] macro_rules! rest_struct { ($struct_name:ident { $($field_id:ident : $field_ty:ty),* }) => { @@ -10,6 +17,52 @@ macro_rules! rest_struct { } } +#[cfg(feature = "openapi")] +#[macro_export] +macro_rules! rest_struct { + ($struct_name:ident { $($field_id:ident : $field_ty:ty),* }) => { + #[derive(serde::Deserialize, serde::Serialize)] + struct $struct_name + { + $($field_id : $field_ty),* + } + + impl ::gotham_restful::OpenapiType for $struct_name + { + fn schema_name() -> Option + { + Some(stringify!($struct_name).to_string()) + } + + fn to_schema() -> ::gotham_restful::helper::openapi::SchemaKind + { + use ::gotham_restful::helper::openapi::*; + + let mut properties : IndexMap>> = IndexMap::new(); + let mut required : Vec = Vec::new(); + + $( + properties.insert( + stringify!($field_id).to_string(), + ReferenceOr::Item(Box::new(Schema { + schema_data: SchemaData::default(), + schema_kind: <$field_ty>::to_schema() + })) + ); + )* + + SchemaKind::Type(Type::Object(ObjectType { + properties, + required, + additional_properties: None, + min_properties: None, + max_properties: None + })) + } + } + } +} + #[macro_export] macro_rules! rest_resource { ($res_name:ident, $route:ident => $setup:block) => { diff --git a/src/openapi/router.rs b/src/openapi/router.rs index 5de695f..76d637c 100644 --- a/src/openapi/router.rs +++ b/src/openapi/router.rs @@ -15,7 +15,8 @@ use indexmap::IndexMap; use log::error; use mime::{APPLICATION_JSON, TEXT_PLAIN}; use openapiv3::{ - MediaType, OpenAPI, Operation, PathItem, Paths, ReferenceOr, ReferenceOr::Item, Response, Responses, Server, StatusCode + Components, MediaType, OpenAPI, Operation, PathItem, Paths, ReferenceOr, ReferenceOr::Item, ReferenceOr::Reference, + Response, Responses, Schema, SchemaData, Server, StatusCode }; use serde::de::DeserializeOwned; use std::panic::RefUnwindSafe; @@ -57,24 +58,27 @@ impl OpenapiRouter { return item; } - return PathItem { - get: None, - put: None, - post: None, - delete: None, - options: None, - head: None, - patch: None, - trace: None, - servers: Vec::new(), - parameters: Vec::new() - }; + return PathItem::default() } fn add_path(&mut self, path : Path, item : PathItem) { self.0.paths.insert(path.to_string(), Item(item)); } + + fn add_schema(&mut self, name : Name, item : Schema) + { + 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); + } + }; + } } #[derive(Clone)] @@ -159,11 +163,21 @@ macro_rules! implOpenapiRouter { Res : ResourceResult, Handler : ResourceReadAll { - let path = &self.1; - let mut item = (self.0).1.remove_path(path); + let schema = Res::schema_name().unwrap_or_else(|| { + format!("Resource_{}_ReadAllResult", self.1) + }); + (self.0).1.add_schema(&schema, Schema { + schema_data: SchemaData::default(), + schema_kind: Res::to_schema() + }); + + let path = format!("/{}", &self.1); + let mut item = (self.0).1.remove_path(&path); let mut content : IndexMap = IndexMap::new(); content.insert(APPLICATION_JSON.to_string(), MediaType { - schema: None, // TODO + schema: Some(Reference { + reference: format!("#/components/schemas/{}", schema) + }), example: None, examples: IndexMap::new(), encoding: IndexMap::new() diff --git a/src/openapi/types.rs b/src/openapi/types.rs index ba37f61..8ed2851 100644 --- a/src/openapi/types.rs +++ b/src/openapi/types.rs @@ -1,12 +1,17 @@ use indexmap::IndexMap; use openapiv3::{ - IntegerType, NumberType, ObjectType, SchemaKind, StringType, Type + ArrayType, IntegerType, NumberType, ObjectType, ReferenceOr::Item, Schema, SchemaData, SchemaKind, StringType, Type }; use serde::Serialize; pub trait OpenapiType : Serialize { + fn schema_name() -> Option + { + None + } + fn to_schema() -> SchemaKind; } @@ -67,3 +72,24 @@ macro_rules! str_types { } str_types!(String, &str); + +impl OpenapiType for Vec +{ + fn schema_name() -> Option + { + T::schema_name().map(|name| format!("{}Array", name)) + } + + fn to_schema() -> SchemaKind + { + SchemaKind::Type(Type::Array(ArrayType { + items: Item(Box::new(Schema { + schema_data: SchemaData::default(), + schema_kind: T::to_schema() + })), + min_items: None, + max_items: None, + unique_items: false + })) + } +} diff --git a/src/result.rs b/src/result.rs index 79f42c8..97d3f26 100644 --- a/src/result.rs +++ b/src/result.rs @@ -8,7 +8,10 @@ use std::error::Error; pub trait ResourceResult { fn to_json(&self) -> Result<(StatusCode, String), SerdeJsonError>; - + + #[cfg(feature = "openapi")] + fn schema_name() -> Option; + #[cfg(feature = "openapi")] fn to_schema() -> SchemaKind; } @@ -44,7 +47,14 @@ impl ResourceResult for Result } }) } - + + #[cfg(feature = "openapi")] + fn schema_name() -> Option + { + R::schema_name() + } + + #[cfg(feature = "openapi")] fn to_schema() -> SchemaKind { R::to_schema() @@ -68,7 +78,14 @@ impl ResourceResult for Success { Ok((StatusCode::OK, serde_json::to_string(&self.0)?)) } - + + #[cfg(feature = "openapi")] + fn schema_name() -> Option + { + T::schema_name() + } + + #[cfg(feature = "openapi")] fn to_schema() -> SchemaKind { T::to_schema()