Split JsonSchema implementations into separate files

This commit is contained in:
Graham Esau 2019-09-07 21:58:31 +01:00
parent a68b105450
commit 00148bdf57
11 changed files with 439 additions and 354 deletions

View file

@ -1,354 +0,0 @@
use crate::gen::{BoolSchemas, SchemaGenerator};
use crate::schema::*;
use crate::{JsonSchema, Map, Result};
use serde_json::json;
// TODO any other serde/json types other than serde_json value?
// TODO serde yaml value/map under feature flag
// TODO add some inline attributes
// https://github.com/serde-rs/serde/blob/ce75418e40a593fc5c0902cbf4a45305a4178dd7/serde/src/ser/impls.rs
// Cell<T>, RefCell<T>, Mutex<T>, RwLock<T>, Result<R,E>?, Duration, SystemTime,
// IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6, SocketAddrV6,
// Path, PathBuf, OsStr, OsString, Wrapping<T>, Reverse<T>, AtomicBool, AtomixI8 etc.,
// NonZeroU8 etc., ArcWeak, RcWeak, BTreeMap, HashMap, (!)?, Bound?, Range?, RangeInclusive?,
// PhantomData?, CString?, CStr?, fmt::Arguments?
macro_rules! no_ref_schema {
() => {
fn is_referenceable() -> bool {
false
}
};
}
////////// PRIMITIVES //////////
macro_rules! simple_impl {
($type:tt => $instance_type:ident) => {
impl JsonSchema for $type {
no_ref_schema!();
fn schema_name() -> String {
stringify!($instance_type).to_owned()
}
fn json_schema(_: &mut SchemaGenerator) -> Result {
Ok(SchemaObject {
instance_type: Some(InstanceType::$instance_type.into()),
..Default::default()
}
.into())
}
}
};
}
simple_impl!(str => String);
simple_impl!(String => String);
simple_impl!(bool => Boolean);
simple_impl!(f32 => Number);
simple_impl!(f64 => Number);
simple_impl!(i8 => Integer);
simple_impl!(i16 => Integer);
simple_impl!(i32 => Integer);
simple_impl!(i64 => Integer);
simple_impl!(i128 => Integer);
simple_impl!(isize => Integer);
simple_impl!(u8 => Integer);
simple_impl!(u16 => Integer);
simple_impl!(u32 => Integer);
simple_impl!(u64 => Integer);
simple_impl!(u128 => Integer);
simple_impl!(usize => Integer);
simple_impl!(() => Null);
impl JsonSchema for char {
no_ref_schema!();
fn schema_name() -> String {
"Character".to_owned()
}
fn json_schema(_: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("minLength".to_owned(), json!(1));
extensions.insert("maxLength".to_owned(), json!(1));
Ok(SchemaObject {
instance_type: Some(InstanceType::String.into()),
extensions,
..Default::default()
}
.into())
}
}
////////// ARRAYS //////////
// Does not require T: JsonSchema.
impl<T> JsonSchema for [T; 0] {
no_ref_schema!();
fn schema_name() -> String {
"Empty_Array".to_owned()
}
fn json_schema(_: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("maxItems".to_owned(), json!(0));
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
extensions,
..Default::default()
}
.into())
}
}
macro_rules! array_impls {
($($len:tt)+) => {
$(
impl<T: JsonSchema> JsonSchema for [T; $len] {
no_ref_schema!();
fn schema_name() -> String {
format!("Array_Size_{}_Of_{}", $len, T::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("minItems".to_owned(), json!($len));
extensions.insert("maxItems".to_owned(), json!($len));
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
items: Some(gen.subschema_for::<T>()?.into()),
extensions,
..Default::default()
}.into())
}
}
)+
}
}
array_impls! {
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32
}
////////// TUPLES //////////
macro_rules! tuple_impls {
($($len:expr => ($($name:ident)+))+) => {
$(
impl<$($name: JsonSchema),+> JsonSchema for ($($name,)+) {
no_ref_schema!();
fn schema_name() -> String {
["Tuple_Of".to_owned()$(, $name::schema_name())+].join("_And_")
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("minItems".to_owned(), json!($len));
extensions.insert("maxItems".to_owned(), json!($len));
let items = vec![
$(gen.subschema_for::<$name>()?),+
];
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
items: Some(items.into()),
extensions,
..Default::default()
}.into())
}
}
)+
}
}
tuple_impls! {
1 => (T0)
2 => (T0 T1)
3 => (T0 T1 T2)
4 => (T0 T1 T2 T3)
5 => (T0 T1 T2 T3 T4)
6 => (T0 T1 T2 T3 T4 T5)
7 => (T0 T1 T2 T3 T4 T5 T6)
8 => (T0 T1 T2 T3 T4 T5 T6 T7)
9 => (T0 T1 T2 T3 T4 T5 T6 T7 T8)
10 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9)
11 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10)
12 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11)
13 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12)
14 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13)
15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14)
16 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15)
}
////////// SEQUENCES /////////
macro_rules! seq_impl {
($($desc:tt)+) => {
impl $($desc)+
where
T: JsonSchema,
{
no_ref_schema!();
fn schema_name() -> String {
format!("Array_Of_{}", T::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
items: Some(gen.subschema_for::<T>()?.into()),
..Default::default()
}.into())
}
}
};
}
seq_impl!(<T: Ord> JsonSchema for std::collections::BinaryHeap<T>);
seq_impl!(<T: Ord> JsonSchema for std::collections::BTreeSet<T>);
seq_impl!(<T: Eq + core::hash::Hash, H: core::hash::BuildHasher> JsonSchema for std::collections::HashSet<T, H>);
seq_impl!(<T> JsonSchema for std::collections::LinkedList<T>);
seq_impl!(<T> JsonSchema for Vec<T>);
seq_impl!(<T> JsonSchema for std::collections::VecDeque<T>);
////////// MAPS /////////
macro_rules! map_impl {
($($desc:tt)+) => {
impl $($desc)+
where
K: Into<String>,
V: JsonSchema,
{
no_ref_schema!();
fn schema_name() -> String {
format!("Map_Of_{}", V::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let subschema = gen.subschema_for::<V>()?;
let json_schema_bool = gen.settings().bool_schemas == BoolSchemas::AdditionalPropertiesOnly
&& subschema == gen.schema_for_any();
let mut extensions = Map::new();
extensions.insert(
"additionalProperties".to_owned(),
if json_schema_bool {
json!(true)
} else {
json!(subschema)
}
);
Ok(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
extensions,
..Default::default()
}.into())
}
}
};
}
map_impl!(<K: Ord, V> JsonSchema for std::collections::BTreeMap<K, V>);
map_impl!(<K: Eq + core::hash::Hash, V, H: core::hash::BuildHasher> JsonSchema for std::collections::HashMap<K, V, H>);
////////// OPTION //////////
// TODO should a field with a default set also be considered nullable?
impl<T: JsonSchema> JsonSchema for Option<T> {
no_ref_schema!();
fn schema_name() -> String {
format!("Nullable_{}", T::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let mut schema = if gen.settings().option_nullable {
T::json_schema(gen)?
} else {
gen.subschema_for::<T>()?
};
if gen.settings().option_add_null_type {
schema = match schema {
Schema::Bool(true) => Schema::Bool(true),
Schema::Bool(false) => <()>::json_schema(gen)?,
schema => SchemaObject {
any_of: Some(vec![schema, <()>::json_schema(gen)?]),
..Default::default()
}
.into(),
}
}
if gen.settings().option_nullable {
let mut deref = gen.get_schema_object(&schema)?;
deref.extensions.insert("nullable".to_owned(), json!(true));
schema = Schema::Object(deref);
};
Ok(schema)
}
}
impl<T: ?Sized> JsonSchema for std::marker::PhantomData<T> {
no_ref_schema!();
fn schema_name() -> String {
<()>::schema_name()
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
<()>::json_schema(gen)
}
}
////////// DEREF //////////
macro_rules! deref_impl {
($($desc:tt)+) => {
impl $($desc)+
where
T: ?Sized + JsonSchema,
{
fn is_referenceable() -> bool {
T::is_referenceable()
}
fn schema_name() -> String {
T::schema_name()
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
T::json_schema(gen)
}
}
};
}
deref_impl!(<'a, T> JsonSchema for &'a T);
deref_impl!(<'a, T> JsonSchema for &'a mut T);
deref_impl!(<T> JsonSchema for Box<T>);
deref_impl!(<T> JsonSchema for std::rc::Rc<T>);
deref_impl!(<T> JsonSchema for std::sync::Arc<T>);
deref_impl!(<'a, T: ToOwned> JsonSchema for std::borrow::Cow<'a, T>);
////////// SERDE_JSON //////////
impl JsonSchema for serde_json::Value {
no_ref_schema!();
fn schema_name() -> String {
"Any_Value".to_owned()
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
Ok(gen.schema_for_any())
}
}

View file

@ -0,0 +1,76 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::{JsonSchema, Map, Result};
use serde_json::json;
// Does not require T: JsonSchema.
impl<T> JsonSchema for [T; 0] {
no_ref_schema!();
fn schema_name() -> String {
"Empty_Array".to_owned()
}
fn json_schema(_: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("maxItems".to_owned(), json!(0));
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
extensions,
..Default::default()
}
.into())
}
}
macro_rules! array_impls {
($($len:tt)+) => {
$(
impl<T: JsonSchema> JsonSchema for [T; $len] {
no_ref_schema!();
fn schema_name() -> String {
format!("Array_Size_{}_Of_{}", $len, T::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("minItems".to_owned(), json!($len));
extensions.insert("maxItems".to_owned(), json!($len));
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
items: Some(gen.subschema_for::<T>()?.into()),
extensions,
..Default::default()
}.into())
}
}
)+
}
}
array_impls! {
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{schema_for, schema_object_for};
use pretty_assertions::assert_eq;
#[test]
fn schema_for_array() {
let schema = schema_object_for::<[i32; 8]>();
assert_eq!(
schema.instance_type,
Some(SingleOrVec::from(InstanceType::Array))
);
assert_eq!(schema.extensions.get("minItems"), Some(&json!(8)));
assert_eq!(schema.extensions.get("maxItems"), Some(&json!(8)));
assert_eq!(schema.items, Some(SingleOrVec::from(schema_for::<i32>())));
}
}

