1
0
Fork 0
mirror of https://gitlab.com/msrd0/gotham-restful.git synced 2025-04-19 06:24:45 +00:00

redo and test openapi type implementations

This commit is contained in:
Dominic 2021-03-09 17:07:16 +01:00
parent 2a35e044db
commit eecd192458
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
4 changed files with 413 additions and 291 deletions

View file

@ -17,6 +17,10 @@ openapi_type_derive = "0.1.0-dev"
openapiv3 = "=0.3.2" openapiv3 = "=0.3.2"
serde_json = "1.0" serde_json = "1.0"
# optional dependencies / features
chrono = { version = "0.4.19", optional = true }
uuid = { version = "0.8.2" , optional = true }
[dev-dependencies] [dev-dependencies]
paste = "1.0" paste = "1.0"
serde = "1.0" serde = "1.0"

View file

@ -1,203 +1,120 @@
use crate::{OpenapiSchema, OpenapiType}; use crate::{OpenapiSchema, OpenapiType};
use indexmap::IndexMap; #[cfg(feature = "chrono")]
use chrono::{offset::TimeZone, Date, DateTime, NaiveDate, NaiveDateTime};
use indexmap::{IndexMap, IndexSet};
use openapiv3::{ use openapiv3::{
AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, ReferenceOr, SchemaKind, StringType, AdditionalProperties, ArrayType, IntegerType, NumberFormat, NumberType, ObjectType, ReferenceOr, SchemaKind,
Type, VariantOrUnknownOrEmpty StringFormat, StringType, Type, VariantOrUnknownOrEmpty
}; };
use serde_json::Value;
use std::{ use std::{
collections::{BTreeSet, HashMap, HashSet}, collections::{BTreeMap, BTreeSet, HashMap, HashSet},
hash::BuildHasher, hash::BuildHasher,
num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize} num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}
}; };
#[cfg(feature = "uuid")]
use uuid::Uuid;
impl OpenapiType for () { macro_rules! impl_openapi_type {
($($ty:ident $(<$($generic:ident : $bound:path),+>)*),* => $schema:expr) => {
$(
impl $(<$($generic : $bound),+>)* OpenapiType for $ty $(<$($generic),+>)* {
fn schema() -> OpenapiSchema { fn schema() -> OpenapiSchema {
$schema
}
}
)*
};
}
type Unit = ();
impl_openapi_type!(Unit => {
OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType { OpenapiSchema::new(SchemaKind::Type(Type::Object(ObjectType {
additional_properties: Some(AdditionalProperties::Any(false)), additional_properties: Some(AdditionalProperties::Any(false)),
..Default::default() ..Default::default()
}))) })))
});
impl_openapi_type!(Value => {
OpenapiSchema {
nullable: true,
name: None,
schema: SchemaKind::Any(Default::default()),
dependencies: Default::default()
} }
});
impl_openapi_type!(bool => OpenapiSchema::new(SchemaKind::Type(Type::Boolean {})));
#[inline]
fn int_schema(minimum: Option<i64>, bits: Option<i64>) -> OpenapiSchema {
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType {
minimum,
format: bits
.map(|bits| VariantOrUnknownOrEmpty::Unknown(format!("int{}", bits)))
.unwrap_or(VariantOrUnknownOrEmpty::Empty),
..Default::default()
})))
} }
impl OpenapiType for bool { impl_openapi_type!(isize => int_schema(None, None));
fn schema() -> OpenapiSchema { impl_openapi_type!(i8 => int_schema(None, Some(8)));
OpenapiSchema::new(SchemaKind::Type(Type::Boolean {})) impl_openapi_type!(i16 => int_schema(None, Some(16)));
} impl_openapi_type!(i32 => int_schema(None, Some(32)));
} impl_openapi_type!(i64 => int_schema(None, Some(64)));
impl_openapi_type!(i128 => int_schema(None, Some(128)));
macro_rules! int_types { impl_openapi_type!(usize => int_schema(Some(0), None));
($($int_ty:ty),*) => {$( impl_openapi_type!(u8 => int_schema(Some(0), Some(8)));
impl OpenapiType for $int_ty impl_openapi_type!(u16 => int_schema(Some(0), Some(16)));
{ impl_openapi_type!(u32 => int_schema(Some(0), Some(32)));
fn schema() -> OpenapiSchema impl_openapi_type!(u64 => int_schema(Some(0), Some(64)));
{ impl_openapi_type!(u128 => int_schema(Some(0), Some(128)));
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType::default())))
}
}
)*};
(unsigned $($int_ty:ty),*) => {$( impl_openapi_type!(NonZeroUsize => int_schema(Some(1), None));
impl OpenapiType for $int_ty impl_openapi_type!(NonZeroU8 => int_schema(Some(1), Some(8)));
{ impl_openapi_type!(NonZeroU16 => int_schema(Some(1), Some(16)));
fn schema() -> OpenapiSchema impl_openapi_type!(NonZeroU32 => int_schema(Some(1), Some(32)));
{ impl_openapi_type!(NonZeroU64 => int_schema(Some(1), Some(64)));
OpenapiSchema::new(SchemaKind::Type(Type::Integer(IntegerType { impl_openapi_type!(NonZeroU128 => int_schema(Some(1), Some(128)));
minimum: Some(0),
..Default::default()
})))
}
}
)*};
(gtzero $($int_ty:ty),*) => {$( #[inline]
impl OpenapiType for $int_ty fn float_schema(format: NumberFormat) -> OpenapiSchema {
{
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 { OpenapiSchema::new(SchemaKind::Type(Type::Number(NumberType {
format: VariantOrUnknownOrEmpty::Item(NumberFormat::$num_fmt), format: VariantOrUnknownOrEmpty::Item(format),
..Default::default() ..Default::default()
}))) })))
}
}
)*}
} }
num_types!(f32 = Float, f64 = Double); impl_openapi_type!(f32 => float_schema(NumberFormat::Float));
impl_openapi_type!(f64 => float_schema(NumberFormat::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;
#[inline]
fn str_schema(format: VariantOrUnknownOrEmpty<StringFormat>) -> OpenapiSchema {
OpenapiSchema::new(SchemaKind::Type(Type::String(StringType { OpenapiSchema::new(SchemaKind::Type(Type::String(StringType {
format: VariantOrUnknownOrEmpty::Item(StringFormat::$format), format,
..Default::default() ..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); impl_openapi_type!(String => str_schema(VariantOrUnknownOrEmpty::Empty));
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
str_types!(format = Date, Date<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate); impl_openapi_type!(Date<T: TimeZone>, NaiveDate => {
str_schema(VariantOrUnknownOrEmpty::Item(StringFormat::Date))
});
#[cfg(feature = "chrono")] #[cfg(feature = "chrono")]
str_types!( impl_openapi_type!(DateTime<T: TimeZone>, NaiveDateTime => {
format = DateTime, str_schema(VariantOrUnknownOrEmpty::Item(StringFormat::DateTime))
DateTime<FixedOffset>, });
DateTime<Local>,
DateTime<Utc>,
NaiveDateTime
);
#[cfg(feature = "uuid")] #[cfg(feature = "uuid")]
str_types!(format_str = "uuid", Uuid); impl_openapi_type!(Uuid => {
str_schema(VariantOrUnknownOrEmpty::Unknown("uuid".to_owned()))
});
impl<T: OpenapiType> OpenapiType for Option<T> { impl_openapi_type!(Option<T: OpenapiType> => {
fn schema() -> OpenapiSchema {
let schema = T::schema(); let schema = T::schema();
let mut dependencies = schema.dependencies.clone(); let mut dependencies = schema.dependencies.clone();
let schema = match schema.name.clone() { let schema = match schema.name.clone() {
@ -217,11 +134,10 @@ impl<T: OpenapiType> OpenapiType for Option<T> {
schema, schema,
dependencies dependencies
} }
} });
}
impl<T: OpenapiType> OpenapiType for Vec<T> { #[inline]
fn schema() -> OpenapiSchema { fn array_schema<T: OpenapiType>(unique_items: bool) -> OpenapiSchema {
let schema = T::schema(); let schema = T::schema();
let mut dependencies = schema.dependencies.clone(); let mut dependencies = schema.dependencies.clone();
@ -243,27 +159,19 @@ impl<T: OpenapiType> OpenapiType for Vec<T> {
items, items,
min_items: None, min_items: None,
max_items: None, max_items: None,
unique_items: false unique_items
})), })),
dependencies dependencies
} }
}
} }
impl<T: OpenapiType> OpenapiType for BTreeSet<T> { impl_openapi_type!(Vec<T: OpenapiType> => array_schema::<T>(false));
fn schema() -> OpenapiSchema { impl_openapi_type!(BTreeSet<T: OpenapiType>, IndexSet<T: OpenapiType>, HashSet<T: OpenapiType, S: BuildHasher> => {
<Vec<T> as OpenapiType>::schema() array_schema::<T>(true)
} });
}
impl<T: OpenapiType, S: BuildHasher> OpenapiType for HashSet<T, S> { #[inline]
fn schema() -> OpenapiSchema { fn map_schema<K: OpenapiType, T: OpenapiType>() -> OpenapiSchema {
<Vec<T> as OpenapiType>::schema()
}
}
impl<K: OpenapiType, T: OpenapiType, S: BuildHasher> OpenapiType for HashMap<K, T, S> {
fn schema() -> OpenapiSchema {
let key_schema = K::schema(); let key_schema = K::schema();
let mut dependencies = key_schema.dependencies.clone(); let mut dependencies = key_schema.dependencies.clone();
@ -306,16 +214,11 @@ impl<K: OpenapiType, T: OpenapiType, S: BuildHasher> OpenapiType for HashMap<K,
})), })),
dependencies dependencies
} }
}
} }
impl OpenapiType for serde_json::Value { impl_openapi_type!(
fn schema() -> OpenapiSchema { BTreeMap<K: OpenapiType, T: OpenapiType>,
OpenapiSchema { IndexMap<K: OpenapiType, T: OpenapiType>,
nullable: true, HashMap<K: OpenapiType, T: OpenapiType, S: BuildHasher>
name: None, => map_schema::<K, T>()
schema: SchemaKind::Any(Default::default()), );
dependencies: Default::default()
}
}
}

