use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::BTreeMap; use std::sync::Arc; // Schema mirrors the Go Punc Generator's schema struct for consistency. // It is an order-preserving representation of a JSON Schema. pub fn deserialize_some<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { let v = Value::deserialize(deserializer)?; Ok(Some(v)) } #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct SchemaObject { // Core Schema Keywords #[serde(rename = "$id")] #[serde(skip_serializing_if = "Option::is_none")] pub id: Option, #[serde(rename = "$ref")] #[serde(skip_serializing_if = "Option::is_none")] pub r#ref: Option, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, #[serde(default)] // Allow missing type #[serde(rename = "type")] #[serde(skip_serializing_if = "Option::is_none")] pub type_: Option, // Handles string or array of strings // Object Keywords #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>>, #[serde(rename = "patternProperties")] #[serde(skip_serializing_if = "Option::is_none")] pub pattern_properties: Option>>, #[serde(rename = "additionalProperties")] #[serde(skip_serializing_if = "Option::is_none")] pub additional_properties: Option>, #[serde(rename = "$family")] #[serde(skip_serializing_if = "Option::is_none")] pub family: Option, #[serde(skip_serializing_if = "Option::is_none")] pub required: Option>, // dependencies can be schema dependencies or property dependencies #[serde(skip_serializing_if = "Option::is_none")] pub dependencies: Option>, // Array Keywords #[serde(rename = "items")] #[serde(skip_serializing_if = "Option::is_none")] pub items: Option>, #[serde(rename = "prefixItems")] #[serde(skip_serializing_if = "Option::is_none")] pub prefix_items: Option>>, // String Validation #[serde(rename = "minLength")] #[serde(skip_serializing_if = "Option::is_none")] pub min_length: Option, #[serde(rename = "maxLength")] #[serde(skip_serializing_if = "Option::is_none")] pub max_length: Option, #[serde(skip_serializing_if = "Option::is_none")] pub pattern: Option, // Array Validation #[serde(rename = "minItems")] #[serde(skip_serializing_if = "Option::is_none")] pub min_items: Option, #[serde(rename = "maxItems")] #[serde(skip_serializing_if = "Option::is_none")] pub max_items: Option, #[serde(rename = "uniqueItems")] #[serde(skip_serializing_if = "Option::is_none")] pub unique_items: Option, #[serde(rename = "contains")] #[serde(skip_serializing_if = "Option::is_none")] pub contains: Option>, #[serde(rename = "minContains")] #[serde(skip_serializing_if = "Option::is_none")] pub min_contains: Option, #[serde(rename = "maxContains")] #[serde(skip_serializing_if = "Option::is_none")] pub max_contains: Option, // Object Validation #[serde(rename = "minProperties")] #[serde(skip_serializing_if = "Option::is_none")] pub min_properties: Option, #[serde(rename = "maxProperties")] #[serde(skip_serializing_if = "Option::is_none")] pub max_properties: Option, #[serde(rename = "propertyNames")] #[serde(skip_serializing_if = "Option::is_none")] pub property_names: Option>, // Numeric Validation #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, #[serde(rename = "enum")] #[serde(skip_serializing_if = "Option::is_none")] pub enum_: Option>, // `enum` is a reserved keyword in Rust #[serde( default, rename = "const", deserialize_with = "crate::database::schema::deserialize_some" )] #[serde(skip_serializing_if = "Option::is_none")] pub const_: Option, // Numeric Validation #[serde(rename = "multipleOf")] #[serde(skip_serializing_if = "Option::is_none")] pub multiple_of: Option, #[serde(skip_serializing_if = "Option::is_none")] pub minimum: Option, #[serde(skip_serializing_if = "Option::is_none")] pub maximum: Option, #[serde(rename = "exclusiveMinimum")] #[serde(skip_serializing_if = "Option::is_none")] pub exclusive_minimum: Option, #[serde(rename = "exclusiveMaximum")] #[serde(skip_serializing_if = "Option::is_none")] pub exclusive_maximum: Option, // Combining Keywords #[serde(rename = "allOf")] #[serde(skip_serializing_if = "Option::is_none")] pub all_of: Option>>, #[serde(rename = "oneOf")] #[serde(skip_serializing_if = "Option::is_none")] pub one_of: Option>>, #[serde(rename = "not")] #[serde(skip_serializing_if = "Option::is_none")] pub not: Option>, #[serde(rename = "if")] #[serde(skip_serializing_if = "Option::is_none")] pub if_: Option>, #[serde(rename = "then")] #[serde(skip_serializing_if = "Option::is_none")] pub then_: Option>, #[serde(rename = "else")] #[serde(skip_serializing_if = "Option::is_none")] pub else_: Option>, // Custom Vocabularies #[serde(skip_serializing_if = "Option::is_none")] pub form: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub display: Option>, #[serde(rename = "enumNames")] #[serde(skip_serializing_if = "Option::is_none")] pub enum_names: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub control: Option, #[serde(skip_serializing_if = "Option::is_none")] pub actions: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub computer: Option, #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] pub extensible: Option, #[serde(skip)] pub compiled_format: Option, #[serde(skip)] pub compiled_pattern: Option, #[serde(skip)] pub compiled_pattern_properties: Option)>>, } /// Represents a compiled format validator #[derive(Clone)] pub enum CompiledFormat { Func(fn(&serde_json::Value) -> Result<(), Box>), Regex(regex::Regex), } impl std::fmt::Debug for CompiledFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { CompiledFormat::Func(_) => write!(f, "CompiledFormat::Func(...)"), CompiledFormat::Regex(r) => write!(f, "CompiledFormat::Regex({:?})", r), } } } /// A wrapper for compiled regex patterns #[derive(Debug, Clone)] pub struct CompiledRegex(pub regex::Regex); #[derive(Debug, Clone, Serialize, Default)] pub struct Schema { #[serde(flatten)] pub obj: SchemaObject, #[serde(skip)] pub always_fail: bool, } impl std::ops::Deref for Schema { type Target = SchemaObject; fn deref(&self) -> &Self::Target { &self.obj } } impl std::ops::DerefMut for Schema { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.obj } } impl Schema { pub fn compile_internals(&mut self) { self.map_children(|child| child.compile_internals()); if let Some(format_str) = &self.obj.format && let Some(fmt) = crate::database::formats::FORMATS.get(format_str.as_str()) { self.obj.compiled_format = Some(crate::database::schema::CompiledFormat::Func(fmt.func)); } if let Some(pattern_str) = &self.obj.pattern && let Ok(re) = regex::Regex::new(pattern_str) { self.obj.compiled_pattern = Some(crate::database::schema::CompiledRegex(re)); } if let Some(pattern_props) = &self.obj.pattern_properties { let mut compiled = Vec::new(); for (k, v) in pattern_props { if let Ok(re) = regex::Regex::new(k) { compiled.push((crate::database::schema::CompiledRegex(re), v.clone())); } } if !compiled.is_empty() { self.obj.compiled_pattern_properties = Some(compiled); } } } pub fn harvest(&mut self, to_insert: &mut Vec<(String, Schema)>) { if let Some(id) = &self.obj.id { to_insert.push((id.clone(), self.clone())); } self.map_children(|child| child.harvest(to_insert)); } pub fn map_children(&mut self, mut f: F) where F: FnMut(&mut Schema), { if let Some(props) = &mut self.obj.properties { for v in props.values_mut() { let mut inner = (**v).clone(); f(&mut inner); *v = Arc::new(inner); } } if let Some(pattern_props) = &mut self.obj.pattern_properties { for v in pattern_props.values_mut() { let mut inner = (**v).clone(); f(&mut inner); *v = Arc::new(inner); } } let mut map_arr = |arr: &mut Vec>| { for v in arr.iter_mut() { let mut inner = (**v).clone(); f(&mut inner); *v = Arc::new(inner); } }; if let Some(arr) = &mut self.obj.prefix_items { map_arr(arr); } if let Some(arr) = &mut self.obj.all_of { map_arr(arr); } if let Some(arr) = &mut self.obj.one_of { map_arr(arr); } let mut map_opt = |opt: &mut Option>| { if let Some(v) = opt { let mut inner = (**v).clone(); f(&mut inner); *v = Arc::new(inner); } }; map_opt(&mut self.obj.additional_properties); map_opt(&mut self.obj.items); map_opt(&mut self.obj.contains); map_opt(&mut self.obj.property_names); map_opt(&mut self.obj.not); map_opt(&mut self.obj.if_); map_opt(&mut self.obj.then_); map_opt(&mut self.obj.else_); } } impl<'de> Deserialize<'de> for Schema { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let v: Value = Deserialize::deserialize(deserializer)?; if let Some(b) = v.as_bool() { let mut obj = SchemaObject::default(); if b { obj.extensible = Some(true); } return Ok(Schema { obj, always_fail: !b, }); } let mut obj: SchemaObject = serde_json::from_value(v.clone()).map_err(serde::de::Error::custom)?; // If a schema is effectively empty (except for potentially carrying an ID), // it functions as a boolean `true` schema in Draft2020 which means it should not // restrict additional properties natively let is_empty = obj.type_.is_none() && obj.properties.is_none() && obj.pattern_properties.is_none() && obj.additional_properties.is_none() && obj.required.is_none() && obj.dependencies.is_none() && obj.items.is_none() && obj.prefix_items.is_none() && obj.contains.is_none() && obj.format.is_none() && obj.enum_.is_none() && obj.const_.is_none() && obj.all_of.is_none() && obj.one_of.is_none() && obj.not.is_none() && obj.if_.is_none() && obj.then_.is_none() && obj.else_.is_none() && obj.r#ref.is_none() && obj.family.is_none(); if is_empty && obj.extensible.is_none() { obj.extensible = Some(true); } Ok(Schema { obj, always_fail: false, }) } } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum SchemaTypeOrArray { Single(String), Multiple(Vec), } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Action { #[serde(skip_serializing_if = "Option::is_none")] pub navigate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub punc: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum Dependency { Props(Vec), Schema(Arc), }