View file

@ -0,0 +1,49 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::{JsonSchema, Result};
use serde_json::json;
impl<T: JsonSchema> JsonSchema for Option<T> {
no_ref_schema!();
fn schema_name() -> String {
format!("Nullable_{}", T::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let mut schema = if gen.settings().option_nullable {
T::json_schema(gen)?
} else {
gen.subschema_for::<T>()?
};
if gen.settings().option_add_null_type {
schema = match schema {
Schema::Bool(true) => Schema::Bool(true),
Schema::Bool(false) => <()>::json_schema(gen)?,
schema => SchemaObject {
any_of: Some(vec![schema, <()>::json_schema(gen)?]),
..Default::default()
}
.into(),
}
}
if gen.settings().option_nullable {
let mut deref = gen.get_schema_object(&schema)?;
deref.extensions.insert("nullable".to_owned(), json!(true));
schema = Schema::Object(deref);
};
Ok(schema)
}
}
impl<T: ?Sized> JsonSchema for std::marker::PhantomData<T> {
no_ref_schema!();
fn schema_name() -> String {
<()>::schema_name()
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
<()>::json_schema(gen)
}
}

View file

@ -0,0 +1,30 @@
use crate::gen::SchemaGenerator;
use crate::{JsonSchema, Result};
macro_rules! deref_impl {
($($desc:tt)+) => {
impl $($desc)+
where
T: ?Sized + JsonSchema,
{
fn is_referenceable() -> bool {
T::is_referenceable()
}
fn schema_name() -> String {
T::schema_name()
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
T::json_schema(gen)
}
}
};
}
deref_impl!(<'a, T> JsonSchema for &'a T);
deref_impl!(<'a, T> JsonSchema for &'a mut T);
deref_impl!(<T> JsonSchema for Box<T>);
deref_impl!(<T> JsonSchema for std::rc::Rc<T>);
deref_impl!(<T> JsonSchema for std::sync::Arc<T>);
deref_impl!(<'a, T: ToOwned> JsonSchema for std::borrow::Cow<'a, T>);

View file

@ -0,0 +1,43 @@
use crate::gen::{BoolSchemas, SchemaGenerator};
use crate::schema::*;
use crate::{JsonSchema, Map, Result};
use serde_json::json;
macro_rules! map_impl {
($($desc:tt)+) => {
impl $($desc)+
where
K: Into<String>,
V: JsonSchema,
{
no_ref_schema!();
fn schema_name() -> String {
format!("Map_Of_{}", V::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let subschema = gen.subschema_for::<V>()?;
let json_schema_bool = gen.settings().bool_schemas == BoolSchemas::AdditionalPropertiesOnly
&& subschema == gen.schema_for_any();
let mut extensions = Map::new();
extensions.insert(
"additionalProperties".to_owned(),
if json_schema_bool {
json!(true)
} else {
json!(subschema)
}
);
Ok(SchemaObject {
instance_type: Some(InstanceType::Object.into()),
extensions,
..Default::default()
}.into())
}
}
};
}
map_impl!(<K: Ord, V> JsonSchema for std::collections::BTreeMap<K, V>);
map_impl!(<K: Eq + core::hash::Hash, V, H: core::hash::BuildHasher> JsonSchema for std::collections::HashMap<K, V, H>);

View file

@ -0,0 +1,25 @@
macro_rules! no_ref_schema {
() => {
fn is_referenceable() -> bool {
false
}
};
}
mod array;
mod core;
mod deref;
mod maps;
mod primitives;
mod sequences;
mod serdejson;
mod tuple;
// TODO chrono types under feature flag
// TODO serde yaml value/map under feature flag
// https://github.com/serde-rs/serde/blob/ce75418e40a593fc5c0902cbf4a45305a4178dd7/serde/src/ser/impls.rs
// Cell<T>, RefCell<T>, Mutex<T>, RwLock<T>, Result<R,E>?, Duration, SystemTime,
// IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV6, SocketAddrV6,
// Path, PathBuf, OsStr, OsString, Wrapping<T>, Reverse<T>, AtomicBool, AtomixI8 etc.,
// NonZeroU8 etc., ArcWeak, RcWeak, (!)?, Bound?, Range?, RangeInclusive?,
// CString?, CStr?, fmt::Arguments?

View file

@ -0,0 +1,63 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::{JsonSchema, Map, Result};
use serde_json::json;
macro_rules! simple_impl {
($type:tt => $instance_type:ident) => {
impl JsonSchema for $type {
no_ref_schema!();
fn schema_name() -> String {
stringify!($instance_type).to_owned()
}
fn json_schema(_: &mut SchemaGenerator) -> Result {
Ok(SchemaObject {
instance_type: Some(InstanceType::$instance_type.into()),
..Default::default()
}
.into())
}
}
};
}
simple_impl!(str => String);
simple_impl!(String => String);
simple_impl!(bool => Boolean);
simple_impl!(f32 => Number);
simple_impl!(f64 => Number);
simple_impl!(i8 => Integer);
simple_impl!(i16 => Integer);
simple_impl!(i32 => Integer);
simple_impl!(i64 => Integer);
simple_impl!(i128 => Integer);
simple_impl!(isize => Integer);
simple_impl!(u8 => Integer);
simple_impl!(u16 => Integer);
simple_impl!(u32 => Integer);
simple_impl!(u64 => Integer);
simple_impl!(u128 => Integer);
simple_impl!(usize => Integer);
simple_impl!(() => Null);
impl JsonSchema for char {
no_ref_schema!();
fn schema_name() -> String {
"Character".to_owned()
}
fn json_schema(_: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("minLength".to_owned(), json!(1));
extensions.insert("maxLength".to_owned(), json!(1));
Ok(SchemaObject {
instance_type: Some(InstanceType::String.into()),
extensions,
..Default::default()
}
.into())
}
}

View file

@ -0,0 +1,33 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::{JsonSchema, Result};
macro_rules! seq_impl {
($($desc:tt)+) => {
impl $($desc)+
where
T: JsonSchema,
{
no_ref_schema!();
fn schema_name() -> String {
format!("Array_Of_{}", T::schema_name())
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
items: Some(gen.subschema_for::<T>()?.into()),
..Default::default()
}.into())
}
}
};
}
seq_impl!(<T: Ord> JsonSchema for std::collections::BinaryHeap<T>);
seq_impl!(<T: Ord> JsonSchema for std::collections::BTreeSet<T>);
seq_impl!(<T: Eq + core::hash::Hash, H: core::hash::BuildHasher> JsonSchema for std::collections::HashSet<T, H>);
seq_impl!(<T> JsonSchema for std::collections::LinkedList<T>);
seq_impl!(<T> JsonSchema for Vec<T>);
seq_impl!(<T> JsonSchema for std::collections::VecDeque<T>);