View file

@ -0,0 +1,216 @@
#[cfg(feature = "chrono")]
use chrono::{Date, DateTime, FixedOffset, Local, NaiveDate, NaiveDateTime, Utc};
use indexmap::{IndexMap, IndexSet};
use openapi_type::OpenapiType;
use serde_json::Value;
use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
num::{NonZeroU128, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize}
};
#[cfg(feature = "uuid")]
use uuid::Uuid;
macro_rules! test_type {
($($ty:ident $(<$($generic:ident),+>)*),* = $json:tt) => {
paste::paste! { $(
#[test]
fn [< $ty:lower $($(_ $generic:lower)+)* >]() {
let schema = <$ty $(<$($generic),+>)* 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);
}
)* }
};
}
type Unit = ();
test_type!(Unit = {
"type": "object",
"additionalProperties": false
});
test_type!(Value = {
"nullable": true
});
test_type!(bool = {
"type": "boolean"
});
// ### integer types
test_type!(isize = {
"type": "integer"
});
test_type!(usize = {
"type": "integer",
"minimum": 0
});
test_type!(i8 = {
"type": "integer",
"format": "int8"
});
test_type!(u8 = {
"type": "integer",
"format": "int8",
"minimum": 0
});
test_type!(i16 = {
"type": "integer",
"format": "int16"
});
test_type!(u16 = {
"type": "integer",
"format": "int16",
"minimum": 0
});
test_type!(i32 = {
"type": "integer",
"format": "int32"
});
test_type!(u32 = {
"type": "integer",
"format": "int32",
"minimum": 0
});
test_type!(i64 = {
"type": "integer",
"format": "int64"
});
test_type!(u64 = {
"type": "integer",
"format": "int64",
"minimum": 0
});
test_type!(i128 = {
"type": "integer",
"format": "int128"
});
test_type!(u128 = {
"type": "integer",
"format": "int128",
"minimum": 0
});
// ### non-zero integer types
test_type!(NonZeroUsize = {
"type": "integer",
"minimum": 1
});
test_type!(NonZeroU8 = {
"type": "integer",
"format": "int8",
"minimum": 1
});
test_type!(NonZeroU16 = {
"type": "integer",
"format": "int16",
"minimum": 1
});
test_type!(NonZeroU32 = {
"type": "integer",
"format": "int32",
"minimum": 1
});
test_type!(NonZeroU64 = {
"type": "integer",
"format": "int64",
"minimum": 1
});
test_type!(NonZeroU128 = {
"type": "integer",
"format": "int128",
"minimum": 1
});
// ### floats
test_type!(f32 = {
"type": "number",
"format": "float"
});
test_type!(f64 = {
"type": "number",
"format": "double"
});
// ### string
test_type!(String = {
"type": "string"
});
#[cfg(feature = "uuid")]
test_type!(Uuid = {
"type": "string",
"format": "uuid"
});
// ### date/time
#[cfg(feature = "chrono")]
test_type!(Date<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate = {
"type": "string",
"format": "date"
});
#[cfg(feature = "chrono")]
test_type!(DateTime<FixedOffset>, DateTime<Local>, DateTime<Utc>, NaiveDateTime = {
"type": "string",
"format": "date-time"
});
// ### some std types
test_type!(Option<String> = {
"type": "string",
"nullable": true
});
test_type!(Vec<String> = {
"type": "array",
"items": {
"type": "string"
}
});
test_type!(BTreeSet<String>, IndexSet<String>, HashSet<String> = {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
});
test_type!(BTreeMap<isize, String>, IndexMap<isize, String>, HashMap<isize, String> = {
"type": "object",
"properties": {
"default": {
"type": "integer"
}
},
"required": ["default"],
"additionalProperties": {
"type": "string"
}
});

View file

@ -7,7 +7,6 @@ use openapiv3::{
ReferenceOr::{Item, Reference}, ReferenceOr::{Item, Reference},
Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty Schema, SchemaData, SchemaKind, StringType, Type, VariantOrUnknownOrEmpty
}; };
use std::{ use std::{
collections::{BTreeSet, HashMap, HashSet}, collections::{BTreeSet, HashMap, HashSet},
hash::BuildHasher, hash::BuildHasher,