164 lines
4.9 KiB
Rust
164 lines
4.9 KiB
Rust
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<crate::drop::Error>,
|
|
) {
|
|
#[cfg(not(test))]
|
|
for c in id.chars() {
|
|
if !c.is_ascii_lowercase() && !c.is_ascii_digit() && 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<Schema>,
|
|
root_id: &str,
|
|
path: String,
|
|
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
|
errors: &mut Vec<crate::drop::Error>,
|
|
) {
|
|
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<Schema>,
|
|
root_id: &str,
|
|
path: String,
|
|
to_insert: &mut Vec<(String, Arc<Schema>)>,
|
|
errors: &mut Vec<crate::drop::Error>,
|
|
) {
|
|
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<Arc<Schema>>, 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<Arc<Schema>>, 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,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|