diff --git a/schemars/rust-toolchain b/schemars/rust-toolchain deleted file mode 100644 index bf867e0..0000000 --- a/schemars/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 38c8e31..935c398 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -1,9 +1,7 @@ -use crate::make_schema::{MakeSchema, SchemaTypeId}; +use crate::make_schema::MakeSchema; use crate::schema::*; use crate::{MakeSchemaError, Result}; use std::collections::BTreeMap as Map; -use std::collections::BTreeSet as Set; -use std::iter::FromIterator; #[derive(Debug, PartialEq, Clone)] pub struct SchemaSettings { @@ -54,8 +52,7 @@ impl SchemaSettings { #[derive(Debug, Default, Clone)] pub struct SchemaGenerator { settings: SchemaSettings, - names: Set, - definitions: Map, + definitions: Map, } impl SchemaGenerator { @@ -94,52 +91,37 @@ impl SchemaGenerator { return T::make_schema(self); } - let type_id = T::schema_type_id(); - let name = self - .definitions - .get(&type_id) - .map(|(n, _)| Ok(n.clone())) - .unwrap_or_else(|| { - let name = self.make_unique_name::(); - self.insert_new_subschema_for::(type_id, name.clone())?; - Ok(name) - })?; + let name = T::schema_name(); + if !self.definitions.contains_key(&name) { + self.insert_new_subschema_for::(name.clone())?; + } let reference = format!("{}{}", self.settings().definitions_path, name); Ok(SchemaRef { reference }.into()) } - fn insert_new_subschema_for( - &mut self, - type_id: SchemaTypeId, - name: String, - ) -> Result { - self.names.insert(name.clone()); + fn insert_new_subschema_for(&mut self, name: String) -> Result<()> { let dummy = Schema::Bool(false); // insert into definitions BEFORE calling make_schema to avoid infinite recursion - self.definitions - .insert(type_id.clone(), (name.clone(), dummy)); + self.definitions.insert(name.clone(), dummy); match T::make_schema(self) { Ok(schema) => { - self.definitions - .entry(type_id) - .and_modify(|(_, s)| *s = schema); - Ok(name) + self.definitions.insert(name.clone(), schema); + Ok(()) } Err(e) => { - self.names.remove(&name); - self.definitions.remove(&type_id); + self.definitions.remove(&name); Err(e) } } } - pub fn definitions(&self) -> Map { - Map::from_iter(self.definitions.values().cloned()) + pub fn definitions(&self) -> &Map { + &self.definitions } pub fn into_definitions(self) -> Map { - Map::from_iter(self.definitions.into_iter().map(|(_, v)| v)) + self.definitions } pub fn root_schema_for(&mut self) -> Result { @@ -148,7 +130,7 @@ impl SchemaGenerator { Schema::Object(mut o) => { o.schema = Some("http://json-schema.org/draft-07/schema#".to_owned()); o.title = Some(T::schema_name()); - o.definitions.extend(self.definitions()); + o.definitions.extend(self.definitions().clone()); Schema::Object(o) } schema => schema, @@ -187,34 +169,25 @@ impl SchemaGenerator { Schema::Ref(r.clone()), ) })?; - // FIXME this is pretty inefficient - schema = self - .definitions - .values() - .filter(|(n, _)| n == name) - .map(|(_, s)| s) - .next() - .ok_or_else(|| { - MakeSchemaError::new( - "Could not find referenced schema.", - Schema::Ref(r.clone()), - ) - })?; - } - } - } - } - fn make_unique_name(&mut self) -> String { - let base_name = T::schema_name(); - if self.names.contains(&base_name) { - for i in 2.. { - let name = format!("{}{}", base_name, i); - if !self.names.contains(&name) { - return name; + schema = self.definitions.get(name).ok_or_else(|| { + MakeSchemaError::new( + "Could not find referenced schema.", + Schema::Ref(r.clone()), + ) + })?; + + match schema { + Schema::Ref(r2) if r2 == r => { + return Err(MakeSchemaError::new( + "Schema is referencing itself.", + schema.clone(), + )); + } + _ => {} + } } } } - base_name } } diff --git a/schemars/src/make_schema.rs b/schemars/src/make_schema.rs index f6564a5..1c2900d 100644 --- a/schemars/src/make_schema.rs +++ b/schemars/src/make_schema.rs @@ -4,36 +4,14 @@ use crate::Result; use serde_json::json; use std::collections::BTreeMap as Map; -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] -pub struct SchemaTypeId(&'static str); - pub trait MakeSchema { - fn schema_type_id() -> SchemaTypeId { - // FIXME schema name might not be unique! - SchemaTypeId(core::any::type_name::()) - } - - fn schema_name() -> String { - // TODO this requires nightly - // It's probably worth removing the default implemenation, - // then make every impl in this file set an explicit name - // Or maybe hide it under feature flag? - core::any::type_name::().replace(|c: char| !c.is_ascii_alphanumeric(), "_") - } - fn is_referenceable() -> bool { true } - fn make_schema(gen: &mut SchemaGenerator) -> Result; -} + fn schema_name() -> String; -macro_rules! no_ref_schema { - () => { - fn is_referenceable() -> bool { - false - } - }; + fn make_schema(gen: &mut SchemaGenerator) -> Result; } // TODO any other serde/json types other than serde_json value? @@ -46,13 +24,25 @@ macro_rules! no_ref_schema { // 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:tt) => { + ($type:tt => $instance_type:ident) => { impl MakeSchema for $type { no_ref_schema!(); + fn schema_name() -> String { + stringify!($instance_type).to_owned() + } + fn make_schema(_: &mut SchemaGenerator) -> Result { Ok(SchemaObject { instance_type: Some(InstanceType::$instance_type.into()), @@ -86,6 +76,10 @@ simple_impl!(() => Null); impl MakeSchema for char { no_ref_schema!(); + fn schema_name() -> String { + "Character".to_owned() + } + fn make_schema(_: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("minLength".to_owned(), json!(1)); @@ -105,6 +99,10 @@ impl MakeSchema for char { impl MakeSchema for [T; 0] { no_ref_schema!(); + fn schema_name() -> String { + "Empty_Array".to_owned() + } + fn make_schema(_: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("maxItems".to_owned(), json!(0)); @@ -123,6 +121,10 @@ macro_rules! array_impls { impl MakeSchema for [T; $len] { no_ref_schema!(); + fn schema_name() -> String { + format!("Array_Size_{}_Of_{}", $len, T::schema_name()) + } + fn make_schema(gen: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("minItems".to_owned(), json!($len)); @@ -154,6 +156,10 @@ macro_rules! tuple_impls { impl<$($name: MakeSchema),+> MakeSchema for ($($name,)+) { no_ref_schema!(); + fn schema_name() -> String { + ["Tuple_Of".to_owned()$(, $name::schema_name())+].join("_And_") + } + fn make_schema(gen: &mut SchemaGenerator) -> Result { let mut extensions = Map::new(); extensions.insert("minItems".to_owned(), json!($len)); @@ -202,6 +208,10 @@ macro_rules! seq_impl { { no_ref_schema!(); + fn schema_name() -> String { + format!("Array_Of_{}", T::schema_name()) + } + fn make_schema(gen: &mut SchemaGenerator) -> Result { Ok(SchemaObject { instance_type: Some(InstanceType::Array.into()), @@ -231,6 +241,10 @@ macro_rules! map_impl { { no_ref_schema!(); + fn schema_name() -> String { + format!("Map_Of_{}", V::schema_name()) + } + fn make_schema(gen: &mut SchemaGenerator) -> Result { let subschema = gen.subschema_for::()?; let make_schema_bool = gen.settings().bool_schemas == BoolSchemas::AdditionalPropertiesOnly @@ -260,13 +274,15 @@ map_impl!( MakeSchema f ////////// OPTION ////////// impl MakeSchema for Option { - fn is_referenceable() -> bool { - // TODO only really needs to be referenceable with option_nullable enabled. - // TODO what if T is Box and U is referenceable? - T::is_referenceable() + no_ref_schema!(); + + fn schema_name() -> String { + format!("Nullable_{}", T::schema_name()) } fn make_schema(gen: &mut SchemaGenerator) -> Result { + // FIXME this may produce a subschema that is not referenced in the final schema, + // e.g. SingleOrVec_For_InstanceType in schema-openapi3.json let mut schema = gen.subschema_for::()?; if gen.settings().option_add_null_type { schema = match schema { @@ -296,10 +312,16 @@ macro_rules! deref_impl { where T: MakeSchema, { - no_ref_schema!(); + fn is_referenceable() -> bool { + T::is_referenceable() + } + + fn schema_name() -> String { + T::schema_name() + } fn make_schema(gen: &mut SchemaGenerator) -> Result { - gen.subschema_for::() + T::make_schema(gen) } } }; @@ -317,6 +339,10 @@ deref_impl!(<'a, T: ToOwned> MakeSchema for std::borrow::Cow<'a, T>); impl MakeSchema for serde_json::Value { no_ref_schema!(); + fn schema_name() -> String { + "Any_Value".to_owned() + } + fn make_schema(gen: &mut SchemaGenerator) -> Result { Ok(gen.schema_for_any()) } diff --git a/schemars/tests/schema-openapi3.json b/schemars/tests/schema-openapi3.json index dac35e0..a2fdaba 100644 --- a/schemars/tests/schema-openapi3.json +++ b/schemars/tests/schema-openapi3.json @@ -1,47 +1,19 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "schemars__schema__Schema", + "title": "Schema", "anyOf": [ { "type": "boolean" }, { - "$ref": "#/components/schemas/schemars__schema__SchemaRef" + "$ref": "#/components/schemas/SchemaRef" }, { - "$ref": "#/components/schemas/schemars__schema__SchemaObject" + "$ref": "#/components/schemas/SchemaObject" } ], "definitions": { - "core__option__Option_schemars__schema__SingleOrVec_schemars__schema__InstanceType__": { - "anyOf": [ - { - "$ref": "#/components/schemas/schemars__schema__InstanceType" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/schemars__schema__InstanceType" - } - } - ], - "nullable": true - }, - "core__option__Option_schemars__schema__SingleOrVec_schemars__schema__Schema__": { - "anyOf": [ - { - "$ref": "#/components/schemas/schemars__schema__Schema" - }, - { - "type": "array", - "items": { - "$ref": "#/components/schemas/schemars__schema__Schema" - } - } - ], - "nullable": true - }, - "schemars__schema__InstanceType": { + "InstanceType": { "enum": [ "null", "boolean", @@ -52,20 +24,20 @@ "integer" ] }, - "schemars__schema__Schema": { + "Schema": { "anyOf": [ { "type": "boolean" }, { - "$ref": "#/components/schemas/schemars__schema__SchemaRef" + "$ref": "#/components/schemas/SchemaRef" }, { - "$ref": "#/components/schemas/schemars__schema__SchemaObject" + "$ref": "#/components/schemas/SchemaObject" } ] }, - "schemars__schema__SchemaObject": { + "SchemaObject": { "properties": { "$id": { "type": "string", @@ -78,21 +50,21 @@ "allOf": { "type": "array", "items": { - "$ref": "#/components/schemas/schemars__schema__Schema" + "$ref": "#/components/schemas/Schema" }, "nullable": true }, "anyOf": { "type": "array", "items": { - "$ref": "#/components/schemas/schemars__schema__Schema" + "$ref": "#/components/schemas/Schema" }, "nullable": true }, "definitions": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/schemars__schema__Schema" + "$ref": "#/components/schemas/Schema" } }, "description": { @@ -109,7 +81,18 @@ "additionalProperties": true }, "items": { - "$ref": "#/components/schemas/core__option__Option_schemars__schema__SingleOrVec_schemars__schema__Schema__" + "anyOf": [ + { + "$ref": "#/components/schemas/Schema" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/Schema" + } + } + ], + "nullable": true }, "not": { "anyOf": [ @@ -117,10 +100,10 @@ "type": "boolean" }, { - "$ref": "#/components/schemas/schemars__schema__SchemaRef" + "$ref": "#/components/schemas/SchemaRef" }, { - "$ref": "#/components/schemas/schemars__schema__SchemaObject" + "$ref": "#/components/schemas/SchemaObject" } ], "nullable": true @@ -128,14 +111,14 @@ "oneOf": { "type": "array", "items": { - "$ref": "#/components/schemas/schemars__schema__Schema" + "$ref": "#/components/schemas/Schema" }, "nullable": true }, "properties": { "type": "object", "additionalProperties": { - "$ref": "#/components/schemas/schemars__schema__Schema" + "$ref": "#/components/schemas/Schema" } }, "required": { @@ -150,39 +133,50 @@ "nullable": true }, "type": { - "$ref": "#/components/schemas/core__option__Option_schemars__schema__SingleOrVec_schemars__schema__InstanceType__" + "anyOf": [ + { + "$ref": "#/components/schemas/InstanceType" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/InstanceType" + } + } + ], + "nullable": true } } }, - "schemars__schema__SchemaRef": { + "SchemaRef": { "properties": { "$ref": { "type": "string" } } }, - "schemars__schema__SingleOrVec_schemars__schema__InstanceType_": { + "SingleOrVec_For_InstanceType": { "anyOf": [ { - "$ref": "#/components/schemas/schemars__schema__InstanceType" + "$ref": "#/components/schemas/InstanceType" }, { "type": "array", "items": { - "$ref": "#/components/schemas/schemars__schema__InstanceType" + "$ref": "#/components/schemas/InstanceType" } } ] }, - "schemars__schema__SingleOrVec_schemars__schema__Schema_": { + "SingleOrVec_For_Schema": { "anyOf": [ { - "$ref": "#/components/schemas/schemars__schema__Schema" + "$ref": "#/components/schemas/Schema" }, { "type": "array", "items": { - "$ref": "#/components/schemas/schemars__schema__Schema" + "$ref": "#/components/schemas/Schema" } } ] diff --git a/schemars/tests/schema.json b/schemars/tests/schema.json index 111d775..4df8ab3 100644 --- a/schemars/tests/schema.json +++ b/schemars/tests/schema.json @@ -1,39 +1,19 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "schemars__schema__Schema", + "title": "Schema", "anyOf": [ { "type": "boolean" }, { - "$ref": "#/definitions/schemars__schema__SchemaRef" + "$ref": "#/definitions/SchemaRef" }, { - "$ref": "#/definitions/schemars__schema__SchemaObject" + "$ref": "#/definitions/SchemaObject" } ], "definitions": { - "core__option__Option_schemars__schema__SingleOrVec_schemars__schema__InstanceType__": { - "anyOf": [ - { - "$ref": "#/definitions/schemars__schema__SingleOrVec_schemars__schema__InstanceType_" - }, - { - "type": "null" - } - ] - }, - "core__option__Option_schemars__schema__SingleOrVec_schemars__schema__Schema__": { - "anyOf": [ - { - "$ref": "#/definitions/schemars__schema__SingleOrVec_schemars__schema__Schema_" - }, - { - "type": "null" - } - ] - }, - "schemars__schema__InstanceType": { + "InstanceType": { "enum": [ "null", "boolean", @@ -44,20 +24,20 @@ "integer" ] }, - "schemars__schema__Schema": { + "Schema": { "anyOf": [ { "type": "boolean" }, { - "$ref": "#/definitions/schemars__schema__SchemaRef" + "$ref": "#/definitions/SchemaRef" }, { - "$ref": "#/definitions/schemars__schema__SchemaObject" + "$ref": "#/definitions/SchemaObject" } ] }, - "schemars__schema__SchemaObject": { + "SchemaObject": { "properties": { "$id": { "anyOf": [ @@ -84,7 +64,7 @@ { "type": "array", "items": { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" } }, { @@ -97,7 +77,7 @@ { "type": "array", "items": { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" } }, { @@ -108,7 +88,7 @@ "definitions": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" } }, "description": { @@ -137,12 +117,19 @@ "additionalProperties": true }, "items": { - "$ref": "#/definitions/core__option__Option_schemars__schema__SingleOrVec_schemars__schema__Schema__" + "anyOf": [ + { + "$ref": "#/definitions/SingleOrVec_For_Schema" + }, + { + "type": "null" + } + ] }, "not": { "anyOf": [ { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" }, { "type": "null" @@ -154,7 +141,7 @@ { "type": "array", "items": { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" } }, { @@ -165,7 +152,7 @@ "properties": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" } }, "required": { @@ -192,39 +179,46 @@ ] }, "type": { - "$ref": "#/definitions/core__option__Option_schemars__schema__SingleOrVec_schemars__schema__InstanceType__" + "anyOf": [ + { + "$ref": "#/definitions/SingleOrVec_For_InstanceType" + }, + { + "type": "null" + } + ] } } }, - "schemars__schema__SchemaRef": { + "SchemaRef": { "properties": { "$ref": { "type": "string" } } }, - "schemars__schema__SingleOrVec_schemars__schema__InstanceType_": { + "SingleOrVec_For_InstanceType": { "anyOf": [ { - "$ref": "#/definitions/schemars__schema__InstanceType" + "$ref": "#/definitions/InstanceType" }, { "type": "array", "items": { - "$ref": "#/definitions/schemars__schema__InstanceType" + "$ref": "#/definitions/InstanceType" } } ] }, - "schemars__schema__SingleOrVec_schemars__schema__Schema_": { + "SingleOrVec_For_Schema": { "anyOf": [ { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" }, { "type": "array", "items": { - "$ref": "#/definitions/schemars__schema__Schema" + "$ref": "#/definitions/Schema" } } ] diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index 273c732..235d8ef 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -23,18 +23,29 @@ pub fn derive_make_schema(input: proc_macro::TokenStream) -> proc_macro::TokenSt return compile_error(input.span(), e).into(); } - let name = cont.ident; - let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl(); - let schema = match cont.data { Data::Struct(Style::Struct, ref fields) => schema_for_struct(fields), Data::Enum(ref variants) => schema_for_enum(variants, &cont.attrs), _ => unimplemented!("work in progress!"), }; + let name = cont.ident; + let type_params: Vec<_> = cont.generics.type_params().map(|ty| &ty.ident).collect(); + let type_param_fmt = match type_params.len() { + 0 => "{}".to_owned(), + 1 => "{}_For_{}".to_owned(), + n => format!("{{}}_For_{{}}_And{}", "_{}".repeat(n - 1)), + }; + + let (impl_generics, ty_generics, where_clause) = cont.generics.split_for_impl(); + let impl_block = quote! { #[automatically_derived] impl #impl_generics schemars::MakeSchema for #name #ty_generics #where_clause { + fn schema_name() -> String { + format!(#type_param_fmt, stringify!(#name) #(,#type_params::schema_name())*) + } + fn make_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::Result { #schema }