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:
parent
d9c7f4135f
commit
667009bd22
7 changed files with 388 additions and 17 deletions
|
@ -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
321
openapi_type/src/impls.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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};
|
||||||
|
|
||||||
|
|
12
openapi_type/src/private.rs
Normal file
12
openapi_type/src/private.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
openapi_type/tests/custom_types.rs
Normal file
44
openapi_type/tests/custom_types.rs
Normal 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"]
|
||||||
|
});
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Reference in a new issue