387 lines
12 KiB
Rust
387 lines
12 KiB
Rust
use crate::schema::Schema;
|
|
use regex::Regex;
|
|
use serde_json::Value;
|
|
// use std::collections::HashMap;
|
|
use std::error::Error;
|
|
use std::sync::Arc;
|
|
|
|
/// Represents a compiled format validator
|
|
#[derive(Debug, Clone)]
|
|
pub enum CompiledFormat {
|
|
/// A simple function pointer validator
|
|
Func(fn(&Value) -> Result<(), Box<dyn Error + Send + Sync>>),
|
|
/// A regex-based validator
|
|
Regex(Regex),
|
|
}
|
|
|
|
/// A wrapper for compiled regex patterns
|
|
#[derive(Debug, Clone)]
|
|
pub struct CompiledRegex(pub Regex);
|
|
|
|
/// The Compiler is responsible for pre-calculating high-cost schema operations
|
|
pub struct Compiler;
|
|
|
|
impl Compiler {
|
|
/// Internal: Compiles formats and regexes in-place
|
|
fn compile_formats_and_regexes(schema: &mut Schema) {
|
|
// 1. Compile Format
|
|
if let Some(format_str) = &schema.format {
|
|
if let Some(fmt) = crate::formats::FORMATS.get(format_str.as_str()) {
|
|
schema.compiled_format = Some(CompiledFormat::Func(fmt.func));
|
|
}
|
|
}
|
|
|
|
// 2. Compile Pattern (regex)
|
|
if let Some(pattern_str) = &schema.pattern {
|
|
if let Ok(re) = Regex::new(pattern_str) {
|
|
schema.compiled_pattern = Some(CompiledRegex(re));
|
|
}
|
|
}
|
|
|
|
// 2.5 Compile Pattern Properties
|
|
if let Some(pp) = &schema.pattern_properties {
|
|
let mut compiled_pp = Vec::new();
|
|
for (pattern, sub_schema) in pp {
|
|
if let Ok(re) = Regex::new(pattern) {
|
|
compiled_pp.push((CompiledRegex(re), sub_schema.clone()));
|
|
} else {
|
|
eprintln!(
|
|
"Invalid patternProperty regex in schema (compile time): {}",
|
|
pattern
|
|
);
|
|
}
|
|
}
|
|
if !compiled_pp.is_empty() {
|
|
schema.compiled_pattern_properties = Some(compiled_pp);
|
|
}
|
|
}
|
|
|
|
// 3. Recurse
|
|
Self::compile_recursive(schema);
|
|
}
|
|
|
|
fn normalize_dependencies(schema: &mut Schema) {
|
|
if let Some(deps) = schema.dependencies.take() {
|
|
for (key, dep) in deps {
|
|
match dep {
|
|
crate::schema::Dependency::Props(props) => {
|
|
schema
|
|
.dependent_required
|
|
.get_or_insert_with(std::collections::BTreeMap::new)
|
|
.insert(key, props);
|
|
}
|
|
crate::schema::Dependency::Schema(sub_schema) => {
|
|
schema
|
|
.dependent_schemas
|
|
.get_or_insert_with(std::collections::BTreeMap::new)
|
|
.insert(key, sub_schema);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn compile_recursive(schema: &mut Schema) {
|
|
Self::normalize_dependencies(schema);
|
|
|
|
// Compile self
|
|
if let Some(format_str) = &schema.format {
|
|
if let Some(fmt) = crate::formats::FORMATS.get(format_str.as_str()) {
|
|
schema.compiled_format = Some(CompiledFormat::Func(fmt.func));
|
|
}
|
|
}
|
|
if let Some(pattern_str) = &schema.pattern {
|
|
if let Ok(re) = Regex::new(pattern_str) {
|
|
schema.compiled_pattern = Some(CompiledRegex(re));
|
|
}
|
|
}
|
|
|
|
// Recurse
|
|
|
|
if let Some(defs) = &mut schema.definitions {
|
|
for s in defs.values_mut() {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
if let Some(defs) = &mut schema.defs {
|
|
for s in defs.values_mut() {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
if let Some(props) = &mut schema.properties {
|
|
for s in props.values_mut() {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
|
|
// ... Recurse logic ...
|
|
if let Some(items) = &mut schema.items {
|
|
Self::compile_recursive(Arc::make_mut(items));
|
|
}
|
|
if let Some(prefix_items) = &mut schema.prefix_items {
|
|
for s in prefix_items {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
if let Some(not) = &mut schema.not {
|
|
Self::compile_recursive(Arc::make_mut(not));
|
|
}
|
|
if let Some(all_of) = &mut schema.all_of {
|
|
for s in all_of {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
if let Some(any_of) = &mut schema.any_of {
|
|
for s in any_of {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
if let Some(one_of) = &mut schema.one_of {
|
|
for s in one_of {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
if let Some(s) = &mut schema.if_ {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
if let Some(s) = &mut schema.then_ {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
if let Some(s) = &mut schema.else_ {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
|
|
if let Some(ds) = &mut schema.dependent_schemas {
|
|
for s in ds.values_mut() {
|
|
Self::compile_recursive(Arc::make_mut(s));
|
|
}
|
|
}
|
|
if let Some(pn) = &mut schema.property_names {
|
|
Self::compile_recursive(Arc::make_mut(pn));
|
|
}
|
|
}
|
|
|
|
/// Recursively traverses the schema tree to build the local registry index.
|
|
fn compile_index(
|
|
schema: &Arc<Schema>,
|
|
registry: &mut crate::registry::Registry,
|
|
parent_base: Option<String>,
|
|
pointer: json_pointer::JsonPointer<String, Vec<String>>,
|
|
) {
|
|
// 1. Index using Parent Base (Path from Parent)
|
|
if let Some(base) = &parent_base {
|
|
// We use the pointer's string representation (e.g., "/definitions/foo")
|
|
// and append it to the base.
|
|
let fragment = pointer.to_string();
|
|
let ptr_uri = if fragment.is_empty() {
|
|
base.clone()
|
|
} else {
|
|
format!("{}#{}", base, fragment)
|
|
};
|
|
registry.insert(ptr_uri, schema.clone());
|
|
}
|
|
|
|
// 2. Determine Current Scope... (unchanged logic)
|
|
let mut current_base = parent_base.clone();
|
|
let mut child_pointer = pointer.clone();
|
|
|
|
if let Some(id) = &schema.obj.id {
|
|
let mut new_base = None;
|
|
if let Ok(_) = url::Url::parse(id) {
|
|
new_base = Some(id.clone());
|
|
} else if let Some(base) = ¤t_base {
|
|
if let Ok(base_url) = url::Url::parse(base) {
|
|
if let Ok(joined) = base_url.join(id) {
|
|
new_base = Some(joined.to_string());
|
|
}
|
|
}
|
|
} else {
|
|
new_base = Some(id.clone());
|
|
}
|
|
|
|
if let Some(base) = new_base {
|
|
// println!("DEBUG: Compiling index for path: {}", base); // Added println
|
|
registry.insert(base.clone(), schema.clone());
|
|
current_base = Some(base);
|
|
child_pointer = json_pointer::JsonPointer::new(vec![]); // Reset
|
|
}
|
|
}
|
|
|
|
// 3. Index by Anchor
|
|
if let Some(anchor) = &schema.obj.anchor {
|
|
if let Some(base) = ¤t_base {
|
|
let anchor_uri = format!("{}#{}", base, anchor);
|
|
registry.insert(anchor_uri, schema.clone());
|
|
}
|
|
}
|
|
// Index by Dynamic Anchor
|
|
if let Some(d_anchor) = &schema.obj.dynamic_anchor {
|
|
if let Some(base) = ¤t_base {
|
|
let anchor_uri = format!("{}#{}", base, d_anchor);
|
|
registry.insert(anchor_uri, schema.clone());
|
|
}
|
|
}
|
|
|
|
// 4. Recurse (unchanged logic structure, just passing registry)
|
|
if let Some(defs) = schema.defs.as_ref().or(schema.definitions.as_ref()) {
|
|
let segment = if schema.defs.is_some() {
|
|
"$defs"
|
|
} else {
|
|
"definitions"
|
|
};
|
|
for (key, sub_schema) in defs {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push(segment.to_string());
|
|
let decoded_key = percent_encoding::percent_decode_str(key).decode_utf8_lossy();
|
|
sub.push(decoded_key.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
|
|
if let Some(props) = &schema.properties {
|
|
for (key, sub_schema) in props {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("properties".to_string());
|
|
sub.push(key.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
|
|
if let Some(items) = &schema.items {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("items".to_string());
|
|
Self::compile_index(items, registry, current_base.clone(), sub);
|
|
}
|
|
|
|
if let Some(prefix_items) = &schema.prefix_items {
|
|
for (i, sub_schema) in prefix_items.iter().enumerate() {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("prefixItems".to_string());
|
|
sub.push(i.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
|
|
if let Some(all_of) = &schema.all_of {
|
|
for (i, sub_schema) in all_of.iter().enumerate() {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("allOf".to_string());
|
|
sub.push(i.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
if let Some(any_of) = &schema.any_of {
|
|
for (i, sub_schema) in any_of.iter().enumerate() {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("anyOf".to_string());
|
|
sub.push(i.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
if let Some(one_of) = &schema.one_of {
|
|
for (i, sub_schema) in one_of.iter().enumerate() {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("oneOf".to_string());
|
|
sub.push(i.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
|
|
if let Some(not) = &schema.not {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("not".to_string());
|
|
Self::compile_index(not, registry, current_base.clone(), sub);
|
|
}
|
|
if let Some(if_) = &schema.if_ {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("if".to_string());
|
|
Self::compile_index(if_, registry, current_base.clone(), sub);
|
|
}
|
|
if let Some(then_) = &schema.then_ {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("then".to_string());
|
|
Self::compile_index(then_, registry, current_base.clone(), sub);
|
|
}
|
|
if let Some(else_) = &schema.else_ {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("else".to_string());
|
|
Self::compile_index(else_, registry, current_base.clone(), sub);
|
|
}
|
|
if let Some(deps) = &schema.dependent_schemas {
|
|
for (key, sub_schema) in deps {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("dependentSchemas".to_string());
|
|
sub.push(key.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
if let Some(pp) = &schema.pattern_properties {
|
|
for (key, sub_schema) in pp {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("patternProperties".to_string());
|
|
sub.push(key.to_string());
|
|
Self::compile_index(sub_schema, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
if let Some(contains) = &schema.contains {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("contains".to_string());
|
|
Self::compile_index(contains, registry, current_base.clone(), sub);
|
|
}
|
|
if let Some(property_names) = &schema.property_names {
|
|
let mut sub = child_pointer.clone();
|
|
sub.push("propertyNames".to_string());
|
|
Self::compile_index(property_names, registry, current_base.clone(), sub);
|
|
}
|
|
}
|
|
|
|
pub fn compile(mut root_schema: Schema, root_id: Option<String>) -> Arc<Schema> {
|
|
// 1. Compile in-place (formats/regexes/normalization)
|
|
Self::compile_formats_and_regexes(&mut root_schema);
|
|
|
|
// Apply root_id override if schema ID is missing
|
|
if let Some(rid) = &root_id {
|
|
if root_schema.obj.id.is_none() {
|
|
root_schema.obj.id = Some(rid.clone());
|
|
}
|
|
}
|
|
|
|
// 2. Build ID/Pointer Index
|
|
let mut registry = crate::registry::Registry::new();
|
|
|
|
// We need a temporary Arc to satisfy compile_index recursion
|
|
// But we are modifying root_schema.
|
|
// This is tricky. compile_index takes &Arc<Schema>.
|
|
// We should build the index first, THEN attach it.
|
|
|
|
let root = Arc::new(root_schema);
|
|
|
|
// Default base_uri to ""
|
|
let base_uri = root_id
|
|
.clone()
|
|
.or_else(|| root.obj.id.clone())
|
|
.or(Some("".to_string()));
|
|
|
|
Self::compile_index(
|
|
&root,
|
|
&mut registry,
|
|
base_uri,
|
|
json_pointer::JsonPointer::new(vec![]),
|
|
);
|
|
|
|
// Also ensure root id is indexed if present
|
|
if let Some(rid) = root_id {
|
|
registry.insert(rid, root.clone());
|
|
}
|
|
|
|
// Now we need to attach this registry to the root schema.
|
|
// Since root is an Arc, we might need to recreate it if we can't mutate.
|
|
// Schema struct modifications require &mut.
|
|
|
|
let mut final_schema = Arc::try_unwrap(root).unwrap_or_else(|arc| (*arc).clone());
|
|
final_schema.obj.compiled_registry = Some(Arc::new(registry));
|
|
|
|
Arc::new(final_schema)
|
|
}
|
|
}
|