Add schema_id(), handles different types with the same name (#247)

This commit is contained in:
Graham Esau 2023-09-17 20:36:52 +01:00 committed by GitHub
parent 53bb51cb25
commit 342b2dff33
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 415 additions and 34 deletions

View file

@ -1,3 +1,4 @@
{ {
"rust-analyzer.check.command": "clippy" "rust-analyzer.check.command": "clippy",
"rust-analyzer.showUnlinkedFileNotification": false
} }

View file

@ -11,6 +11,8 @@ use crate::schema::*;
use crate::{visit::*, JsonSchema, Map}; use crate::{visit::*, JsonSchema, Map};
use dyn_clone::DynClone; use dyn_clone::DynClone;
use serde::Serialize; use serde::Serialize;
use std::borrow::Cow;
use std::collections::HashMap;
use std::{any::Any, collections::HashSet, fmt::Debug}; use std::{any::Any, collections::HashSet, fmt::Debug};
/// Settings to customize how Schemas are generated. /// Settings to customize how Schemas are generated.
@ -149,7 +151,9 @@ impl SchemaSettings {
pub struct SchemaGenerator { pub struct SchemaGenerator {
settings: SchemaSettings, settings: SchemaSettings,
definitions: Map<String, Schema>, definitions: Map<String, Schema>,
pending_schema_names: HashSet<String>, pending_schema_ids: HashSet<Cow<'static, str>>,
schema_id_to_name: HashMap<Cow<'static, str>, String>,
used_schema_names: HashSet<String>,
} }
impl Clone for SchemaGenerator { impl Clone for SchemaGenerator {
@ -157,7 +161,9 @@ impl Clone for SchemaGenerator {
Self { Self {
settings: self.settings.clone(), settings: self.settings.clone(),
definitions: self.definitions.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 /// If `T`'s schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will
/// add them to the `SchemaGenerator`'s schema definitions. /// add them to the `SchemaGenerator`'s schema definitions.
pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema { pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
let name = T::schema_name(); let id = T::schema_id();
let return_ref = T::is_referenceable() 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 { 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) { if !self.definitions.contains_key(&name) {
self.insert_new_subschema_for::<T>(name); self.insert_new_subschema_for::<T>(name, id);
} }
Schema::new_ref(reference) Schema::new_ref(reference)
} else { } else {
self.json_schema_internal::<T>(&name) self.json_schema_internal::<T>(id)
} }
} }
fn insert_new_subschema_for<T: ?Sized + JsonSchema>(&mut self, name: String) { fn insert_new_subschema_for<T: ?Sized + JsonSchema>(
&mut self,
name: String,
id: Cow<'static, str>,
) {
let dummy = Schema::Bool(false); let dummy = Schema::Bool(false);
// insert into definitions BEFORE calling json_schema to avoid infinite recursion // insert into definitions BEFORE calling json_schema to avoid infinite recursion
self.definitions.insert(name.clone(), dummy); self.definitions.insert(name.clone(), dummy);
let schema = self.json_schema_internal::<T>(&name); let schema = self.json_schema_internal::<T>(id);
self.definitions.insert(name, schema); 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 /// add them to the `SchemaGenerator`'s schema definitions and include them in the returned `SchemaObject`'s
/// [`definitions`](../schema/struct.Metadata.html#structfield.definitions) /// [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema { pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
let name = T::schema_name(); let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object();
let mut schema = self.json_schema_internal::<T>(&name).into_object(); schema.metadata().title.get_or_insert_with(T::schema_name);
schema.metadata().title.get_or_insert(name);
let mut root = RootSchema { let mut root = RootSchema {
meta_schema: self.settings.meta_schema.clone(), meta_schema: self.settings.meta_schema.clone(),
definitions: self.definitions.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 /// 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) /// include them in the returned `SchemaObject`'s [`definitions`](../schema/struct.Metadata.html#structfield.definitions)
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema { pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
let name = T::schema_name(); let mut schema = self.json_schema_internal::<T>(T::schema_id()).into_object();
let mut schema = self.json_schema_internal::<T>(&name).into_object(); schema.metadata().title.get_or_insert_with(T::schema_name);
schema.metadata().title.get_or_insert(name);
let mut root = RootSchema { let mut root = RootSchema {
meta_schema: self.settings.meta_schema, meta_schema: self.settings.meta_schema,
definitions: self.definitions, definitions: self.definitions,
@ -418,29 +449,29 @@ impl SchemaGenerator {
} }
} }
fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, name: &str) -> Schema { fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, id: Cow<'static, str>) -> Schema {
struct PendingSchemaState<'a> { struct PendingSchemaState<'a> {
gen: &'a mut SchemaGenerator, gen: &'a mut SchemaGenerator,
name: &'a str, id: Cow<'static, str>,
did_add: bool, did_add: bool,
} }
impl<'a> PendingSchemaState<'a> { impl<'a> PendingSchemaState<'a> {
fn new(gen: &'a mut SchemaGenerator, name: &'a str) -> Self { fn new(gen: &'a mut SchemaGenerator, id: Cow<'static, str>) -> Self {
let did_add = gen.pending_schema_names.insert(name.to_owned()); let did_add = gen.pending_schema_ids.insert(id.clone());
Self { gen, name, did_add } Self { gen, id, did_add }
} }
} }
impl Drop for PendingSchemaState<'_> { impl Drop for PendingSchemaState<'_> {
fn drop(&mut self) { fn drop(&mut self) {
if self.did_add { 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) T::json_schema(pss.gen)
} }
} }

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
// Does not require T: JsonSchema. // Does not require T: JsonSchema.
impl<T> JsonSchema for [T; 0] { impl<T> JsonSchema for [T; 0] {
@ -10,6 +11,10 @@ impl<T> JsonSchema for [T; 0] {
"EmptyArray".to_owned() "EmptyArray".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("[]")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),
@ -33,6 +38,11 @@ macro_rules! array_impls {
format!("Array_size_{}_of_{}", $len, T::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),

View file

@ -3,6 +3,7 @@ use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use chrono::prelude::*; use chrono::prelude::*;
use serde_json::json; use serde_json::json;
use std::borrow::Cow;
impl JsonSchema for Weekday { impl JsonSchema for Weekday {
no_ref_schema!(); no_ref_schema!();
@ -11,6 +12,10 @@ impl JsonSchema for Weekday {
"Weekday".to_owned() "Weekday".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("chrono::Weekday")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),
@ -41,6 +46,10 @@ macro_rules! formatted_string_impl {
stringify!($ty).to_owned() stringify!($ty).to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed(stringify!(chrono::$ty))
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),

View file

@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use serde_json::json; use serde_json::json;
use std::borrow::Cow;
use std::ops::{Bound, Range, RangeInclusive}; use std::ops::{Bound, Range, RangeInclusive};
impl<T: JsonSchema> JsonSchema for Option<T> { impl<T: JsonSchema> JsonSchema for Option<T> {
@ -11,6 +12,10 @@ impl<T: JsonSchema> JsonSchema for Option<T> {
format!("Nullable_{}", T::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = gen.subschema_for::<T>(); let mut schema = gen.subschema_for::<T>();
if gen.settings().option_add_null_type { if gen.settings().option_add_null_type {
@ -69,6 +74,10 @@ impl<T: JsonSchema, E: JsonSchema> JsonSchema for Result<T, E> {
format!("Result_of_{}_or_{}", T::schema_name(), E::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut ok_schema = SchemaObject { let mut ok_schema = SchemaObject {
instance_type: Some(InstanceType::Object.into()), instance_type: Some(InstanceType::Object.into()),
@ -99,6 +108,10 @@ impl<T: JsonSchema> JsonSchema for Bound<T> {
format!("Bound_of_{}", T::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut included_schema = SchemaObject { let mut included_schema = SchemaObject {
instance_type: Some(InstanceType::Object.into()), instance_type: Some(InstanceType::Object.into()),
@ -139,6 +152,10 @@ impl<T: JsonSchema> JsonSchema for Range<T> {
format!("Range_of_{}", T::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { let mut schema = SchemaObject {
instance_type: Some(InstanceType::Object.into()), instance_type: Some(InstanceType::Object.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
macro_rules! decimal_impl { macro_rules! decimal_impl {
($type:ty) => { ($type:ty) => {
@ -11,6 +12,10 @@ macro_rules! decimal_impl {
"Decimal".to_owned() "Decimal".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed($name)
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),

View file

@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use either::Either; use either::Either;
use std::borrow::Cow;
impl<L: JsonSchema, R: JsonSchema> JsonSchema for Either<L, R> { impl<L: JsonSchema, R: JsonSchema> JsonSchema for Either<L, R> {
no_ref_schema!(); no_ref_schema!();
@ -10,6 +11,14 @@ impl<L: JsonSchema, R: JsonSchema> JsonSchema for Either<L, R> {
format!("Either_{}_or_{}", L::schema_name(), R::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject::default(); let mut schema = SchemaObject::default();
schema.subschemas().any_of = Some(vec![gen.subschema_for::<L>(), gen.subschema_for::<R>()]); schema.subschemas().any_of = Some(vec![gen.subschema_for::<L>(), gen.subschema_for::<R>()]);

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use std::ffi::{CStr, CString, OsStr, OsString}; use std::ffi::{CStr, CString, OsStr, OsString};
impl JsonSchema for OsString { impl JsonSchema for OsString {
@ -8,6 +9,10 @@ impl JsonSchema for OsString {
"OsString".to_owned() "OsString".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("std::ffi::OsString")
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut unix_schema = SchemaObject { let mut unix_schema = SchemaObject {
instance_type: Some(InstanceType::Object.into()), instance_type: Some(InstanceType::Object.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
macro_rules! map_impl { macro_rules! map_impl {
($($desc:tt)+) => { ($($desc:tt)+) => {
@ -14,6 +15,10 @@ macro_rules! map_impl {
format!("Map_of_{}", V::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let subschema = gen.subschema_for::<V>(); let subschema = gen.subschema_for::<V>();
SchemaObject { SchemaObject {

View file

@ -17,6 +17,10 @@ macro_rules! forward_impl {
<$target>::schema_name() <$target>::schema_name()
} }
fn schema_id() -> std::borrow::Cow<'static, str> {
<$target>::schema_id()
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
<$target>::json_schema(gen) <$target>::json_schema(gen)
} }

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use std::num::*; use std::num::*;
macro_rules! nonzero_unsigned_impl { macro_rules! nonzero_unsigned_impl {
@ -12,6 +13,10 @@ macro_rules! nonzero_unsigned_impl {
stringify!($type).to_owned() stringify!($type).to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed(stringify!(std::num::$type))
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let zero_schema: Schema = SchemaObject { let zero_schema: Schema = SchemaObject {
const_value: Some(0.into()), const_value: Some(0.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use std::num::*; use std::num::*;
macro_rules! nonzero_unsigned_impl { macro_rules! nonzero_unsigned_impl {
@ -12,6 +13,10 @@ macro_rules! nonzero_unsigned_impl {
stringify!($type).to_owned() stringify!($type).to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed(stringify!(std::num::$type))
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema: SchemaObject = <$primitive>::json_schema(gen).into(); let mut schema: SchemaObject = <$primitive>::json_schema(gen).into();
schema.number().minimum = Some(1.0); schema.number().minimum = Some(1.0);

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -19,6 +20,10 @@ macro_rules! simple_impl {
$name.to_owned() $name.to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed($name)
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::$instance_type.into()), instance_type: Some(InstanceType::$instance_type.into()),
@ -64,6 +69,10 @@ macro_rules! unsigned_impl {
$format.to_owned() $format.to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed($format)
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { let mut schema = SchemaObject {
instance_type: Some(InstanceType::$instance_type.into()), instance_type: Some(InstanceType::$instance_type.into()),
@ -91,6 +100,10 @@ impl JsonSchema for char {
"Character".to_owned() "Character".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("char")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),

View file

@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use semver::Version; use semver::Version;
use std::borrow::Cow;
impl JsonSchema for Version { impl JsonSchema for Version {
no_ref_schema!(); no_ref_schema!();
@ -10,6 +11,10 @@ impl JsonSchema for Version {
"Version".to_owned() "Version".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("semver::Version")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
macro_rules! seq_impl { macro_rules! seq_impl {
($($desc:tt)+) => { ($($desc:tt)+) => {
@ -14,6 +15,11 @@ macro_rules! seq_impl {
format!("Array_of_{}", T::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),
@ -41,6 +47,11 @@ macro_rules! set_impl {
format!("Set_of_{}", T::schema_name()) 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::Array.into()), instance_type: Some(InstanceType::Array.into()),

View file

@ -2,6 +2,7 @@ use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use serde_json::{Map, Number, Value}; use serde_json::{Map, Number, Value};
use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
impl JsonSchema for Value { impl JsonSchema for Value {
@ -11,6 +12,10 @@ impl JsonSchema for Value {
"AnyValue".to_owned() "AnyValue".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("AnyValue")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
Schema::Bool(true) Schema::Bool(true)
} }
@ -25,6 +30,10 @@ impl JsonSchema for Number {
"Number".to_owned() "Number".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("Number")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::Number.into()), instance_type: Some(InstanceType::Number.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
impl JsonSchema for Duration { impl JsonSchema for Duration {
@ -8,6 +9,10 @@ impl JsonSchema for Duration {
"Duration".to_owned() "Duration".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("std::time::Duration")
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { let mut schema = SchemaObject {
instance_type: Some(InstanceType::Object.into()), instance_type: Some(InstanceType::Object.into()),
@ -29,6 +34,10 @@ impl JsonSchema for SystemTime {
"SystemTime".to_owned() "SystemTime".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("std::time::SystemTime")
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema = SchemaObject { let mut schema = SchemaObject {
instance_type: Some(InstanceType::Object.into()), instance_type: Some(InstanceType::Object.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
macro_rules! tuple_impls { macro_rules! tuple_impls {
($($len:expr => ($($name:ident)+))+) => { ($($len:expr => ($($name:ident)+))+) => {
@ -14,6 +15,14 @@ macro_rules! tuple_impls {
name 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 { fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let items = vec![ let items = vec![
$(gen.subschema_for::<$name>()),+ $(gen.subschema_for::<$name>()),+

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use url::Url; use url::Url;
impl JsonSchema for Url { impl JsonSchema for Url {
@ -10,6 +11,10 @@ impl JsonSchema for Url {
"Url".to_owned() "Url".to_owned()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("url::Url")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use uuid08::Uuid; use uuid08::Uuid;
impl JsonSchema for Uuid { impl JsonSchema for Uuid {
@ -10,6 +11,10 @@ impl JsonSchema for Uuid {
"Uuid".to_string() "Uuid".to_string()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("uuid::Uuid")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),

View file

@ -1,6 +1,7 @@
use crate::gen::SchemaGenerator; use crate::gen::SchemaGenerator;
use crate::schema::*; use crate::schema::*;
use crate::JsonSchema; use crate::JsonSchema;
use std::borrow::Cow;
use uuid1::Uuid; use uuid1::Uuid;
impl JsonSchema for Uuid { impl JsonSchema for Uuid {
@ -10,6 +11,10 @@ impl JsonSchema for Uuid {
"Uuid".to_string() "Uuid".to_string()
} }
fn schema_id() -> Cow<'static, str> {
Cow::Borrowed("uuid::Uuid")
}
fn json_schema(_: &mut SchemaGenerator) -> Schema { fn json_schema(_: &mut SchemaGenerator) -> Schema {
SchemaObject { SchemaObject {
instance_type: Some(InstanceType::String.into()), instance_type: Some(InstanceType::String.into()),

View file

@ -41,6 +41,8 @@ pub mod visit;
#[cfg(feature = "schemars_derive")] #[cfg(feature = "schemars_derive")]
extern crate schemars_derive; extern crate schemars_derive;
use std::borrow::Cow;
#[cfg(feature = "schemars_derive")] #[cfg(feature = "schemars_derive")]
pub use 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)]`. /// This can also be automatically derived on most custom types with `#[derive(JsonSchema)]`.
/// ///
/// # Example /// # Examples
/// Deriving an implementation:
/// ``` /// ```
/// use schemars::{schema_for, JsonSchema}; /// use schemars::{schema_for, JsonSchema};
/// ///
@ -67,6 +70,64 @@ use schema::Schema;
/// ///
/// let my_schema = schema_for!(MyStruct); /// 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<T>(PhantomData<T>);
///
/// impl<T: JsonSchema> JsonSchema for GenericType<T> {
/// 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!(<GenericType<i32>>::schema_id(), <&mut GenericType<&i32>>::schema_id());
/// ```
///
pub trait JsonSchema { pub trait JsonSchema {
/// Whether JSON Schemas generated for this type should be re-used where possible using the `$ref` keyword. /// 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. /// This is used as the title for root schemas, and the key within the root's `definitions` property for subschemas.
fn schema_name() -> String; 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. /// Generates a JSON Schema for this type.
/// ///
/// If the returned schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will /// If the returned schema depends on any [referenceable](JsonSchema::is_referenceable) schemas, then this method will

View file

@ -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"
}
}
}
}
}

View file

@ -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::<Config2>("same_name")
}

View file

@ -67,6 +67,10 @@ fn derive_json_schema(
<#ty as schemars::JsonSchema>::schema_name() <#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 { fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
<#ty as schemars::JsonSchema>::json_schema(gen) <#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 const_params: Vec<_> = cont.generics.const_params().map(|c| &c.ident).collect();
let params: Vec<_> = type_params.iter().chain(const_params.iter()).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('{')) || (cont.attrs.is_renamed && !schema_base_name.contains('{'))
{ {
(
quote! { quote! {
#schema_base_name.to_owned() #schema_base_name.to_owned()
} },
quote! {
std::borrow::Cow::Borrowed(std::concat!(
std::module_path!(),
"::",
#schema_base_name
))
},
)
} else if cont.attrs.is_renamed { } else if cont.attrs.is_renamed {
let mut schema_name_fmt = schema_base_name; let mut schema_name_fmt = schema_base_name;
for tp in &params { for tp in &params {
schema_name_fmt.push_str(&format!("{{{}:.0}}", tp)); schema_name_fmt.push_str(&format!("{{{}:.0}}", tp));
} }
(
quote! { quote! {
format!(#schema_name_fmt #(,#type_params=#type_params::schema_name())* #(,#const_params=#const_params)*) 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 { } else {
let mut schema_name_fmt = schema_base_name; let mut schema_name_fmt = schema_base_name;
schema_name_fmt.push_str("_for_{}"); schema_name_fmt.push_str("_for_{}");
schema_name_fmt.push_str(&"_and_{}".repeat(params.len() - 1)); schema_name_fmt.push_str(&"_and_{}".repeat(params.len() - 1));
(
quote! { quote! {
format!(#schema_name_fmt #(,#type_params::schema_name())* #(,#const_params)*) 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 { let schema_expr = if repr {
@ -138,6 +181,10 @@ fn derive_json_schema(
#schema_name #schema_name
} }
fn schema_id() -> std::borrow::Cow<'static, str> {
#schema_id
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
#schema_expr #schema_expr
} }

View file

@ -109,6 +109,15 @@ fn type_for_schema(with_attr: &WithAttr) -> (syn::Type, Option<TokenStream>) {
#fn_name.to_string() #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 { fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
#fun(gen) #fun(gen)
} }