diff --git a/schemars/rust-toolchain b/schemars/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/schemars/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/schemars/src/generator.rs b/schemars/src/generator.rs new file mode 100644 index 0000000..6d6e183 --- /dev/null +++ b/schemars/src/generator.rs @@ -0,0 +1,69 @@ +use crate::make_schema::MakeSchema; +use crate::schema::*; +use core::any::{type_name, TypeId}; +use std::collections::HashMap as Map; +use std::collections::HashSet as Set; + +#[derive(Debug, Default)] +pub struct SchemaGenerator { + names: Set, + definitions: Map, +} + +impl SchemaGenerator { + pub fn new() -> SchemaGenerator { + SchemaGenerator { + ..Default::default() + } + } + + pub fn subschema_for(&mut self) -> Schema { + let type_id = TypeId::of::(); + // TODO is there a nicer way to do this? + if !self.definitions.contains_key(&type_id) { + let name = self.make_unique_name::(); + self.names.insert(name.clone()); + // insert into definitions BEFORE calling make_schema to avoid infinite recursion + let dummy = Schema::Bool(false); + self.definitions.insert(type_id, (name.clone(), dummy)); + + let schema = T::make_schema(self); + self.definitions + .entry(type_id) + .and_modify(|(_, s)| *s = schema); + } + let ref name = self.definitions.get(&type_id).unwrap().0; + SchemaRef { + reference: format!("#/definitions/{}", name), + } + .into() + } + + pub fn root_schema_for(&mut self) -> Schema { + let schema = T::make_schema(self); + if let Schema::Object(mut o) = schema { + o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); + o.title = Some(Self::schema_name::()); + for (_, (name, schema)) in self.definitions.iter() { + o.definitions.insert(name.clone(), schema.clone()); + } + return Schema::Object(o); + } + schema + } + + fn schema_name() -> String { + let override_name = T::override_schema_name(); + if override_name.is_empty() { + type_name::().to_owned() + } else { + override_name + } + } + + fn make_unique_name(&mut self) -> String { + Self::schema_name::() + // TODO remove namespace, remove special chars + // TODO enforce uniqueness + } +} diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index 3c330b4..b095a42 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -1,5 +1,6 @@ pub mod make_schema; pub mod schema; +pub mod generator; #[cfg(test)] mod tests { diff --git a/schemars/src/main.rs b/schemars/src/main.rs index a6c024e..f88bcbf 100644 --- a/schemars/src/main.rs +++ b/schemars/src/main.rs @@ -1,9 +1,12 @@ -mod make_schema; -mod schema; +pub mod generator; +pub mod make_schema; +pub mod schema; use make_schema::MakeSchema; +use schema::*; use serde::{Deserialize, Serialize}; use serde_json::Result; +use std::collections::BTreeMap as Map; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] diff --git a/schemars/src/make_schema.rs b/schemars/src/make_schema.rs index 3f07def..091045b 100644 --- a/schemars/src/make_schema.rs +++ b/schemars/src/make_schema.rs @@ -1,9 +1,18 @@ +use crate::generator::SchemaGenerator; use crate::schema::*; use serde_json::json; use std::collections::BTreeMap as Map; pub trait MakeSchema { - fn make_schema() -> Schema; + fn override_schema_name() -> String { + String::new() + } + + fn makes_ref_schema() -> bool { + false + } + + fn make_schema(generator: &mut SchemaGenerator) -> Schema; } // TODO structs, enums, tuples @@ -15,16 +24,15 @@ pub trait MakeSchema { // Path, PathBuf, OsStr, OsString, Wrapping, Reverse, AtomicBool, AtomixI8 etc., // NonZeroU8 etc., ArcWeak, RcWeak, BTreeMap, HashMap, (!)?, Bound?, Range?, RangeInclusive?, // PhantomData?, CString?, CStr?, fmt::Arguments? -// !map keys must be Into! -////////// PRIMITIVES (except ints) ////////// +////////// PRIMITIVES ////////// macro_rules! simple_impl { - ($type:tt => $instance_type:expr) => { + ($type:tt => $instance_type:tt) => { impl MakeSchema for $type { - fn make_schema() -> Schema { + fn make_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { - instance_type: Some($instance_type.into()), + instance_type: Some(InstanceType::$instance_type.into()), ..Default::default() } .into() @@ -33,15 +41,27 @@ macro_rules! simple_impl { }; } -simple_impl!(str => InstanceType::String); -simple_impl!(String => InstanceType::String); -simple_impl!(bool => InstanceType::Boolean); -simple_impl!(f32 => InstanceType::Number); -simple_impl!(f64 => InstanceType::Number); -simple_impl!(() => InstanceType::Null); +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 MakeSchema for char { - fn make_schema() -> Schema { + fn make_schema(_: &mut SchemaGenerator) -> Schema { let mut extra_properties = Map::new(); extra_properties.insert("minLength".to_owned(), json!(1)); extra_properties.insert("maxLength".to_owned(), json!(1)); @@ -54,45 +74,11 @@ impl MakeSchema for char { } } -////////// INTS ////////// - -macro_rules! int_impl { - ($type:ident) => { - impl MakeSchema for $type { - fn make_schema() -> Schema { - let mut extra_properties = Map::new(); - // this may be overkill... - extra_properties.insert("minimum".to_owned(), json!($type::min_value())); - extra_properties.insert("maximum".to_owned(), json!($type::max_value())); - SchemaObject { - instance_type: Some(InstanceType::Integer.into()), - extra_properties, - ..Default::default() - } - .into() - } - } - }; -} - -int_impl!(i8); -int_impl!(i16); -int_impl!(i32); -int_impl!(i64); -int_impl!(i128); -int_impl!(isize); -int_impl!(u8); -int_impl!(u16); -int_impl!(u32); -int_impl!(u64); -int_impl!(u128); -int_impl!(usize); - ////////// ARRAYS ////////// // Does not require T: MakeSchema. impl MakeSchema for [T; 0] { - fn make_schema() -> Schema { + fn make_schema(_: &mut SchemaGenerator) -> Schema { let mut extra_properties = Map::new(); extra_properties.insert("maxItems".to_owned(), json!(0)); SchemaObject { @@ -107,15 +93,15 @@ impl MakeSchema for [T; 0] { macro_rules! array_impls { ($($len:tt)+) => { $( - impl MakeSchema for [T; $len] + impl MakeSchema for [T; $len] { - fn make_schema() -> Schema { + fn make_schema(gen: &mut SchemaGenerator) -> Schema { let mut extra_properties = Map::new(); extra_properties.insert("minItems".to_owned(), json!($len)); extra_properties.insert("maxItems".to_owned(), json!($len)); SchemaObject { instance_type: Some(InstanceType::Array.into()), - items: Some(Box::from(T::make_schema())), + items: Some(Box::from(gen.subschema_for::())), extra_properties, ..Default::default() }.into() @@ -138,13 +124,13 @@ macro_rules! seq_impl { ($($desc:tt)+) => { impl $($desc)+ where - T: MakeSchema, + T: MakeSchema + 'static, { - fn make_schema() -> Schema + fn make_schema(gen: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Array.into()), - items: Some(Box::from(T::make_schema())), + items: Some(Box::from(gen.subschema_for::())), ..Default::default() }.into() } @@ -166,14 +152,14 @@ macro_rules! map_impl { impl $($desc)+ where K: Into, - T: MakeSchema, + T: MakeSchema + 'static, { - fn make_schema() -> Schema + fn make_schema(gen: &mut SchemaGenerator) -> Schema { let mut extra_properties = Map::new(); extra_properties.insert( "additionalProperties".to_owned(), - json!(T::make_schema()) + json!(gen.subschema_for::()) ); SchemaObject { instance_type: Some(InstanceType::Object.into()), @@ -190,13 +176,13 @@ map_impl!( MakeSchema f ////////// OPTION ////////// -impl MakeSchema for Option { - fn make_schema() -> Schema { - match T::make_schema() { +impl MakeSchema for Option { + fn make_schema(gen: &mut SchemaGenerator) -> Schema { + match gen.subschema_for::() { Schema::Bool(true) => true.into(), - Schema::Bool(false) => <()>::make_schema(), - Schema::Object(schema) => SchemaObject { - any_of: Some(vec![schema.into(), <()>::make_schema()]), + Schema::Bool(false) => <()>::make_schema(gen), + schema => SchemaObject { + any_of: Some(vec![schema, <()>::make_schema(gen)]), ..Default::default() } .into(), @@ -210,10 +196,10 @@ macro_rules! deref_impl { ($($desc:tt)+) => { impl $($desc)+ where - T: ?Sized + MakeSchema, + T: MakeSchema + 'static, { - fn make_schema() -> Schema { - T::make_schema() + fn make_schema(gen: &mut SchemaGenerator) -> Schema { + gen.subschema_for::() } } }; @@ -229,7 +215,7 @@ deref_impl!(<'a, T: ToOwned> MakeSchema for std::borrow::Cow<'a, T>); ////////// SERDE_JSON ////////// impl MakeSchema for serde_json::Value { - fn make_schema() -> Schema { + fn make_schema(_: &mut SchemaGenerator) -> Schema { true.into() } } diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index b8f97ff..68ec599 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -2,10 +2,11 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap as Map; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum Schema { Bool(bool), + Ref(SchemaRef), Object(SchemaObject), } @@ -21,7 +22,19 @@ impl From for Schema { } } -#[derive(Serialize, Deserialize, Debug, Default)] +impl From for Schema { + fn from(r: SchemaRef) -> Self { + Schema::Ref(r) + } +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] +pub struct SchemaRef { + #[serde(rename = "$ref")] + pub reference: String, +} + +#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[serde(rename_all = "camelCase")] pub struct SchemaObject { #[serde(rename = "$schema", skip_serializing_if = "Option::is_none")] @@ -56,7 +69,7 @@ pub struct SchemaObject { pub extra_properties: Map, } -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub enum InstanceType { Null, @@ -68,7 +81,7 @@ pub enum InstanceType { Integer, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum SingleOrVec { Single(T), @@ -98,15 +111,3 @@ impl Into> for SingleOrVec { } } } - -/*pub struct SchemaObject { - pub ref_path: Option, - pub description: Option, - pub schema_type: Option, - pub format: Option, - pub enum_values: Option>, - pub required: Option>, - pub items: Option>, - pub properties: Option>, -} -*/