use crate::database::schema::Schema; use std::sync::Arc; impl Schema { #[allow(unused_variables)] pub(crate) fn validate_identifier( id: &str, field_name: &str, root_id: &str, path: &str, errors: &mut Vec, ) { #[cfg(not(test))] for c in id.chars() { if !c.is_ascii_lowercase() && !c.is_ascii_digit() && c != '_' && c != '.' && c != '$' { errors.push(crate::drop::Error { code: "INVALID_IDENTIFIER".to_string(), message: format!( "Invalid character '{}' in JSON Schema '{}' property: '{}'. Identifiers must exclusively contain [a-z0-9_.$]", c, field_name, id ), details: crate::drop::ErrorDetails { path: Some(path.to_string()), schema: Some(root_id.to_string()), ..Default::default() }, }); return; } } } pub fn collect_schemas( schema_arc: &Arc, root_id: &str, path: String, to_insert: &mut Vec<(String, Arc)>, errors: &mut Vec, ) { if let Some(crate::database::object::SchemaTypeOrArray::Single(t)) = &schema_arc.obj.type_ { if t == "array" { if let Some(items) = &schema_arc.obj.items { if let Some(crate::database::object::SchemaTypeOrArray::Single(it)) = &items.obj.type_ { if !crate::database::object::is_primitive_type(it) { if items.obj.properties.is_some() || items.obj.cases.is_some() { to_insert.push((path.clone(), Arc::clone(schema_arc))); } } } } } else if !crate::database::object::is_primitive_type(t) { Self::validate_identifier(t, "type", root_id, &path, errors); // Is this an explicit inline ad-hoc composition? if schema_arc.obj.properties.is_some() || schema_arc.obj.cases.is_some() { to_insert.push((path.clone(), Arc::clone(schema_arc))); } } } if let Some(family) = &schema_arc.obj.family { Self::validate_identifier(family, "family", root_id, &path, errors); } Self::collect_child_schemas(schema_arc, root_id, path, to_insert, errors); } pub fn collect_child_schemas( schema_arc: &Arc, root_id: &str, path: String, to_insert: &mut Vec<(String, Arc)>, errors: &mut Vec, ) { if let Some(props) = &schema_arc.obj.properties { for (k, v) in props.iter() { let next_path = format!("{}/{}", path, k); Self::collect_schemas(v, root_id, next_path, to_insert, errors); } } if let Some(pattern_props) = &schema_arc.obj.pattern_properties { for (k, v) in pattern_props.iter() { let next_path = format!("{}/{}", path, k); Self::collect_schemas(v, root_id, next_path, to_insert, errors); } } let mut map_arr = |arr: &Vec>, sub: &str| { for (i, v) in arr.iter().enumerate() { Self::collect_schemas( v, root_id, format!("{}/{}/{}", path, sub, i), to_insert, errors, ); } }; if let Some(arr) = &schema_arc.obj.prefix_items { map_arr(arr, "prefixItems"); } if let Some(arr) = &schema_arc.obj.one_of { map_arr(arr, "oneOf"); } let mut map_opt = |opt: &Option>, pass_path: bool, sub: &str| { if let Some(v) = opt { if pass_path { // Arrays explicitly push their wrapper natively. // 'items' becomes a transparent conduit, bypassing self-promotion and skipping the '/items' suffix. Self::collect_child_schemas(v, root_id, path.clone(), to_insert, errors); } else { Self::collect_child_schemas(v, root_id, format!("{}/{}", path, sub), to_insert, errors); } } }; map_opt( &schema_arc.obj.additional_properties, false, "additionalProperties", ); map_opt(&schema_arc.obj.items, true, "items"); map_opt(&schema_arc.obj.not, false, "not"); map_opt(&schema_arc.obj.contains, false, "contains"); map_opt(&schema_arc.obj.property_names, false, "propertyNames"); if let Some(cases) = &schema_arc.obj.cases { for (i, c) in cases.iter().enumerate() { if let Some(when) = &c.when { Self::collect_schemas( when, root_id, format!("{}/cases/{}/when", path, i), to_insert, errors, ); } if let Some(then) = &c.then { Self::collect_schemas( then, root_id, format!("{}/cases/{}/then", path, i), to_insert, errors, ); } if let Some(else_) = &c.else_ { Self::collect_schemas( else_, root_id, format!("{}/cases/{}/else", path, i), to_insert, errors, ); } } } } }