From 667009bd228dbee456b44d2f41342b9e4dec684e Mon Sep 17 00:00:00 2001 From: Dominic Date: Mon, 8 Mar 2021 16:33:38 +0100 Subject: [PATCH] copy OpenapiType implementations and fix codegen reference --- openapi_type/Cargo.toml | 2 + openapi_type/src/impls.rs | 321 +++++++++++++++++++++++++++++ openapi_type/src/lib.rs | 4 + openapi_type/src/private.rs | 12 ++ openapi_type/tests/custom_types.rs | 44 ++++ openapi_type_derive/src/codegen.rs | 5 +- openapi_type_derive/src/lib.rs | 17 +- 7 files changed, 388 insertions(+), 17 deletions(-) create mode 100644 openapi_type/src/impls.rs create mode 100644 openapi_type/src/private.rs create mode 100644 openapi_type/tests/custom_types.rs diff --git a/openapi_type/Cargo.toml b/openapi_type/Cargo.toml index 5e2972a..028c753 100644 --- a/openapi_type/Cargo.toml +++ b/openapi_type/Cargo.toml @@ -15,6 +15,8 @@ repository = "https://gitlab.com/msrd0/gotham-restful/-/tree/master/openapi_type indexmap = "1.6" openapi_type_derive = "0.1.0-dev" openapiv3 = "=0.3.2" +serde_json = "1.0" [dev-dependencies] +paste = "1.0" trybuild = "1.0" diff --git a/openapi_type/src/impls.rs b/openapi_type/src/impls.rs new file mode 100644 index 0000000..f363818 --- /dev/null +++ b/openapi_type/src/impls.rs @@ -0,0 +1,321 @@ +use crate::{OpenapiSchema, OpenapiType}; +use indexmap::IndexMap; +use openapiv3::{ + AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, ReferenceOr, SchemaKind, StringType, + Type, VariantOrUnknownOrEmpty +}; +use std::{ + collections::{BTreeSet, HashMap, HashSet}, + hash::BuildHasher, + num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize} +}; + +impl OpenapiType for () { + fn schema() -> OpenapiSchema { + OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType { + additional_properties: Some(AdditionalProperties::Any(false)), + ..Default::default() + }))) + } +} + +impl OpenapiType for bool { + fn schema() -> OpenapiSchema { + OpenapiSchema::new(SchemaKind::Type(Type::Boolean {})) + } +} + +macro_rules! int_types { + ($($int_ty:ty),*) => {$( + impl OpenapiType for $int_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType::default()))) + } + } + )*}; + + (unsigned $($int_ty:ty),*) => {$( + impl OpenapiType for $int_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { + minimum: Some(0), + ..Default::default() + }))) + } + } + )*}; + + (gtzero $($int_ty:ty),*) => {$( + impl OpenapiType for $int_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { + minimum: Some(1), + ..Default::default() + }))) + } + } + )*}; + + (bits = $bits:expr, $($int_ty:ty),*) => {$( + impl OpenapiType for $int_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { + format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)), + ..Default::default() + }))) + } + } + )*}; + + (unsigned bits = $bits:expr, $($int_ty:ty),*) => {$( + impl OpenapiType for $int_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { + format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)), + minimum: Some(0), + ..Default::default() + }))) + } + } + )*}; + + (gtzero bits = $bits:expr, $($int_ty:ty),*) => {$( + impl OpenapiType for $int_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { + format: VariantOrUnknownOrEmpty::Unknown(format!("int{}", $bits)), + minimum: Some(1), + ..Default::default() + }))) + } + } + )*}; +} + +int_types!(isize); +int_types!(unsigned usize); +int_types!(gtzero NonZeroUsize); +int_types!(bits = 8, i8); +int_types!(unsigned bits = 8, u8); +int_types!(gtzero bits = 8, NonZeroU8); +int_types!(bits = 16, i16); +int_types!(unsigned bits = 16, u16); +int_types!(gtzero bits = 16, NonZeroU16); +int_types!(bits = 32, i32); +int_types!(unsigned bits = 32, u32); +int_types!(gtzero bits = 32, NonZeroU32); +int_types!(bits = 64, i64); +int_types!(unsigned bits = 64, u64); +int_types!(gtzero bits = 64, NonZeroU64); +int_types!(bits = 128, i128); +int_types!(unsigned bits = 128, u128); +int_types!(gtzero bits = 128, NonZeroU128); + +macro_rules! num_types { + ($($num_ty:ty = $num_fmt:ident),*) => {$( + impl OpenapiType for $num_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::Number(NumberType { + format: VariantOrUnknownOrEmpty::Item(NumberFormat::$num_fmt), + ..Default::default() + }))) + } + } + )*} +} + +num_types!(f32 = Float, f64 = Double); + +macro_rules! str_types { + ($($str_ty:ty),*) => {$( + impl OpenapiType for $str_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::String(StringType::default()))) + } + } + )*}; + + (format = $format:ident, $($str_ty:ty),*) => {$( + impl OpenapiType for $str_ty + { + fn schema() -> OpenapiSchema + { + use openapiv3::StringFormat; + + OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { + format: VariantOrUnknownOrEmpty::Item(StringFormat::$format), + ..Default::default() + }))) + } + } + )*}; + + (format_str = $format:expr, $($str_ty:ty),*) => {$( + impl OpenapiType for $str_ty + { + fn schema() -> OpenapiSchema + { + OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { + format: VariantOrUnknownOrEmpty::Unknown($format.to_string()), + ..Default::default() + }))) + } + } + )*}; +} + +str_types!(String, &str); + +#[cfg(feature = "chrono")] +str_types!(format = Date, Date, Date, Date, NaiveDate); +#[cfg(feature = "chrono")] +str_types!( + format = DateTime, + DateTime, + DateTime, + DateTime, + NaiveDateTime +); + +#[cfg(feature = "uuid")] +str_types!(format_str = "uuid", Uuid); + +impl OpenapiType for Option { + fn schema() -> OpenapiSchema { + let schema = T::schema(); + let mut dependencies = schema.dependencies.clone(); + let schema = match schema.name.clone() { + Some(name) => { + let reference = ReferenceOr::Reference { + reference: format!("#/components/schemas/{}", name) + }; + dependencies.insert(name, schema); + SchemaKind::AllOf { all_of: vec![reference] } + }, + None => schema.schema + }; + + OpenapiSchema { + nullable: true, + name: None, + schema, + dependencies + } + } +} + +impl OpenapiType for Vec { + fn schema() -> OpenapiSchema { + let schema = T::schema(); + let mut dependencies = schema.dependencies.clone(); + + let items = match schema.name.clone() { + Some(name) => { + let reference = ReferenceOr::Reference { + reference: format!("#/components/schemas/{}", name) + }; + dependencies.insert(name, schema); + reference + }, + None => ReferenceOr::Item(Box::new(schema.into_schema())) + }; + + OpenapiSchema { + nullable: false, + name: None, + schema: SchemaKind::Type(Type::Array(ArrayType { + items, + min_items: None, + max_items: None, + unique_items: false + })), + dependencies + } + } +} + +impl OpenapiType for BTreeSet { + fn schema() -> OpenapiSchema { + as OpenapiType>::schema() + } +} + +impl OpenapiType for HashSet { + fn schema() -> OpenapiSchema { + as OpenapiType>::schema() + } +} + +impl OpenapiType for HashMap { + fn schema() -> OpenapiSchema { + let key_schema = K::schema(); + let mut dependencies = key_schema.dependencies.clone(); + + let keys = match key_schema.name.clone() { + Some(name) => { + let reference = ReferenceOr::Reference { + reference: format!("#/components/schemas/{}", name) + }; + dependencies.insert(name, key_schema); + reference + }, + None => ReferenceOr::Item(Box::new(key_schema.into_schema())) + }; + + let schema = T::schema(); + dependencies.extend(schema.dependencies.iter().map(|(k, v)| (k.clone(), v.clone()))); + + let items = Box::new(match schema.name.clone() { + Some(name) => { + let reference = ReferenceOr::Reference { + reference: format!("#/components/schemas/{}", name) + }; + dependencies.insert(name, schema); + reference + }, + None => ReferenceOr::Item(schema.into_schema()) + }); + + let mut properties = IndexMap::new(); + properties.insert("default".to_owned(), keys); + + OpenapiSchema { + nullable: false, + name: None, + schema: SchemaKind::Type(Type::Object(ObjectType { + properties, + required: vec!["default".to_owned()], + additional_properties: Some(AdditionalProperties::Schema(items)), + ..Default::default() + })), + dependencies + } + } +} + +impl OpenapiType for serde_json::Value { + fn schema() -> OpenapiSchema { + OpenapiSchema { + nullable: true, + name: None, + schema: SchemaKind::Any(Default::default()), + dependencies: Default::default() + } + } +} diff --git a/openapi_type/src/lib.rs b/openapi_type/src/lib.rs index b5c0341..590800b 100644 --- a/openapi_type/src/lib.rs +++ b/openapi_type/src/lib.rs @@ -9,6 +9,10 @@ pub use indexmap; pub use openapi_type_derive::OpenapiType; pub use openapiv3 as openapi; +mod impls; +#[doc(hidden)] +pub mod private; + use indexmap::IndexMap; use openapi::{Schema, SchemaData, SchemaKind}; diff --git a/openapi_type/src/private.rs b/openapi_type/src/private.rs new file mode 100644 index 0000000..892b8e3 --- /dev/null +++ b/openapi_type/src/private.rs @@ -0,0 +1,12 @@ +use crate::OpenapiSchema; +use indexmap::IndexMap; + +pub type Dependencies = IndexMap; + +pub fn add_dependencies(dependencies: &mut Dependencies, other: &mut Dependencies) { + while let Some((dep_name, dep_schema)) = other.pop() { + if !dependencies.contains_key(&dep_name) { + dependencies.insert(dep_name, dep_schema); + } + } +} diff --git a/openapi_type/tests/custom_types.rs b/openapi_type/tests/custom_types.rs new file mode 100644 index 0000000..ba52b41 --- /dev/null +++ b/openapi_type/tests/custom_types.rs @@ -0,0 +1,44 @@ +#![allow(dead_code)] +use openapi_type::OpenapiType; + +macro_rules! test_type { + ($ty:ty = $json:tt) => { + paste::paste! { + #[test] + fn [< $ty:lower >]() { + let schema = <$ty as OpenapiType>::schema(); + let schema = openapi_type::OpenapiSchema::into_schema(schema); + let schema_json = serde_json::to_value(&schema).unwrap(); + let expected = serde_json::json!($json); + assert_eq!(schema_json, expected); + } + } + }; +} + +#[derive(OpenapiType)] +struct UnitStruct; +test_type!(UnitStruct = { + "type": "object", + "title": "UnitStruct", + "additionalProperties": false +}); + +#[derive(OpenapiType)] +struct SimpleStruct { + foo: String, + bar: isize +} +test_type!(SimpleStruct = { + "type": "object", + "title": "SimpleStruct", + "properties": { + "foo": { + "type": "string" + }, + "bar": { + "type": "integer" + } + }, + "required": ["foo", "bar"] +}); diff --git a/openapi_type_derive/src/codegen.rs b/openapi_type_derive/src/codegen.rs index 9ae9db7..4aae6a3 100644 --- a/openapi_type_derive/src/codegen.rs +++ b/openapi_type_derive/src/codegen.rs @@ -29,7 +29,10 @@ fn gen_struct(fields: Vec<(LitStr, Type)>) -> syn::Result { #({ const FIELD_NAME: &::core::primitive::str = #field_name; let mut field_schema = <#field_ty as ::openapi_type::OpenapiType>::schema(); - add_dependencies(&mut field_schema.dependencies); + ::openapi_type::private::add_dependencies( + &mut dependencies, + &mut field_schema.dependencies + ); // fields in OpenAPI are nullable by default match field_schema.nullable { diff --git a/openapi_type_derive/src/lib.rs b/openapi_type_derive/src/lib.rs index af4f9be..cdbdbf3 100644 --- a/openapi_type_derive/src/lib.rs +++ b/openapi_type_derive/src/lib.rs @@ -59,22 +59,7 @@ fn expand_openapi_type(mut input: DeriveInput) -> syn::Result { impl #impl_generics ::openapi_type::OpenapiType for #ident #ty_generics #where_clause { fn schema() -> ::openapi_type::OpenapiSchema { // prepare the dependencies - let mut dependencies = <::openapi_type::indexmap::IndexMap< - ::std::string::String, - ::openapi_type::OpenapiSchema - >>::new(); - - // this function can be used to include dependencies of dependencies - let add_dependencies = |deps: &mut ::openapi_type::indexmap::IndexMap< - ::std::string::String, - ::openapi_type::OpenapiSchema - >| { - while let ::std::option::Option::Some((dep_name, dep_schema)) = deps.pop() { - if !dependencies.contains_key(&dep_name) { - dependencies.insert(dep_name, dep_schema); - } - } - }; + let mut dependencies = ::openapi_type::private::Dependencies::new(); // create the schema let schema = #schema_code;