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};
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SchemaSettings {
pub option_nullable: bool,
pub option_add_null_type: bool,
pub definitions_path: String,
pub meta_schema: Option<String>,
pub visitors: Vec<Box<dyn GenVisitor>>,
pub inline_subschemas: bool,
}
impl Default for SchemaSettings {
fn default() -> SchemaSettings {
SchemaSettings::draft07()
}
}
impl SchemaSettings {
pub fn draft07() -> SchemaSettings {
SchemaSettings {
option_nullable: false,
option_add_null_type: true,
definitions_path: "#/definitions/".to_owned(),
meta_schema: Some("http://json-schema.org/draft-07/schema#".to_owned()),
visitors: vec![Box::new(RemoveRefSiblings)],
inline_subschemas: false,
}
}
pub fn draft2019_09() -> SchemaSettings {
SchemaSettings {
option_nullable: false,
option_add_null_type: true,
definitions_path: "#/definitions/".to_owned(),
meta_schema: Some("https://json-schema.org/draft/2019-09/schema".to_owned()),
visitors: Vec::default(),
inline_subschemas: false,
}
}
pub fn openapi3() -> SchemaSettings {
SchemaSettings {
option_nullable: true,
option_add_null_type: false,
definitions_path: "#/components/schemas/".to_owned(),
meta_schema: Some(
"https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema"
.to_owned(),
),
visitors: vec![
Box::new(RemoveRefSiblings),
Box::new(ReplaceBoolSchemas {
skip_additional_properties: true,
}),
Box::new(SetSingleExample {
retain_examples: false,
}),
],
inline_subschemas: false,
}
}
pub fn with(mut self, configure_fn: impl FnOnce(&mut Self)) -> Self {
configure_fn(&mut self);
self
}
pub fn with_visitor(mut self, visitor: impl Visitor + Debug + Clone + 'static) -> Self {
self.visitors.push(Box::new(visitor));
self
}
pub fn into_generator(self) -> SchemaGenerator {
SchemaGenerator::new(self)
}
}
#[derive(Debug, Default)]
pub struct SchemaGenerator {
settings: SchemaSettings,
definitions: Map<String, Schema>,
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 {
fn clone(&self) -> Self {
Self {
settings: self.settings.clone(),
definitions: self.definitions.clone(),
pending_schema_ids: HashSet::new(),
schema_id_to_name: HashMap::new(),
used_schema_names: HashSet::new(),
}
}
}
impl From<SchemaSettings> for SchemaGenerator {
fn from(settings: SchemaSettings) -> Self {
settings.into_generator()
}
}
impl SchemaGenerator {
pub fn new(settings: SchemaSettings) -> SchemaGenerator {
SchemaGenerator {
settings,
..Default::default()
}
}
pub fn settings(&self) -> &SchemaSettings {
&self.settings
}
#[deprecated = "This method no longer has any effect."]
pub fn make_extensible(&self, _schema: &mut SchemaObject) {}
#[deprecated = "Use `Schema::Bool(true)` instead"]
pub fn schema_for_any(&self) -> Schema {
Schema::Bool(true)
}
#[deprecated = "Use `Schema::Bool(false)` instead"]
pub fn schema_for_none(&self) -> Schema {
Schema::Bool(false)
}
pub fn subschema_for<T: ?Sized + JsonSchema>(&mut self) -> Schema {
let id = T::schema_id();
let return_ref = T::is_referenceable()
&& (!self.settings.inline_subschemas || self.pending_schema_ids.contains(&id));
if return_ref {
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::<T>(name, id);
}
Schema::new_ref(reference)
} else {
self.json_schema_internal::<T>(id)
}
}
fn insert_new_subschema_for<T: ?Sized + JsonSchema>(
&mut self,
name: String,
id: Cow<'static, str>,
) {
let dummy = Schema::Bool(false);
self.definitions.insert(name.clone(), dummy);
let schema = self.json_schema_internal::<T>(id);
self.definitions.insert(name, schema);
}
pub fn definitions(&self) -> &Map<String, Schema> {
&self.definitions
}
pub fn definitions_mut(&mut self) -> &mut Map<String, Schema> {
&mut self.definitions
}
pub fn take_definitions(&mut self) -> Map<String, Schema> {
std::mem::take(&mut self.definitions)
}
pub fn visitors_mut(&mut self) -> impl Iterator<Item = &mut dyn GenVisitor> {
self.settings.visitors.iter_mut().map(|v| v.as_mut())
}
pub fn root_schema_for<T: ?Sized + JsonSchema>(&mut self) -> RootSchema {
let mut schema = self.json_schema_internal::<T>(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(),
schema,
};
for visitor in &mut self.settings.visitors {
visitor.visit_root_schema(&mut root)
}
root
}
pub fn into_root_schema_for<T: ?Sized + JsonSchema>(mut self) -> RootSchema {
let mut schema = self.json_schema_internal::<T>(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,
schema,
};
for visitor in &mut self.settings.visitors {
visitor.visit_root_schema(&mut root)
}
root
}
pub fn root_schema_for_value<T: ?Sized + Serialize>(
&mut self,
value: &T,
) -> Result<RootSchema, serde_json::Error> {
let mut schema = value
.serialize(crate::ser::Serializer {
gen: self,
include_title: true,
})?
.into_object();
if let Ok(example) = serde_json::to_value(value) {
schema.metadata().examples.push(example);
}
let mut root = RootSchema {
meta_schema: self.settings.meta_schema.clone(),
definitions: self.definitions.clone(),
schema,
};
for visitor in &mut self.settings.visitors {
visitor.visit_root_schema(&mut root)
}
Ok(root)
}
pub fn into_root_schema_for_value<T: ?Sized + Serialize>(
mut self,
value: &T,
) -> Result<RootSchema, serde_json::Error> {
let mut schema = value
.serialize(crate::ser::Serializer {
gen: &mut self,
include_title: true,
})?
.into_object();
if let Ok(example) = serde_json::to_value(value) {
schema.metadata().examples.push(example);
}
let mut root = RootSchema {
meta_schema: self.settings.meta_schema,
definitions: self.definitions,
schema,
};
for visitor in &mut self.settings.visitors {
visitor.visit_root_schema(&mut root)
}
Ok(root)
}
pub fn dereference<'a>(&'a self, schema: &Schema) -> Option<&'a Schema> {
match schema {
Schema::Object(SchemaObject {
reference: Some(ref schema_ref),
..
}) => {
let definitions_path = &self.settings().definitions_path;
if schema_ref.starts_with(definitions_path) {
let name = &schema_ref[definitions_path.len()..];
self.definitions.get(name)
} else {
None
}
}
_ => None,
}
}
fn json_schema_internal<T: ?Sized + JsonSchema>(&mut self, id: Cow<'static, str>) -> Schema {
struct PendingSchemaState<'a> {
gen: &'a mut SchemaGenerator,
id: Cow<'static, str>,
did_add: bool,
}
impl<'a> PendingSchemaState<'a> {
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_ids.remove(&self.id);
}
}
}
let pss = PendingSchemaState::new(self, id);
T::json_schema(pss.gen)
}
}
pub trait GenVisitor: Visitor + Debug + DynClone + Any {
fn as_any(&self) -> &dyn Any;
}
dyn_clone::clone_trait_object!(GenVisitor);
impl<T> GenVisitor for T
where
T: Visitor + Debug + Clone + Any,
{
fn as_any(&self) -> &dyn Any {
self
}
}