use schemars::{
JsonSchema, SchemaGenerator, generate::SchemaSettings, json_schema, transform::Transform,
};
use serde_json::Map;
use serde_reflection::{
ContainerFormat, Format, Named, Samples, Tracer, TracerConfig, VariantFormat,
};
use crate::{
Internal, Meta, TreeDeserializeOwned, TreeSerialize,
trace::{Node, Types},
};
pub const TREE_ABSENT: &str = "__tree-absent__";
pub const TREE_ACCESS: &str = "__tree-access__";
pub struct Strictify;
impl Transform for Strictify {
fn transform(&mut self, schema: &mut schemars::Schema) {
if let Some(o) = schema.as_object_mut() {
if o.contains_key("prefixItems") {
assert_eq!(o.insert("items".to_string(), false.into()), None);
}
if let Some(k) = o.get("properties") {
let k = k.as_object().unwrap().keys().cloned().collect::<Vec<_>>();
assert_eq!(o.insert("required".to_string(), k.into()), None);
assert_eq!(
o.insert("additionalProperties".to_string(), false.into()),
None
);
}
}
schemars::transform::transform_subschemas(self, schema);
}
}
pub struct AllowAbsent;
impl Transform for AllowAbsent {
fn transform(&mut self, schema: &mut schemars::Schema) {
if let Some(o) = schema.as_object_mut()
&& o.get("tree-maybe-absent") == Some(&true.into())
{
o.remove("tree-maybe-absent").unwrap();
*schema = json_schema!({"oneOf": [schema, {"const": TREE_ABSENT}]});
}
schemars::transform::transform_subschemas(self, schema);
}
}
pub trait ReflectJsonSchema {
fn json_schema(&self, generator: &mut SchemaGenerator) -> Option<schemars::Schema>;
}
impl ReflectJsonSchema for Format {
fn json_schema(&self, generator: &mut SchemaGenerator) -> Option<schemars::Schema> {
Some(match self {
Format::Variable(_variable) => None?, Format::TypeName(name) => schemars::Schema::new_ref(format!("#/$defs/{name}")),
Format::Unit => <()>::json_schema(generator),
Format::Bool => bool::json_schema(generator),
Format::I8 => i8::json_schema(generator),
Format::I16 => i16::json_schema(generator),
Format::I32 => i32::json_schema(generator),
Format::I64 => i64::json_schema(generator),
Format::I128 => i128::json_schema(generator),
Format::U8 => u8::json_schema(generator),
Format::U16 => u16::json_schema(generator),
Format::U32 => u32::json_schema(generator),
Format::U64 => u64::json_schema(generator),
Format::U128 => u128::json_schema(generator),
Format::F32 => f32::json_schema(generator),
Format::F64 => f64::json_schema(generator),
Format::Char => char::json_schema(generator),
Format::Str => str::json_schema(generator),
Format::Bytes => <[u8]>::json_schema(generator),
Format::Option(format) => {
json_schema!({"oneOf": [
format.json_schema(generator)?,
{"const": null}
]})
}
Format::Seq(format) => json_schema!({"items": format.json_schema(generator)?}),
Format::Map { key, value } => {
if matches!(**key, Format::Str) {
json_schema!({
"type": "object",
"additionalProperties": value.json_schema(generator)?
})
} else {
json_schema!({
"type": "array",
"items": {
"prefixItems": [
key.json_schema(generator)?,
value.json_schema(generator)?
],
}
})
}
}
Format::Tuple(formats) => formats.json_schema(generator)?,
Format::TupleArray { content, size } => json_schema!({
"type": "array",
"items": content.json_schema(generator)?,
"minItems": size,
"maxItems": size
}),
})
}
}
impl ReflectJsonSchema for Vec<Named<Format>> {
fn json_schema(&self, generator: &mut SchemaGenerator) -> Option<schemars::Schema> {
let items: Option<Map<_, _>> = self
.iter()
.map(|n| Some((n.name.to_string(), n.value.json_schema(generator)?.into())))
.collect();
Some(json_schema!({
"type": "object",
"properties": items?,
}))
}
}
impl ReflectJsonSchema for Vec<Format> {
fn json_schema(&self, generator: &mut SchemaGenerator) -> Option<schemars::Schema> {
let items: Option<Vec<_>> = self.iter().map(|f| f.json_schema(generator)).collect();
Some(json_schema!({
"type": "array",
"prefixItems": items?
}))
}
}
impl ReflectJsonSchema for ContainerFormat {
fn json_schema(&self, generator: &mut SchemaGenerator) -> Option<schemars::Schema> {
match self {
ContainerFormat::UnitStruct => Some(<()>::json_schema(generator)),
ContainerFormat::NewTypeStruct(format) => format.json_schema(generator),
ContainerFormat::TupleStruct(formats) => formats.json_schema(generator),
ContainerFormat::Struct(nameds) => nameds.json_schema(generator),
ContainerFormat::Enum(map) => {
let variants: Option<Vec<_>> = map
.values()
.map(|n| {
let mut sch = n.value.json_schema(generator)?;
Some(if sch.as_bool() == Some(false) {
json_schema!({"const": &n.name})
} else if generator.settings().untagged_enum_variant_titles {
sch.insert("title".to_string(), n.name.clone().into());
sch
} else {
json_schema!({
"type": "object",
"properties": {&n.name: sch}
})
})
})
.collect();
Some(json_schema!({"oneOf": variants?}))
}
}
}
}
impl ReflectJsonSchema for VariantFormat {
fn json_schema(&self, generator: &mut SchemaGenerator) -> Option<schemars::Schema> {
match self {
VariantFormat::Variable(_variable) => None,
VariantFormat::Unit => Some(false.into()),
VariantFormat::NewType(format) => format.json_schema(generator),
VariantFormat::Tuple(formats) => formats.json_schema(generator),
VariantFormat::Struct(nameds) => nameds.json_schema(generator),
}
}
}
impl ReflectJsonSchema for Node<(&'static crate::Schema, Option<Format>)> {
fn json_schema(&self, generator: &mut SchemaGenerator) -> Option<schemars::Schema> {
let mut sch = if let Some(internal) = self.data.0.internal.as_ref() {
match internal {
Internal::Named(nameds) => {
let items: Option<Map<_, _>> = nameds
.iter()
.zip(&self.children)
.map(|(named, child)| {
let mut sch = child.json_schema(generator)?;
push_meta(&mut sch, "tree-outer-meta", &named.meta);
Some((named.name.to_string(), sch.into()))
})
.collect();
json_schema!({
"type": "object",
"properties": items?,
})
}
Internal::Numbered(numbereds) => {
let items: Option<Vec<_>> = numbereds
.iter()
.zip(&self.children)
.map(|(numbered, child)| {
let mut sch = child.json_schema(generator)?;
push_meta(&mut sch, "tree-outer-meta", &numbered.meta);
Some(sch)
})
.collect();
json_schema!({
"type": "array",
"prefixItems": items?
})
}
Internal::Homogeneous(homogeneous) => {
let mut sch = self.children[0].json_schema(generator)?;
push_meta(&mut sch, "tree-outer-meta", &homogeneous.meta);
json_schema!({
"type": "array",
"items": sch,
"minItems": homogeneous.len,
"maxItems": homogeneous.len
})
}
}
} else {
self.data.1.as_ref()?.json_schema(generator)?
};
sch.insert("tree-maybe-absent".to_string(), true.into());
push_meta(&mut sch, "tree-inner-meta", &self.data.0.meta);
#[cfg(feature = "meta-str")]
if let Some(meta) = &self.data.0.meta
&& let Some(name) = meta.iter().find_map(|(key, typename)| {
(*key == "typename").then_some(format!("tree-internal-{typename}"))
})
{
if let Some(existing) = generator.definitions().get(&name) {
assert_eq!(existing, sch.as_value()); } else {
generator
.definitions_mut()
.insert(name.to_string(), sch.into());
}
return Some(schemars::Schema::new_ref(format!("#/$defs/{name}")));
}
Some(sch)
}
}
fn push_meta(sch: &mut schemars::Schema, key: &str, meta: &Option<Meta>) {
if let Some(meta) = meta {
#[cfg(feature = "meta-str")]
assert_eq!(
sch.insert(
key.to_string(),
meta.iter()
.map(|(k, v)| (k.to_string(), v.to_string().into()))
.collect::<Map<_, _>>()
.into(),
),
None
);
#[cfg(not(any(feature = "meta-str")))]
let _ = (sch, meta, key);
}
}
pub struct TreeJsonSchema<T> {
pub types: Types<T>,
pub registry: serde_reflection::Registry,
pub samples: serde_reflection::Samples,
pub generator: schemars::SchemaGenerator,
pub root: schemars::Schema,
}
impl<T: TreeSerialize + TreeDeserializeOwned> TreeJsonSchema<T> {
pub fn new(value: Option<&T>) -> Result<Self, serde_reflection::Error> {
let mut types: Types<T> = Default::default();
let mut tracer = Tracer::new(
TracerConfig::default()
.is_human_readable(true)
.record_samples_for_newtype_structs(true)
.record_samples_for_structs(true)
.record_samples_for_tuple_structs(true),
);
let mut samples = Samples::new();
if let Some(value) = value {
types.trace_values(&mut tracer, &mut samples, value)?;
}
types.trace_types(&mut tracer, &samples)?;
let registry = tracer.registry()?;
let mut generator = SchemaGenerator::new(SchemaSettings::draft2020_12());
let defs: Vec<_> = registry
.iter()
.map(|(name, value)| (name.clone(), value.json_schema(&mut generator).into()))
.collect();
generator.definitions_mut().extend(defs);
types.normalize()?;
let mut root = types.root().json_schema(&mut generator).ok_or(
serde_reflection::Error::UnknownFormatInContainer("reflection incomplete".to_string()),
)?;
root.insert("$defs".to_string(), generator.definitions().clone().into());
if let Some(meta_schema) = generator.settings().meta_schema.as_deref() {
root.insert("$schema".to_string(), meta_schema.into());
}
Ok(Self {
types,
samples,
registry,
generator,
root,
})
}
}