use crate::Schema;
use crate::_alloc_prelude::*;
use alloc::collections::BTreeSet;
use serde_jsonc2::{jsonc, Map, Value};
pub trait Transform {
fn transform(&mut self, schema: &mut Schema);
#[doc(hidden)]
fn _debug_type_name(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(core::any::type_name::<Self>())
}
}
impl<F> Transform for F
where
F: FnMut(&mut Schema),
{
fn transform(&mut self, schema: &mut Schema) {
self(schema);
}
}
pub fn transform_subschemas<T: Transform + ?Sized>(t: &mut T, schema: &mut Schema) {
for (key, value) in schema.as_object_mut().into_iter().flatten() {
match key.as_str() {
"not"
| "if"
| "then"
| "else"
| "contains"
| "additionalProperties"
| "propertyNames"
| "additionalItems" => {
if let Ok(subschema) = value.try_into() {
t.transform(subschema);
}
}
"allOf" | "anyOf" | "oneOf" | "prefixItems" => {
if let Some(array) = value.as_array_mut() {
for value in array {
if let Ok(subschema) = value.try_into() {
t.transform(subschema);
}
}
}
}
"items" => {
if let Some(array) = value.as_array_mut() {
for value in array {
if let Ok(subschema) = value.try_into() {
t.transform(subschema);
}
}
} else if let Ok(subschema) = value.try_into() {
t.transform(subschema);
}
}
"properties" | "patternProperties" | "$defs" | "definitions" => {
if let Some(obj) = value.as_object_mut() {
for value in obj.values_mut() {
if let Ok(subschema) = value.try_into() {
t.transform(subschema);
}
}
}
}
_ => {}
}
}
}
pub(crate) fn transform_immediate_subschemas<T: Transform + ?Sized>(
t: &mut T,
schema: &mut Schema,
) {
for (key, value) in schema.as_object_mut().into_iter().flatten() {
match key.as_str() {
"if" | "then" | "else" => {
if let Ok(subschema) = value.try_into() {
t.transform(subschema);
}
}
"allOf" | "anyOf" | "oneOf" => {
if let Some(array) = value.as_array_mut() {
for value in array {
if let Ok(subschema) = value.try_into() {
t.transform(subschema);
}
}
}
}
_ => {}
}
}
}
#[derive(Debug, Clone)]
pub struct RecursiveTransform<T>(pub T);
impl<T> Transform for RecursiveTransform<T>
where
T: Transform,
{
fn transform(&mut self, schema: &mut Schema) {
self.0.transform(schema);
transform_subschemas(self, schema);
}
}
#[derive(Debug, Clone)]
pub struct ReplaceBoolSchemas {
pub skip_additional_properties: bool,
}
impl Transform for ReplaceBoolSchemas {
fn transform(&mut self, schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() {
if self.skip_additional_properties {
if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") {
transform_subschemas(self, schema);
schema.insert(ap_key, ap_value);
return;
}
}
transform_subschemas(self, schema);
} else {
schema.ensure_object();
}
}
}
#[derive(Debug, Clone)]
pub struct RemoveRefSiblings;
impl Transform for RemoveRefSiblings {
fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema);
if let Some(obj) = schema.as_object_mut().filter(|o| o.len() > 1) {
if let Some(ref_value) = obj.remove("$ref") {
if let Value::Array(all_of) = obj.entry("allOf").or_insert(Value::Array(Vec::new()))
{
all_of.push(jsonc!({
"$ref": ref_value
}));
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct SetSingleExample;
impl Transform for SetSingleExample {
fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema);
if let Some(Value::Array(examples)) = schema.remove("examples") {
if let Some(first_example) = examples.into_iter().next() {
schema.insert("example".into(), first_example);
}
}
}
}
#[derive(Debug, Clone)]
pub struct ReplaceConstValue;
impl Transform for ReplaceConstValue {
fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema);
if let Some(value) = schema.remove("const") {
schema.insert("enum".into(), Value::Array(vec![value]));
}
}
}
#[derive(Debug, Clone)]
pub struct ReplacePrefixItems;
impl Transform for ReplacePrefixItems {
fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema);
if let Some(prefix_items) = schema.remove("prefixItems") {
let previous_items = schema.insert("items".to_owned(), prefix_items);
if let Some(previous_items) = previous_items {
schema.insert("additionalItems".to_owned(), previous_items);
}
}
}
}
#[derive(Debug, Clone)]
pub struct ReplaceUnevaluatedProperties;
impl Transform for ReplaceUnevaluatedProperties {
fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema);
let Some(up) = schema.remove("unevaluatedProperties") else {
return;
};
schema.insert("additionalProperties".to_owned(), up);
let mut gather_property_names = GatherPropertyNames::default();
gather_property_names.transform(schema);
let property_names = gather_property_names.0;
if property_names.is_empty() {
return;
}
if let Some(properties) = schema
.ensure_object()
.entry("properties")
.or_insert(Map::new().into())
.as_object_mut()
{
for name in property_names {
properties.entry(name).or_insert(true.into());
}
}
}
}
#[derive(Default)]
struct GatherPropertyNames(BTreeSet<String>);
impl Transform for GatherPropertyNames {
fn transform(&mut self, schema: &mut Schema) {
self.0.extend(
schema
.as_object()
.iter()
.filter_map(|o| o.get("properties"))
.filter_map(Value::as_object)
.flat_map(Map::keys)
.cloned(),
);
transform_immediate_subschemas(self, schema);
}
}