slight improvements to error messaging

This commit is contained in:
2025-06-06 14:25:13 -04:00
parent d8a9a7b76b
commit fb333c6cbb
2 changed files with 14 additions and 18 deletions

View File

@ -146,7 +146,7 @@ fn format_drop_errors(raw_errors: Vec<(String, String, String)>, instance: &Valu
// 2. Deduplicate by instance_path and format as DropError // 2. Deduplicate by instance_path and format as DropError
let mut unique_errors: HashMap<String, Value> = HashMap::new(); let mut unique_errors: HashMap<String, Value> = HashMap::new();
for (instance_path, schema_path, message) in plausible_errors { for (instance_path, _schema_path, message) in plausible_errors {
if let Entry::Vacant(entry) = unique_errors.entry(instance_path.clone()) { if let Entry::Vacant(entry) = unique_errors.entry(instance_path.clone()) {
// Convert message to error code and make it human readable // Convert message to error code and make it human readable
let (code, human_message) = enhance_error_message(&message); let (code, human_message) = enhance_error_message(&message);
@ -158,11 +158,8 @@ fn format_drop_errors(raw_errors: Vec<(String, String, String)>, instance: &Valu
"code": code, "code": code,
"message": human_message, "message": human_message,
"details": { "details": {
"path": schema_path, "path": instance_path,
"context": json!({ "context": failing_value,
"instance_path": instance_path,
"failing_value": failing_value
}),
"cause": message // Original error message "cause": message // Original error message
} }
})); }));

View File