View file

@ -0,0 +1,45 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::{JsonSchema, Result};
use serde_json::{Map, Number, Value};
use std::collections::BTreeMap;
impl JsonSchema for Value {
no_ref_schema!();
fn schema_name() -> String {
"Any_Value".to_owned()
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
Ok(gen.schema_for_any())
}
}
impl JsonSchema for Map<String, Value> {
no_ref_schema!();
fn schema_name() -> String {
BTreeMap::<String, Value>::schema_name()
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
BTreeMap::<String, Value>::json_schema(gen)
}
}
impl JsonSchema for Number {
no_ref_schema!();
fn schema_name() -> String {
"Number".to_owned()
}
fn json_schema(_: &mut SchemaGenerator) -> Result {
Ok(SchemaObject {
instance_type: Some(InstanceType::Number.into()),
..Default::default()
}
.into())
}
}

View file

@ -0,0 +1,52 @@
use crate::gen::SchemaGenerator;
use crate::schema::*;
use crate::{JsonSchema, Map, Result};
use serde_json::json;
macro_rules! tuple_impls {
($($len:expr => ($($name:ident)+))+) => {
$(
impl<$($name: JsonSchema),+> JsonSchema for ($($name,)+) {
no_ref_schema!();
fn schema_name() -> String {
["Tuple_Of".to_owned()$(, $name::schema_name())+].join("_And_")
}
fn json_schema(gen: &mut SchemaGenerator) -> Result {
let mut extensions = Map::new();
extensions.insert("minItems".to_owned(), json!($len));
extensions.insert("maxItems".to_owned(), json!($len));
let items = vec![
$(gen.subschema_for::<$name>()?),+
];
Ok(SchemaObject {
instance_type: Some(InstanceType::Array.into()),
items: Some(items.into()),
extensions,
..Default::default()
}.into())
}
}
)+
}
}
tuple_impls! {
1 => (T0)
2 => (T0 T1)
3 => (T0 T1 T2)
4 => (T0 T1 T2 T3)
5 => (T0 T1 T2 T3 T4)
6 => (T0 T1 T2 T3 T4 T5)
7 => (T0 T1 T2 T3 T4 T5 T6)
8 => (T0 T1 T2 T3 T4 T5 T6 T7)
9 => (T0 T1 T2 T3 T4 T5 T6 T7 T8)
10 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9)
11 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10)
12 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11)
13 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12)
14 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13)
15 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14)
16 => (T0 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15)
}

View file

@ -23,3 +23,26 @@ pub trait JsonSchema {
fn json_schema(gen: &mut gen::SchemaGenerator) -> Result; fn json_schema(gen: &mut gen::SchemaGenerator) -> Result;
} }
#[cfg(test)]
pub mod tests {
use super::*;
pub fn schema_for<T: JsonSchema>() -> schema::Schema {
match T::json_schema(&mut gen::SchemaGenerator::default()) {
Ok(s) => s,
Err(e) => panic!(
"Couldn't generate schema object for {}: {}",
T::schema_name(),
e
),
}
}
pub fn schema_object_for<T: JsonSchema>() -> schema::SchemaObject {
match schema_for::<T>() {
schema::Schema::Object(o) => o,
s => panic!("Schema for {} was not an object: {:?}", T::schema_name(), s),
}
}
}