implemented type match checking for types on schema id instead of type const

This commit is contained in:
2025-09-12 01:02:32 -04:00
parent 704770051c
commit bb84f9aa73
3 changed files with 163 additions and 9 deletions

View File

@ -9,7 +9,7 @@ use std::borrow::Cow;
use std::collections::hash_map::Entry;
use std::{collections::HashMap, sync::RwLock};
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq)]
enum SchemaType {
Enum,
Type,
@ -20,6 +20,7 @@ enum SchemaType {
struct BoonCache {
schemas: Schemas,
id_to_index: HashMap<String, SchemaIndex>,
id_to_type: HashMap<String, SchemaType>,
}
// Structure to hold error information without lifetimes
@ -35,6 +36,7 @@ lazy_static! {
static ref SCHEMA_CACHE: RwLock<BoonCache> = RwLock::new(BoonCache {
schemas: Schemas::new(),
id_to_index: HashMap::new(),
id_to_type: HashMap::new(),
});
}
@ -49,6 +51,7 @@ fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
*cache = BoonCache {
schemas: Schemas::new(),
id_to_index: HashMap::new(),
id_to_type: HashMap::new(),
};
// Create the boon compiler and enable format assertions
@ -85,6 +88,7 @@ fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
}));
} else {
all_schema_ids.push(schema_id.to_string());
cache.id_to_type.insert(schema_id.to_string(), SchemaType::Enum);
}
}
}
@ -119,6 +123,7 @@ fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
}));
} else {
all_schema_ids.push(schema_id.to_string());
cache.id_to_type.insert(schema_id.to_string(), SchemaType::Type);
}
}
}
@ -166,6 +171,7 @@ fn cache_json_schemas(enums: JsonB, types: JsonB, puncs: JsonB) -> JsonB {
}));
} else {
all_schema_ids.push(schema_id.to_string());
cache.id_to_type.insert(schema_id.to_string(), schema_type_for_def);
}
}
}
@ -322,6 +328,47 @@ fn apply_strict_validation_recursive(schema: &mut Value, inside_conditional: boo
}
}
fn validate_type_against_schema_id(instance: &Value, schema_id: &str) -> JsonB {
let expected_type = schema_id.split('.').next().unwrap_or(schema_id);
if let Some(actual_type) = instance.get("type").and_then(|v| v.as_str()) {
if actual_type == expected_type {
return JsonB(json!({ "response": "success" }));
}
}
// If we reach here, validation failed. Now we build the specific error.
let (message, cause, context) =
if let Some(actual_type) = instance.get("type").and_then(|v| v.as_str()) {
// This handles the case where the type is a string but doesn't match.
(
format!("Instance type '{}' does not match expected type '{}' derived from schema ID", actual_type, expected_type),
json!({ "expected": expected_type, "actual": actual_type }),
json!(actual_type)
)
} else {
// This handles the case where 'type' is missing or not a string.
(
"Instance 'type' property is missing or not a string".to_string(),
json!("The 'type' property must be a string and is required for this validation."),
instance.get("type").unwrap_or(&Value::Null).clone()
)
};
JsonB(json!({
"errors": [{
"code": "TYPE_MISMATCH",
"message": message,
"details": {
"path": "/type",
"context": context,
"cause": cause,
"schema": schema_id
}
}]
}))
}
#[pg_extern(strict, parallel_safe)]
fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
let cache = SCHEMA_CACHE.read().unwrap();
@ -340,7 +387,16 @@ fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
Some(sch_index) => {
let instance_value: Value = instance.0;
match cache.schemas.validate(&instance_value, *sch_index) {
Ok(_) => JsonB(json!({ "response": "success" })),
Ok(_) => {
// After standard validation, perform custom type check if it's a Type schema
if let Some(&schema_type) = cache.id_to_type.get(schema_id) {
if schema_type == SchemaType::Type {
return validate_type_against_schema_id(&instance_value, schema_id);
}
}
// For non-Type schemas, or if type not found (shouldn't happen), success.
JsonB(json!({ "response": "success" }))
}
Err(validation_error) => {
let mut error_list = Vec::new();
collect_errors(&validation_error, &mut error_list);
@ -961,6 +1017,7 @@ fn clear_json_schemas() -> JsonB {
*cache = BoonCache {
schemas: Schemas::new(),
id_to_index: HashMap::new(),
id_to_type: HashMap::new(),
};
JsonB(json!({ "response": "success" }))
}