@ -153,16 +153,15 @@ fn test_cache_and_validate_json_schema() {
let invalid_result_type = validate_json_schema(schema_id, jsonb(invalid_instance_type)); let invalid_result_type = validate_json_schema(schema_id, jsonb(invalid_instance_type));
assert_failure_with_json!(invalid_result_type, 1, "Value is below the minimum allowed", "Validation with invalid type should fail."); assert_failure_with_json!(invalid_result_type, 1, "Value is below the minimum allowed", "Validation with invalid type should fail.");
let errors_type = invalid_result_type.0["errors"].as_array().unwrap(); let errors_type = invalid_result_type.0["errors"].as_array().unwrap();
assert_eq!(errors_type[0]["details"]["context"]["instance_path"], "/age"); assert_eq!(errors_type[0]["details"]["path"], "/age");
assert_eq!(errors_type[0]["details"]["path"], "urn:my_schema#/properties/age"); assert_eq!(errors_type[0]["details"]["context"], -5);
assert_eq!(errors_type[0]["code"], "MINIMUM_VIOLATED"); assert_eq!(errors_type[0]["code"], "MINIMUM_VIOLATED");
// Missing field // Missing field
let invalid_result_missing = validate_json_schema(schema_id, jsonb(invalid_instance_missing)); let invalid_result_missing = validate_json_schema(schema_id, jsonb(invalid_instance_missing));
assert_failure_with_json!(invalid_result_missing, 1, "Required field is missing", "Validation with missing field should fail."); assert_failure_with_json!(invalid_result_missing, 1, "Required field is missing", "Validation with missing field should fail.");
let errors_missing = invalid_result_missing.0["errors"].as_array().unwrap(); let errors_missing = invalid_result_missing.0["errors"].as_array().unwrap();
assert_eq!(errors_missing[0]["details"]["context"]["instance_path"], ""); assert_eq!(errors_missing[0]["details"]["path"], "");
assert_eq!(errors_missing[0]["details"]["path"], "urn:my_schema#");
assert_eq!(errors_missing[0]["code"], "REQUIRED_FIELD_MISSING"); assert_eq!(errors_missing[0]["code"], "REQUIRED_FIELD_MISSING");
// Schema not found // Schema not found
@ -208,9 +207,9 @@ fn test_cache_invalid_json_schema() {
// Both errors should have ENUM_VIOLATED code // Both errors should have ENUM_VIOLATED code
assert_eq!(errors_array[0]["code"], "ENUM_VIOLATED"); assert_eq!(errors_array[0]["code"], "ENUM_VIOLATED");
assert_eq!(errors_array[1]["code"], "ENUM_VIOLATED"); assert_eq!(errors_array[1]["code"], "ENUM_VIOLATED");
// Check instance paths are preserved in context // Check instance paths are preserved in path field
let paths: Vec<&str> = errors_array.iter() let paths: Vec<&str> = errors_array.iter()
.map(|e| e["details"]["context"]["instance_path"].as_str().unwrap()) .map(|e| e["details"]["path"].as_str().unwrap())
.collect(); .collect();
assert!(paths.contains(&"/type")); assert!(paths.contains(&"/type"));
assert!(paths.contains(&"/type/0")); assert!(paths.contains(&"/type/0"));
@ -283,11 +282,11 @@ fn test_validate_json_schema_oneof_validation_errors() {
// Explicitly check that both expected errors are present, ignoring order // Explicitly check that both expected errors are present, ignoring order
let errors_string = result_invalid_string.0["errors"].as_array().expect("Expected error array for invalid string"); let errors_string = result_invalid_string.0["errors"].as_array().expect("Expected error array for invalid string");
assert!(errors_string.iter().any(|e| assert!(errors_string.iter().any(|e|
e["details"]["context"]["instance_path"] == "/string_prop" && e["details"]["path"] == "/string_prop" &&
e["code"] == "MAX_LENGTH_VIOLATED" e["code"] == "MAX_LENGTH_VIOLATED"
), "Missing maxLength error"); ), "Missing maxLength error");
assert!(errors_string.iter().any(|e| assert!(errors_string.iter().any(|e|
e["details"]["context"]["instance_path"] == "" && e["details"]["path"] == "" &&
e["code"] == "REQUIRED_FIELD_MISSING" e["code"] == "REQUIRED_FIELD_MISSING"
), "Missing number_prop required error"); ), "Missing number_prop required error");
@ -299,11 +298,11 @@ fn test_validate_json_schema_oneof_validation_errors() {
// Explicitly check that both expected errors are present, ignoring order // Explicitly check that both expected errors are present, ignoring order
let errors_number = result_invalid_number.0["errors"].as_array().expect("Expected error array for invalid number"); let errors_number = result_invalid_number.0["errors"].as_array().expect("Expected error array for invalid number");
assert!(errors_number.iter().any(|e| assert!(errors_number.iter().any(|e|
e["details"]["context"]["instance_path"] == "/number_prop" && e["details"]["path"] == "/number_prop" &&
e["code"] == "MINIMUM_VIOLATED" e["code"] == "MINIMUM_VIOLATED"
), "Missing minimum error"); ), "Missing minimum error");
assert!(errors_number.iter().any(|e| assert!(errors_number.iter().any(|e|
e["details"]["context"]["instance_path"] == "" && e["details"]["path"] == "" &&
e["code"] == "REQUIRED_FIELD_MISSING" e["code"] == "REQUIRED_FIELD_MISSING"
), "Missing string_prop required error"); ), "Missing string_prop required error");
@ -317,7 +316,7 @@ fn test_validate_json_schema_oneof_validation_errors() {
let errors_bool = result_invalid_bool.0["errors"].as_array().expect("Expected error array for invalid bool"); let errors_bool = result_invalid_bool.0["errors"].as_array().expect("Expected error array for invalid bool");
assert_eq!(errors_bool.len(), 1, "Expected exactly one error after deduplication"); assert_eq!(errors_bool.len(), 1, "Expected exactly one error after deduplication");
assert_eq!(errors_bool[0]["code"], "TYPE_MISMATCH"); assert_eq!(errors_bool[0]["code"], "TYPE_MISMATCH");
assert_eq!(errors_bool[0]["details"]["context"]["instance_path"], ""); assert_eq!(errors_bool[0]["details"]["path"], "");
// --- Test case 4: Fails missing required for both branches --- // --- Test case 4: Fails missing required for both branches ---
// Input: empty object, expected string_prop (branch 0) OR number_prop (branch 1) // Input: empty object, expected string_prop (branch 0) OR number_prop (branch 1)
@ -329,7 +328,7 @@ fn test_validate_json_schema_oneof_validation_errors() {
let errors_empty = result_empty_obj.0["errors"].as_array().expect("Expected error array for empty object"); let errors_empty = result_empty_obj.0["errors"].as_array().expect("Expected error array for empty object");
assert_eq!(errors_empty.len(), 1, "Expected exactly one error after filtering empty object"); assert_eq!(errors_empty.len(), 1, "Expected exactly one error after filtering empty object");
assert_eq!(errors_empty[0]["code"], "REQUIRED_FIELD_MISSING"); assert_eq!(errors_empty[0]["code"], "REQUIRED_FIELD_MISSING");
assert_eq!(errors_empty[0]["details"]["context"]["instance_path"], ""); assert_eq!(errors_empty[0]["details"]["path"], "");
// The human message should be generic // The human message should be generic
assert_eq!(errors_empty[0]["message"], "Required field is missing"); assert_eq!(errors_empty[0]["message"], "Required field is missing");
} }