branch error filtering
This commit is contained in:
38
src/lib.rs
38
src/lib.rs
@ -91,13 +91,16 @@ fn validate_json_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||
match cache.schemas.validate(&instance_value, *sch_index) {
|
||||
Ok(_) => JsonB(json!({ "success": true })),
|
||||
Err(validation_error) => {
|
||||
// Directly use the result of format_validation_error
|
||||
// which now includes the top-level success indicator and flat error list
|
||||
let mut error_list = Vec::new();
|
||||
collect_leaf_errors(&validation_error, &mut error_list);
|
||||
// Collect all leaf errors first
|
||||
let mut raw_error_list = Vec::new();
|
||||
collect_leaf_errors(&validation_error, &mut raw_error_list);
|
||||
|
||||
// Filter the errors (e.g., deduplicate by instance_path)
|
||||
let filtered_error_list = filter_boon_errors(raw_error_list);
|
||||
|
||||
JsonB(json!({
|
||||
"success": false,
|
||||
"error": error_list // Flat list of specific errors
|
||||
"error": filtered_error_list // Return the filtered list
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -127,6 +130,31 @@ fn collect_leaf_errors(error: &ValidationError, errors_list: &mut Vec<Value>) {
|
||||
}
|
||||
}
|
||||
|
||||
// Filters collected errors, e.g., deduplicating by instance_path
|
||||
fn filter_boon_errors(raw_errors: Vec<Value>) -> Vec<Value> {
|
||||
use std::collections::HashMap;
|
||||
use std::collections::hash_map::Entry;
|
||||
|
||||
// Use a HashMap to keep only the first error for each instance_path
|
||||
let mut unique_errors: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
for error_value in raw_errors {
|
||||
if let Some(instance_path_value) = error_value.get("instance_path") {
|
||||
if let Some(instance_path_str) = instance_path_value.as_str() {
|
||||
// Use Entry API to insert only if the key is not present
|
||||
if let Entry::Vacant(entry) = unique_errors.entry(instance_path_str.to_string()) {
|
||||
entry.insert(error_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If error doesn't have instance_path or it's not a string, we might ignore it or handle differently.
|
||||
// For now, we implicitly ignore errors without a valid string instance_path for deduplication.
|
||||
}
|
||||
|
||||
// Collect the unique errors from the map values
|
||||
unique_errors.into_values().collect()
|
||||
}
|
||||
|
||||
#[pg_extern(strict, parallel_safe)]
|
||||
fn json_schema_cached(schema_id: &str) -> bool {
|
||||
let cache = SCHEMA_CACHE.read().unwrap();
|
||||
|
||||
39
src/tests.rs
39
src/tests.rs
@ -333,32 +333,47 @@ fn test_validate_json_schema_oneof_validation_errors() {
|
||||
// --- Test case 1: Fails string maxLength (in branch 0) AND missing number_prop (in branch 1) ---
|
||||
let invalid_string_instance = json!({ "string_prop": "toolongstring" });
|
||||
let result_invalid_string = validate_json_schema(schema_id, jsonb(invalid_string_instance));
|
||||
// Expect 2 leaf errors: one for maxLength (branch 0), one for missing prop (branch 1)
|
||||
// Check the first error message reported by boon (maxLength).
|
||||
assert_failure_with_json!(result_invalid_string, 2, "length must be <=5", "Validation with invalid string length should have 2 leaf errors");
|
||||
// Expect 2 leaf errors. Check count only with the macro.
|
||||
assert_failure_with_json!(result_invalid_string, 2);
|
||||
// Explicitly check that both expected errors are present, ignoring order
|
||||
let errors_string = result_invalid_string.0["error"].as_array().expect("Expected error array for invalid string");
|
||||
assert!(errors_string.iter().any(|e| e["instance_path"] == "/string_prop" && e["message"].as_str().unwrap().contains("length must be <=5")), "Missing maxLength error");
|
||||
assert!(errors_string.iter().any(|e| e["instance_path"] == "" && e["message"].as_str().unwrap().contains("missing properties 'number_prop'")), "Missing number_prop required error");
|
||||
|
||||
// --- Test case 2: Fails number minimum (in branch 1) AND missing string_prop (in branch 0) ---
|
||||
let invalid_number_instance = json!({ "number_prop": 5 });
|
||||
let result_invalid_number = validate_json_schema(schema_id, jsonb(invalid_number_instance));
|
||||
// Expect 2 leaf errors: one for minimum (branch 1), one for missing prop (branch 0)
|
||||
// Check the first error message reported by boon (missing prop).
|
||||
assert_failure_with_json!(result_invalid_number, 2, "missing properties 'string_prop'", "Validation with invalid number should have 2 leaf errors");
|
||||
// Expect 2 leaf errors. Check count only with the macro.
|
||||
assert_failure_with_json!(result_invalid_number, 2);
|
||||
// Explicitly check that both expected errors are present, ignoring order
|
||||
let errors_number = result_invalid_number.0["error"].as_array().expect("Expected error array for invalid number");
|
||||
assert!(errors_number.iter().any(|e| e["instance_path"] == "/number_prop" && e["message"].as_str().unwrap().contains("must be >=10")), "Missing minimum error");
|
||||
assert!(errors_number.iter().any(|e| e["instance_path"] == "" && e["message"].as_str().unwrap().contains("missing properties 'string_prop'")), "Missing string_prop required error");
|
||||
|
||||
// --- Test case 3: Fails type check (not object) for both branches ---
|
||||
// Input: boolean, expected object for both branches
|
||||
let invalid_bool_instance = json!(true); // Not an object
|
||||
let result_invalid_bool = validate_json_schema(schema_id, jsonb(invalid_bool_instance));
|
||||
// Expect 2 leaf errors, one "Type" error for each branch
|
||||
// Check the first error reported by boon (want object).
|
||||
assert_failure_with_json!(result_invalid_bool, 2, "want object", "Validation with invalid bool should have 2 leaf errors");
|
||||
// Expect only 1 leaf error after filtering, as both original errors have instance_path ""
|
||||
assert_failure_with_json!(result_invalid_bool, 1);
|
||||
// Explicitly check that the single remaining error is the type error for the root instance path
|
||||
let errors_bool = result_invalid_bool.0["error"].as_array().expect("Expected error array for invalid bool");
|
||||
assert_eq!(errors_bool.iter().filter(|e| e["instance_path"] == "" && e["message"].as_str().unwrap().contains("want object")).count(), 1, "Expected one 'want object' error at root after filtering");
|
||||
|
||||
// --- Test case 4: Fails missing required for both branches ---
|
||||
// Input: empty object, expected string_prop (branch 0) OR number_prop (branch 1)
|
||||
let invalid_empty_obj = json!({});
|
||||
let result_empty_obj = validate_json_schema(schema_id, jsonb(invalid_empty_obj));
|
||||
// Expect 2 leaf errors: one required error for branch 0, one required error for branch 1
|
||||
// Check the first error reported by boon (missing string_prop).
|
||||
assert_failure_with_json!(result_empty_obj, 2, "missing properties 'string_prop'", "Validation with empty object should have 2 leaf errors");
|
||||
// Expect only 1 leaf error after filtering, as both original errors have instance_path ""
|
||||
assert_failure_with_json!(result_empty_obj, 1);
|
||||
// Explicitly check that the single remaining error is one of the expected missing properties errors
|
||||
let errors_empty = result_empty_obj.0["error"].as_array().expect("Expected error array for empty object");
|
||||
assert_eq!(errors_empty.len(), 1, "Expected exactly one error after filtering empty object");
|
||||
let the_error = &errors_empty[0];
|
||||
assert_eq!(the_error["instance_path"], "", "Expected instance_path to be empty string");
|
||||
let message = the_error["message"].as_str().unwrap();
|
||||
assert!(message.contains("missing properties 'string_prop'") || message.contains("missing properties 'number_prop'"),
|
||||
"Error message should indicate missing string_prop or number_prop, got: {}", message);
|
||||
}
|
||||
|
||||
#[pg_test]
|
||||
|
||||
Reference in New Issue
Block a user