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

copy OpenapiType implementations and fix codegen reference

This commit is contained in:
Dominic 2021-03-08 16:33:38 +01:00
parent d9c7f4135f
commit 667009bd22
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
7 changed files with 388 additions and 17 deletions

View file

@ -15,6 +15,8 @@ repository = "https://gitlab.com/msrd0/gotham-restful/-/tree/master/openapi_type
indexmap = "1.6" indexmap = "1.6"
openapi_type_derive = "0.1.0-dev" openapi_type_derive = "0.1.0-dev"
openapiv3 = "=0.3.2" openapiv3 = "=0.3.2"
serde_json = "1.0"
[dev-dependencies] [dev-dependencies]
paste = "1.0"
trybuild = "1.0" trybuild = "1.0"

321
openapi_type/src/impls.rs Normal file
View file

@ -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<FixedOffset>, Date<Local>, Date<Utc>, NaiveDate);
#[cfg(feature = "chrono")]
str_types!(
format = DateTime,
DateTime<FixedOffset>,
DateTime<Local>,
DateTime<Utc>,
NaiveDateTime
);
#[cfg(feature = "uuid")]
str_types!(format_str = "uuid", Uuid);
impl<T: OpenapiType> OpenapiType for Option<T> {
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<T: OpenapiType> OpenapiType for Vec<T> {
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<T: OpenapiType> OpenapiType for BTreeSet<T> {
fn schema() -> OpenapiSchema {
<Vec<T> as OpenapiType>::schema()
}
}
impl<T: OpenapiType, S: BuildHasher> OpenapiType for HashSet<T, S> {
fn schema() -> 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 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()
}
}
}

View file

@ -9,6 +9,10 @@ pub use indexmap;
pub use openapi_type_derive::OpenapiType; pub use openapi_type_derive::OpenapiType;
pub use openapiv3 as openapi; pub use openapiv3 as openapi;
mod impls;
#[doc(hidden)]
pub mod private;
use indexmap::IndexMap; use indexmap::IndexMap;
use openapi::{Schema, SchemaData, SchemaKind}; use openapi::{Schema, SchemaData, SchemaKind};

View file

@ -0,0 +1,12 @@
use crate::OpenapiSchema;
use indexmap::IndexMap;
pub type Dependencies = IndexMap<String, OpenapiSchema>;
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);
}
}
}

View file

@ -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"]
});

View file

@ -29,7 +29,10 @@ fn gen_struct(fields: Vec<(LitStr, Type)>) -> syn::Result<TokenStream> {
#({ #({
const FIELD_NAME: &::core::primitive::str = #field_name; const FIELD_NAME: &::core::primitive::str = #field_name;
let mut field_schema = <#field_ty as ::openapi_type::OpenapiType>::schema(); 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 // fields in OpenAPI are nullable by default
match field_schema.nullable { match field_schema.nullable {

View file

@ -59,22 +59,7 @@ fn expand_openapi_type(mut input: DeriveInput) -> syn::Result<TokenStream2> {
impl #impl_generics ::openapi_type::OpenapiType for #ident #ty_generics #where_clause { impl #impl_generics ::openapi_type::OpenapiType for #ident #ty_generics #where_clause {
fn schema() -> ::openapi_type::OpenapiSchema { fn schema() -> ::openapi_type::OpenapiSchema {
// prepare the dependencies // prepare the dependencies
let mut dependencies = <::openapi_type::indexmap::IndexMap< let mut dependencies = ::openapi_type::private::Dependencies::new();
::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);
}
}
};
// create the schema // create the schema
let schema = #schema_code; let schema = #schema_code;