diff --git a/.vscode/settings.json b/.vscode/settings.json index a833602..b2bfead 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "rust-analyzer.check.command": "clippy" + "rust-analyzer.check.command": "clippy", + "rust-analyzer.showUnlinkedFileNotification": false } \ No newline at end of file diff --git a/schemars/src/gen.rs b/schemars/src/gen.rs index 20c8dc0..a31839a 100644 --- a/schemars/src/gen.rs +++ b/schemars/src/gen.rs @@ -11,6 +11,8 @@ use crate::schema::*; use crate::{visit::*, JsonSchema, Map}; use dyn_clone::DynClone; use serde::Serialize; +use std::borrow::Cow; +use std::collections::HashMap; use std::{any::Any, collections::HashSet, fmt::Debug}; /// Settings to customize how Schemas are generated. @@ -149,7 +151,9 @@ impl SchemaSettings { pub struct SchemaGenerator { settings: SchemaSettings, definitions: Map, - pending_schema_names: HashSet, + pending_schema_ids: HashSet>, + schema_id_to_name: HashMap, String>, + used_schema_names: HashSet, } impl Clone for SchemaGenerator { @@ -157,7 +161,9 @@ impl Clone for SchemaGenerator { Self { settings: self.settings.clone(), definitions: self.definitions.clone(), - pending_schema_names: HashSet::new(), + pending_schema_ids: HashSet::new(), + schema_id_to_name: HashMap::new(), + used_schema_names: HashSet::new(), } } } @@ -213,27 +219,54 @@ impl SchemaGenerator { /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will /// add them to the `SchemaGenerator`'s schema definitions. pub fn subschema_for(&mut self) -> Schema { - let name = T::schema_name(); + let id = T::schema_id(); let return_ref = T::is_referenceable() - && (!self.settings.inline_subschemas || self.pending_schema_names.contains(&name)); + && (!self.settings.inline_subschemas || self.pending_schema_ids.contains(&id)); if return_ref { - let reference = format!("{}{}", self.settings().definitions_path, name); + let name = match self.schema_id_to_name.get(&id).cloned() { + Some(n) => n, + None => { + let base_name = T::schema_name(); + let mut name = String::new(); + + if self.used_schema_names.contains(&base_name) { + for i in 2.. { + name = format!("{}{}", base_name, i); + if !self.used_schema_names.contains(&name) { + break; + } + } + } else { + name = base_name; + } + + self.used_schema_names.insert(name.clone()); + self.schema_id_to_name.insert(id.clone(), name.clone()); + name + } + }; + + let reference = format!("{}{}", self.settings.definitions_path, name); if !self.definitions.contains_key(&name) { - self.insert_new_subschema_for::(name); + self.insert_new_subschema_for::(name, id); } Schema::new_ref(reference) } else { - self.json_schema_internal::(&name) + self.json_schema_internal::(id) } } - fn insert_new_subschema_for(&mut self, name: String) { + fn insert_new_subschema_for( + &mut self, + name: String, + id: Cow<'static, str>, + ) { let dummy = Schema::Bool(false); // insert into definitions BEFORE calling json_schema to avoid infinite recursion self.definitions.insert(name.clone(), dummy); - let schema = self.json_schema_internal::(&name); + let schema = self.json_schema_internal::(id); self.definitions.insert(name, schema); } @@ -274,9 +307,8 @@ impl SchemaGenerator { /// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s /// [`definitions`](../schema/struct.Metadata.html#structfield.definitions) pub fn root_schema_for(&mut self) -> RootSchema { - let name = T::schema_name(); - let mut schema = self.json_schema_internal::(&name).into_object(); - schema.metadata().title.get_or_insert(name); + let mut schema = self.json_schema_internal::(T::schema_id()).into_object(); + schema.metadata().title.get_or_insert_with(T::schema_name); let mut root = RootSchema { meta_schema: self.settings.meta_schema.clone(), definitions: self.definitions.clone(), @@ -295,9 +327,8 @@ impl SchemaGenerator { /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will /// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions) pub fn into_root_schema_for(mut self) -> RootSchema { - let name = T::schema_name(); - let mut schema = self.json_schema_internal::(&name).into_object(); - schema.metadata().title.get_or_insert(name); + let mut schema = self.json_schema_internal::(T::schema_id()).into_object(); + schema.metadata().title.get_or_insert_with(T::schema_name); let mut root = RootSchema { meta_schema: self.settings.meta_schema, definitions: self.definitions, @@ -418,29 +449,29 @@ impl SchemaGenerator { } } - fn json_schema_internal(&mut self, name: &str) -> Schema { + fn json_schema_internal(&mut self, id: Cow<'static, str>) -> Schema { struct PendingSchemaState<'a> { gen: &'a mut SchemaGenerator, - name: &'a str, + id: Cow<'static, str>, did_add: bool, } impl<'a> PendingSchemaState<'a> { - fn new(gen: &'a mut SchemaGenerator, name: &'a str) -> Self { - let did_add = gen.pending_schema_names.insert(name.to_owned()); - Self { gen, name, did_add } + fn new(gen: &'a mut SchemaGenerator, id: Cow<'static, str>) -> Self { + let did_add = gen.pending_schema_ids.insert(id.clone()); + Self { gen, id, did_add } } } impl Drop for PendingSchemaState<'_> { fn drop(&mut self) { if self.did_add { - self.gen.pending_schema_names.remove(self.name); + self.gen.pending_schema_ids.remove(&self.id); } } } - let pss = PendingSchemaState::new(self, name); + let pss = PendingSchemaState::new(self, id); T::json_schema(pss.gen) } } diff --git a/schemars/src/json_schema_impls/array.rs b/schemars/src/json_schema_impls/array.rs index 558ddbd..036d042 100644 --- a/schemars/src/json_schema_impls/array.rs +++ b/schemars/src/json_schema_impls/array.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; // Does not require T: JsonSchema. impl JsonSchema for [T; 0] { @@ -10,6 +11,10 @@ impl JsonSchema for [T; 0] { "EmptyArray".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("[]") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Array.into()), @@ -33,6 +38,11 @@ macro_rules! array_impls { format!("Array_size_{}_of_{}", $len, T::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned( + format!("[{}; {}]", $len, T::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Array.into()), diff --git a/schemars/src/json_schema_impls/chrono.rs b/schemars/src/json_schema_impls/chrono.rs index 005c89b..3a1731b 100644 --- a/schemars/src/json_schema_impls/chrono.rs +++ b/schemars/src/json_schema_impls/chrono.rs @@ -3,6 +3,7 @@ use crate::schema::*; use crate::JsonSchema; use chrono::prelude::*; use serde_json::json; +use std::borrow::Cow; impl JsonSchema for Weekday { no_ref_schema!(); @@ -11,6 +12,10 @@ impl JsonSchema for Weekday { "Weekday".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("chrono::Weekday") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), @@ -41,6 +46,10 @@ macro_rules! formatted_string_impl { stringify!($ty).to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed(stringify!(chrono::$ty)) + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), diff --git a/schemars/src/json_schema_impls/core.rs b/schemars/src/json_schema_impls/core.rs index 10f9f0f..955ead6 100644 --- a/schemars/src/json_schema_impls/core.rs +++ b/schemars/src/json_schema_impls/core.rs @@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; use serde_json::json; +use std::borrow::Cow; use std::ops::{Bound, Range, RangeInclusive}; impl JsonSchema for Option { @@ -11,6 +12,10 @@ impl JsonSchema for Option { format!("Nullable_{}", T::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned(format!("Option<{}>", T::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut schema = gen.subschema_for::(); if gen.settings().option_add_null_type { @@ -69,6 +74,10 @@ impl JsonSchema for Result { format!("Result_of_{}_or_{}", T::schema_name(), E::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned(format!("Result<{}, {}>", T::schema_id(), E::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut ok_schema = SchemaObject { instance_type: Some(InstanceType::Object.into()), @@ -99,6 +108,10 @@ impl JsonSchema for Bound { format!("Bound_of_{}", T::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned(format!("Bound<{}>", T::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut included_schema = SchemaObject { instance_type: Some(InstanceType::Object.into()), @@ -139,6 +152,10 @@ impl JsonSchema for Range { format!("Range_of_{}", T::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned(format!("Range<{}>", T::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut schema = SchemaObject { instance_type: Some(InstanceType::Object.into()), diff --git a/schemars/src/json_schema_impls/decimal.rs b/schemars/src/json_schema_impls/decimal.rs index 4f908a2..1a2806a 100644 --- a/schemars/src/json_schema_impls/decimal.rs +++ b/schemars/src/json_schema_impls/decimal.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; macro_rules! decimal_impl { ($type:ty) => { @@ -11,6 +12,10 @@ macro_rules! decimal_impl { "Decimal".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed($name) + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), diff --git a/schemars/src/json_schema_impls/either.rs b/schemars/src/json_schema_impls/either.rs index 1010bc4..957fdd1 100644 --- a/schemars/src/json_schema_impls/either.rs +++ b/schemars/src/json_schema_impls/either.rs @@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; use either::Either; +use std::borrow::Cow; impl JsonSchema for Either { no_ref_schema!(); @@ -10,6 +11,14 @@ impl JsonSchema for Either { format!("Either_{}_or_{}", L::schema_name(), R::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned(format!( + "either::Either<{}, {}>", + L::schema_id(), + R::schema_id() + )) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut schema = SchemaObject::default(); schema.subschemas().any_of = Some(vec![gen.subschema_for::(), gen.subschema_for::()]); diff --git a/schemars/src/json_schema_impls/ffi.rs b/schemars/src/json_schema_impls/ffi.rs index 5ce10d7..55b5012 100644 --- a/schemars/src/json_schema_impls/ffi.rs +++ b/schemars/src/json_schema_impls/ffi.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use std::ffi::{CStr, CString, OsStr, OsString}; impl JsonSchema for OsString { @@ -8,6 +9,10 @@ impl JsonSchema for OsString { "OsString".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("std::ffi::OsString") + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut unix_schema = SchemaObject { instance_type: Some(InstanceType::Object.into()), diff --git a/schemars/src/json_schema_impls/maps.rs b/schemars/src/json_schema_impls/maps.rs index 356464f..7c27808 100644 --- a/schemars/src/json_schema_impls/maps.rs +++ b/schemars/src/json_schema_impls/maps.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; macro_rules! map_impl { ($($desc:tt)+) => { @@ -14,6 +15,10 @@ macro_rules! map_impl { format!("Map_of_{}", V::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned(format!("Map<{}>", V::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let subschema = gen.subschema_for::(); SchemaObject { diff --git a/schemars/src/json_schema_impls/mod.rs b/schemars/src/json_schema_impls/mod.rs index 22d1c46..6066d9b 100644 --- a/schemars/src/json_schema_impls/mod.rs +++ b/schemars/src/json_schema_impls/mod.rs @@ -17,6 +17,10 @@ macro_rules! forward_impl { <$target>::schema_name() } + fn schema_id() -> std::borrow::Cow<'static, str> { + <$target>::schema_id() + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { <$target>::json_schema(gen) } diff --git a/schemars/src/json_schema_impls/nonzero_signed.rs b/schemars/src/json_schema_impls/nonzero_signed.rs index 00bb63f..c2fba2b 100644 --- a/schemars/src/json_schema_impls/nonzero_signed.rs +++ b/schemars/src/json_schema_impls/nonzero_signed.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use std::num::*; macro_rules! nonzero_unsigned_impl { @@ -12,6 +13,10 @@ macro_rules! nonzero_unsigned_impl { stringify!($type).to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed(stringify!(std::num::$type)) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let zero_schema: Schema = SchemaObject { const_value: Some(0.into()), diff --git a/schemars/src/json_schema_impls/nonzero_unsigned.rs b/schemars/src/json_schema_impls/nonzero_unsigned.rs index 284ac8a..1963d56 100644 --- a/schemars/src/json_schema_impls/nonzero_unsigned.rs +++ b/schemars/src/json_schema_impls/nonzero_unsigned.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use std::num::*; macro_rules! nonzero_unsigned_impl { @@ -12,6 +13,10 @@ macro_rules! nonzero_unsigned_impl { stringify!($type).to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed(stringify!(std::num::$type)) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut schema: SchemaObject = <$primitive>::json_schema(gen).into(); schema.number().minimum = Some(1.0); diff --git a/schemars/src/json_schema_impls/primitives.rs b/schemars/src/json_schema_impls/primitives.rs index acdcd7e..d182984 100644 --- a/schemars/src/json_schema_impls/primitives.rs +++ b/schemars/src/json_schema_impls/primitives.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::path::{Path, PathBuf}; @@ -19,6 +20,10 @@ macro_rules! simple_impl { $name.to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed($name) + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::$instance_type.into()), @@ -64,6 +69,10 @@ macro_rules! unsigned_impl { $format.to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed($format) + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { let mut schema = SchemaObject { instance_type: Some(InstanceType::$instance_type.into()), @@ -91,6 +100,10 @@ impl JsonSchema for char { "Character".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("char") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), diff --git a/schemars/src/json_schema_impls/semver.rs b/schemars/src/json_schema_impls/semver.rs index 37c1d24..dd8ed8e 100644 --- a/schemars/src/json_schema_impls/semver.rs +++ b/schemars/src/json_schema_impls/semver.rs @@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; use semver::Version; +use std::borrow::Cow; impl JsonSchema for Version { no_ref_schema!(); @@ -10,6 +11,10 @@ impl JsonSchema for Version { "Version".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("semver::Version") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), diff --git a/schemars/src/json_schema_impls/sequences.rs b/schemars/src/json_schema_impls/sequences.rs index 58bb8e7..780307f 100644 --- a/schemars/src/json_schema_impls/sequences.rs +++ b/schemars/src/json_schema_impls/sequences.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; macro_rules! seq_impl { ($($desc:tt)+) => { @@ -14,6 +15,11 @@ macro_rules! seq_impl { format!("Array_of_{}", T::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned( + format!("[{}]", T::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Array.into()), @@ -41,6 +47,11 @@ macro_rules! set_impl { format!("Set_of_{}", T::schema_name()) } + fn schema_id() -> Cow<'static, str> { + Cow::Owned( + format!("Set<{}>", T::schema_id())) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Array.into()), diff --git a/schemars/src/json_schema_impls/serdejson.rs b/schemars/src/json_schema_impls/serdejson.rs index 01a492c..41eafd5 100644 --- a/schemars/src/json_schema_impls/serdejson.rs +++ b/schemars/src/json_schema_impls/serdejson.rs @@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; use serde_json::{Map, Number, Value}; +use std::borrow::Cow; use std::collections::BTreeMap; impl JsonSchema for Value { @@ -11,6 +12,10 @@ impl JsonSchema for Value { "AnyValue".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("AnyValue") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { Schema::Bool(true) } @@ -25,6 +30,10 @@ impl JsonSchema for Number { "Number".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("Number") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::Number.into()), diff --git a/schemars/src/json_schema_impls/time.rs b/schemars/src/json_schema_impls/time.rs index f4362ee..767a1d2 100644 --- a/schemars/src/json_schema_impls/time.rs +++ b/schemars/src/json_schema_impls/time.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use std::time::{Duration, SystemTime}; impl JsonSchema for Duration { @@ -8,6 +9,10 @@ impl JsonSchema for Duration { "Duration".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("std::time::Duration") + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut schema = SchemaObject { instance_type: Some(InstanceType::Object.into()), @@ -29,6 +34,10 @@ impl JsonSchema for SystemTime { "SystemTime".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("std::time::SystemTime") + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let mut schema = SchemaObject { instance_type: Some(InstanceType::Object.into()), diff --git a/schemars/src/json_schema_impls/tuple.rs b/schemars/src/json_schema_impls/tuple.rs index 383f839..f67f06a 100644 --- a/schemars/src/json_schema_impls/tuple.rs +++ b/schemars/src/json_schema_impls/tuple.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; macro_rules! tuple_impls { ($($len:expr => ($($name:ident)+))+) => { @@ -14,6 +15,14 @@ macro_rules! tuple_impls { name } + fn schema_id() -> Cow<'static, str> { + let mut id = "(".to_owned(); + id.push_str(&[$($name::schema_id()),+].join(",")); + id.push(')'); + + Cow::Owned(id) + } + fn json_schema(gen: &mut SchemaGenerator) -> Schema { let items = vec![ $(gen.subschema_for::<$name>()),+ diff --git a/schemars/src/json_schema_impls/url.rs b/schemars/src/json_schema_impls/url.rs index ffc5426..be18612 100644 --- a/schemars/src/json_schema_impls/url.rs +++ b/schemars/src/json_schema_impls/url.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use url::Url; impl JsonSchema for Url { @@ -10,6 +11,10 @@ impl JsonSchema for Url { "Url".to_owned() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("url::Url") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), diff --git a/schemars/src/json_schema_impls/uuid08.rs b/schemars/src/json_schema_impls/uuid08.rs index 9e6dc58..b3b18f8 100644 --- a/schemars/src/json_schema_impls/uuid08.rs +++ b/schemars/src/json_schema_impls/uuid08.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use uuid08::Uuid; impl JsonSchema for Uuid { @@ -10,6 +11,10 @@ impl JsonSchema for Uuid { "Uuid".to_string() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("uuid::Uuid") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), diff --git a/schemars/src/json_schema_impls/uuid1.rs b/schemars/src/json_schema_impls/uuid1.rs index 85d6789..2e0c6e9 100644 --- a/schemars/src/json_schema_impls/uuid1.rs +++ b/schemars/src/json_schema_impls/uuid1.rs @@ -1,6 +1,7 @@ use crate::gen::SchemaGenerator; use crate::schema::*; use crate::JsonSchema; +use std::borrow::Cow; use uuid1::Uuid; impl JsonSchema for Uuid { @@ -10,6 +11,10 @@ impl JsonSchema for Uuid { "Uuid".to_string() } + fn schema_id() -> Cow<'static, str> { + Cow::Borrowed("uuid::Uuid") + } + fn json_schema(_: &mut SchemaGenerator) -> Schema { SchemaObject { instance_type: Some(InstanceType::String.into()), diff --git a/schemars/src/lib.rs b/schemars/src/lib.rs index 9905b01..f2332e5 100644 --- a/schemars/src/lib.rs +++ b/schemars/src/lib.rs @@ -41,6 +41,8 @@ pub mod visit; #[cfg(feature = "schemars_derive")] extern crate schemars_derive; +use std::borrow::Cow; + #[cfg(feature = "schemars_derive")] pub use schemars_derive::*; @@ -56,7 +58,8 @@ use schema::Schema; /// /// This can also be automatically derived on most custom types with `#[derive(JsonSchema)]`. /// -/// # Example +/// # Examples +/// Deriving an implementation: /// ``` /// use schemars::{schema_for, JsonSchema}; /// @@ -67,6 +70,64 @@ use schema::Schema; /// /// let my_schema = schema_for!(MyStruct); /// ``` +/// +/// When manually implementing `JsonSchema`, as well as determining an appropriate schema, +/// you will need to determine an appropriate name and ID for the type. +/// For non-generic types, the type name/path are suitable for this: +/// ``` +/// use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +/// use std::borrow::Cow; +/// +/// struct NonGenericType; +/// +/// impl JsonSchema for NonGenericType { +/// fn schema_name() -> String { +/// // Exclude the module path to make the name in generated schemas clearer. +/// "NonGenericType".to_owned() +/// } +/// +/// fn schema_id() -> Cow<'static, str> { +/// // Include the module, in case a type with the same name is in another module/crate +/// Cow::Borrowed(concat!(module_path!(), "::NonGenericType")) +/// } +/// +/// fn json_schema(_gen: &mut SchemaGenerator) -> Schema { +/// todo!() +/// } +/// } +/// +/// assert_eq!(NonGenericType::schema_id(), <&mut NonGenericType>::schema_id()); +/// ``` +/// +/// But generic type parameters which may affect the generated schema should typically be included in the name/ID: +/// ``` +/// use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}; +/// use std::{borrow::Cow, marker::PhantomData}; +/// +/// struct GenericType(PhantomData); +/// +/// impl JsonSchema for GenericType { +/// fn schema_name() -> String { +/// format!("GenericType_{}", T::schema_name()) +/// } +/// +/// fn schema_id() -> Cow<'static, str> { +/// Cow::Owned(format!( +/// "{}::GenericType<{}>", +/// module_path!(), +/// T::schema_id() +/// )) +/// } +/// +/// fn json_schema(_gen: &mut SchemaGenerator) -> Schema { +/// todo!() +/// } +/// } +/// +/// assert_eq!(>::schema_id(), <&mut GenericType<&i32>>::schema_id()); +/// ``` +/// + pub trait JsonSchema { /// Whether JSON Schemas generated for this type should be re-used where possible using the `$ref` keyword. /// @@ -83,6 +144,17 @@ pub trait JsonSchema { /// This is used as the title for root schemas, and the key within the root's `definitions` property for subschemas. fn schema_name() -> String; + /// Returns a string that uniquely identifies the schema produced by this type. + /// + /// This does not have to be a human-readable string, and the value will not itself be included in generated schemas. + /// If two types produce different schemas, then they **must** have different `schema_id()`s, + /// but two types that produce identical schemas should *ideally* have the same `schema_id()`. + /// + /// The default implementation returns the same value as `schema_name()`. + fn schema_id() -> Cow<'static, str> { + Cow::Owned(Self::schema_name()) + } + /// Generates a JSON Schema for this type. /// /// If the returned schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will diff --git a/schemars/tests/expected/same_name.json b/schemars/tests/expected/same_name.json new file mode 100644 index 0000000..9e4e6b3 --- /dev/null +++ b/schemars/tests/expected/same_name.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Config2", + "type": "object", + "required": [ + "a_cfg", + "b_cfg" + ], + "properties": { + "a_cfg": { + "$ref": "#/definitions/Config" + }, + "b_cfg": { + "$ref": "#/definitions/Config2" + } + }, + "definitions": { + "Config": { + "type": "object", + "required": [ + "test" + ], + "properties": { + "test": { + "type": "string" + } + } + }, + "Config2": { + "type": "object", + "required": [ + "test2" + ], + "properties": { + "test2": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/schemars/tests/same_name.rs b/schemars/tests/same_name.rs new file mode 100644 index 0000000..5e19611 --- /dev/null +++ b/schemars/tests/same_name.rs @@ -0,0 +1,35 @@ +mod util; +use schemars::JsonSchema; +use util::*; + +mod a { + use super::*; + + #[allow(dead_code)] + #[derive(JsonSchema)] + pub struct Config { + test: String, + } +} + +mod b { + use super::*; + + #[allow(dead_code)] + #[derive(JsonSchema)] + pub struct Config { + test2: String, + } +} + +#[allow(dead_code)] +#[derive(JsonSchema)] +pub struct Config2 { + a_cfg: a::Config, + b_cfg: b::Config, +} + +#[test] +fn same_name() -> TestResult { + test_default_generated_schema::("same_name") +} diff --git a/schemars_derive/src/lib.rs b/schemars_derive/src/lib.rs index e3b3a4f..3ad1031 100644 --- a/schemars_derive/src/lib.rs +++ b/schemars_derive/src/lib.rs @@ -67,6 +67,10 @@ fn derive_json_schema( <#ty as schemars::JsonSchema>::schema_name() } + fn schema_id() -> std::borrow::Cow<'static, str> { + <#ty as schemars::JsonSchema>::schema_id() + } + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { <#ty as schemars::JsonSchema>::json_schema(gen) } @@ -98,27 +102,66 @@ fn derive_json_schema( let const_params: Vec<_> = cont.generics.const_params().map(|c| &c.ident).collect(); let params: Vec<_> = type_params.iter().chain(const_params.iter()).collect(); - let schema_name = if params.is_empty() + let (schema_name, schema_id) = if params.is_empty() || (cont.attrs.is_renamed && !schema_base_name.contains('{')) { - quote! { - #schema_base_name.to_owned() - } + ( + quote! { + #schema_base_name.to_owned() + }, + quote! { + std::borrow::Cow::Borrowed(std::concat!( + std::module_path!(), + "::", + #schema_base_name + )) + }, + ) } else if cont.attrs.is_renamed { let mut schema_name_fmt = schema_base_name; for tp in ¶ms { schema_name_fmt.push_str(&format!("{{{}:.0}}", tp)); } - quote! { - format!(#schema_name_fmt #(,#type_params=#type_params::schema_name())* #(,#const_params=#const_params)*) - } + ( + quote! { + format!(#schema_name_fmt #(,#type_params=#type_params::schema_name())* #(,#const_params=#const_params)*) + }, + quote! { + std::borrow::Cow::Owned( + format!( + std::concat!( + std::module_path!(), + "::", + #schema_name_fmt + ) + #(,#type_params=#type_params::schema_id())* + #(,#const_params=#const_params)* + ) + ) + }, + ) } else { let mut schema_name_fmt = schema_base_name; schema_name_fmt.push_str("_for_{}"); schema_name_fmt.push_str(&"_and_{}".repeat(params.len() - 1)); - quote! { - format!(#schema_name_fmt #(,#type_params::schema_name())* #(,#const_params)*) - } + ( + quote! { + format!(#schema_name_fmt #(,#type_params::schema_name())* #(,#const_params)*) + }, + quote! { + std::borrow::Cow::Owned( + format!( + std::concat!( + std::module_path!(), + "::", + #schema_name_fmt + ) + #(,#type_params::schema_id())* + #(,#const_params)* + ) + ) + }, + ) }; let schema_expr = if repr { @@ -138,6 +181,10 @@ fn derive_json_schema( #schema_name } + fn schema_id() -> std::borrow::Cow<'static, str> { + #schema_id + } + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { #schema_expr } diff --git a/schemars_derive/src/schema_exprs.rs b/schemars_derive/src/schema_exprs.rs index 1f55084..9bd8132 100644 --- a/schemars_derive/src/schema_exprs.rs +++ b/schemars_derive/src/schema_exprs.rs @@ -109,6 +109,15 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option) { #fn_name.to_string() } + fn schema_id() -> std::borrow::Cow<'static, str> { + std::borrow::Cow::Borrowed(std::concat!( + "_SchemarsSchemaWithFunction/", + std::module_path!(), + "/", + #fn_name + )) + } + fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { #fun(gen) }