bringing back type constants for validation via new overrides vocabulary
This commit is contained in:
152
src/lib.rs
152
src/lib.rs
@ -189,147 +189,6 @@ fn compile_all_schemas(
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_and_validate_refs(
|
||||
instance: &Value,
|
||||
schema: &Value,
|
||||
cache: &std::sync::RwLockReadGuard<Cache>,
|
||||
path_parts: &mut Vec<String>,
|
||||
type_validated: bool,
|
||||
top_level_id: Option<&str>,
|
||||
errors: &mut Vec<Value>,
|
||||
) {
|
||||
if let Some(ref_url) = schema.get("$ref").and_then(|v| v.as_str()) {
|
||||
if let Some(s) = cache.map.get(ref_url) {
|
||||
let mut new_type_validated = type_validated;
|
||||
if !type_validated && s.t == SchemaType::Type {
|
||||
let id_to_use = top_level_id.unwrap_or(ref_url);
|
||||
let expected_type = id_to_use.split('.').next().unwrap_or(id_to_use);
|
||||
if let Some(actual_type) = instance.get("type").and_then(|v| v.as_str()) {
|
||||
if actual_type == expected_type {
|
||||
new_type_validated = true;
|
||||
} else {
|
||||
path_parts.push("type".to_string());
|
||||
let path = format!("/{}", path_parts.join("/"));
|
||||
path_parts.pop();
|
||||
errors.push(json!({
|
||||
"code": "TYPE_MISMATCH",
|
||||
"message": format!("Instance type '{}' does not match expected type '{}' derived from schema $ref", actual_type, expected_type),
|
||||
"details": { "path": path, "context": instance, "cause": { "expected": expected_type, "actual": actual_type }, "schema": ref_url }
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if top_level_id.is_some() {
|
||||
let path = if path_parts.is_empty() { "".to_string() } else { format!("/{}", path_parts.join("/")) };
|
||||
errors.push(json!({
|
||||
"code": "TYPE_MISMATCH",
|
||||
"message": "Instance is missing 'type' property required for schema validation",
|
||||
"details": { "path": path, "context": instance, "cause": { "expected": expected_type }, "schema": ref_url }
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
walk_and_validate_refs(instance, &s.value, cache, path_parts, new_type_validated, None, errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(properties) = schema.get("properties").and_then(|v| v.as_object()) {
|
||||
for (prop_name, prop_schema) in properties {
|
||||
if let Some(prop_value) = instance.get(prop_name) {
|
||||
path_parts.push(prop_name.clone());
|
||||
walk_and_validate_refs(prop_value, prop_schema, cache, path_parts, type_validated, None, errors);
|
||||
path_parts.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(items_schema) = schema.get("items") {
|
||||
if let Some(instance_array) = instance.as_array() {
|
||||
for (i, item) in instance_array.iter().enumerate() {
|
||||
path_parts.push(i.to_string());
|
||||
walk_and_validate_refs(item, items_schema, cache, path_parts, false, None, errors);
|
||||
path_parts.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(all_of_array) = schema.get("allOf").and_then(|v| v.as_array()) {
|
||||
for sub_schema in all_of_array {
|
||||
walk_and_validate_refs(instance, sub_schema, cache, path_parts, type_validated, None, errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(any_of_array) = schema.get("anyOf").and_then(|v| v.as_array()) {
|
||||
for sub_schema in any_of_array {
|
||||
walk_and_validate_refs(instance, sub_schema, cache, path_parts, type_validated, None, errors);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(one_of_array) = schema.get("oneOf").and_then(|v| v.as_array()) {
|
||||
let is_clean_ref_union = one_of_array.iter().all(|s| s.get("$ref").is_some());
|
||||
|
||||
if is_clean_ref_union {
|
||||
if let Some(actual_type) = instance.get("type").and_then(|v| v.as_str()) {
|
||||
let mut match_found = false;
|
||||
for sub_schema in one_of_array {
|
||||
if let Some(ref_url) = sub_schema.get("$ref").and_then(|v| v.as_str()) {
|
||||
if ref_url == actual_type {
|
||||
walk_and_validate_refs(instance, sub_schema, cache, path_parts, type_validated, None, errors);
|
||||
match_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !match_found {
|
||||
let path = format!("/{}", path_parts.join("/"));
|
||||
errors.push(json!({
|
||||
"code": "TYPE_MISMATCH_IN_UNION",
|
||||
"message": format!("Instance type '{}' does not match any of the allowed types in the union", actual_type),
|
||||
"details": {
|
||||
"path": path,
|
||||
"context": instance,
|
||||
"cause": {
|
||||
"actual": actual_type,
|
||||
"expected": one_of_array.iter()
|
||||
.filter_map(|s| s.get("$ref").and_then(|r| r.as_str()))
|
||||
.collect::<Vec<_>>()
|
||||
},
|
||||
"schema": top_level_id.unwrap_or("")
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
let path = format!("/{}", path_parts.join("/"));
|
||||
errors.push(json!({
|
||||
"code": "TYPE_REQUIRED_FOR_UNION",
|
||||
"message": "Instance is missing 'type' property required for union (oneOf) validation",
|
||||
"details": { "path": path, "context": instance, "schema": top_level_id.unwrap_or("") }
|
||||
}));
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
for sub_schema in one_of_array {
|
||||
walk_and_validate_refs(instance, sub_schema, cache, path_parts, type_validated, None, errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(if_schema) = schema.get("if") {
|
||||
walk_and_validate_refs(instance, if_schema, cache, path_parts, type_validated, None, errors);
|
||||
}
|
||||
|
||||
if let Some(then_schema) = schema.get("then") {
|
||||
walk_and_validate_refs(instance, then_schema, cache, path_parts, type_validated, None, errors);
|
||||
}
|
||||
|
||||
if let Some(else_schema) = schema.get("else") {
|
||||
walk_and_validate_refs(instance, else_schema, cache, path_parts, type_validated, None, errors);
|
||||
}
|
||||
|
||||
if let Some(not_schema) = schema.get("not") {
|
||||
walk_and_validate_refs(instance, not_schema, cache, path_parts, type_validated, None, errors);
|
||||
}
|
||||
}
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
let cache = SCHEMA_CACHE.read().unwrap();
|
||||
@ -353,18 +212,7 @@ fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
|
||||
match cache.schemas.validate(&instance_value, schema.index, options) {
|
||||
Ok(_) => {
|
||||
let mut custom_errors = Vec::new();
|
||||
if schema.t == SchemaType::Type || schema.t == SchemaType::PublicPunc || schema.t == SchemaType::PrivatePunc {
|
||||
let mut path_parts = vec![];
|
||||
let top_level_id = if schema.t == SchemaType::Type { Some(schema_id) } else { None };
|
||||
walk_and_validate_refs(&instance_value, &schema.value, &cache, &mut path_parts, false, top_level_id, &mut custom_errors);
|
||||
}
|
||||
|
||||
if custom_errors.is_empty() {
|
||||
JsonB(json!({ "response": "success" }))
|
||||
} else {
|
||||
JsonB(json!({ "errors": custom_errors }))
|
||||
}
|
||||
}
|
||||
Err(validation_error) => {
|
||||
let mut error_list = Vec::new();
|
||||
|
||||
Reference in New Issue
Block a user