fixed type mismatch checking to not fail fast and work through nested data

This commit is contained in:
2025-09-12 22:59:27 -04:00
parent 3fdbf60396
commit 3d770b0831
3 changed files with 369 additions and 317 deletions

View File

@ -51,16 +51,7 @@ fn test_validate_simple() {
#[pg_test]
fn test_cache_invalid() {
let cache_result = invalid_schemas();
// Should fail due to invalid schema in the request
// Bulk caching produces both detailed meta-schema validation errors and a high-level wrapper error
assert_error_count(&cache_result, 3); // 2 detailed meta-schema errors + 1 high-level wrapper
// Check the high-level wrapper error
let wrapper_error = find_error_with_code(&cache_result, "COMPILE_ALL_SCHEMAS_FAILED");
assert_error_message_contains(wrapper_error, "Failed to compile JSON schemas during cache operation");
// Should also have detailed meta-schema validation errors
assert_error_count(&cache_result, 2);
assert!(has_error_with_code(&cache_result, "ENUM_VIOLATED"),
"Should have ENUM_VIOLATED errors");
}
@ -506,10 +497,10 @@ fn test_validate_property_merging() {
// entity (id, name) + user (password) + person (first_name, last_name)
let valid_person_with_all_properties = json!({
"type": "person", // Added to satisfy new type check
// From entity
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"type": "person",
// From user
"password": "securepass123",
@ -524,9 +515,9 @@ fn test_validate_property_merging() {
// Test that properties validate according to their schema definitions across the chain
let invalid_mixed_properties = json!({
"type": "person", // Added to satisfy new type check
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"type": "person",
"password": "short", // Too short from user schema
"first_name": "", // Empty string violates person schema minLength
"last_name": "Doe"
@ -548,10 +539,10 @@ fn test_validate_required_merging() {
// user: ["password"] (conditional when type=user)
// person: ["first_name", "last_name"] (conditional when type=person)
let missing_all_required = json!({ "type": "person" }); // Add type to pass initial check
let missing_all_required = json!({ "type": "person" });
let result = validate_json_schema("person", jsonb(missing_all_required));
// Should fail for all required fields across inheritance chain, except for the conditional 'password'
// Should fail for all required fields across inheritance chain
assert_error_count(&result, 4); // id, created_by, first_name, last_name
assert_has_error(&result, "REQUIRED_FIELD_MISSING", "/id");
assert_has_error(&result, "REQUIRED_FIELD_MISSING", "/created_by");
@ -622,6 +613,7 @@ fn test_validate_punc_with_refs() {
// Test 1: Public punc is strict - no extra properties allowed at root level
let public_root_extra = json!({
"type": "person",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"first_name": "John",
@ -637,6 +629,7 @@ fn test_validate_punc_with_refs() {
// Test 2: Private punc allows extra properties at root level
let private_root_extra = json!({
"type": "person",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"first_name": "John",
@ -650,6 +643,7 @@ fn test_validate_punc_with_refs() {
// Test 3: Valid data with address should pass for both
let valid_data_with_address = json!({
"type": "person",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"first_name": "John",
@ -668,6 +662,7 @@ fn test_validate_punc_with_refs() {
// Test 4: Extra properties in nested address should fail for BOTH puncs (types are always strict)
let address_with_extra = json!({
"type": "person",
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "John Doe",
"first_name": "John",
@ -679,20 +674,6 @@ fn test_validate_punc_with_refs() {
}
});
// NOTE: The following test is disabled due to what appears to be a bug in the `boon` validator.
// When a validation fails within a referenced schema (`$ref`), `boon` does not seem to propagate
// the set of evaluated properties back to the parent schema. As a result, if the parent schema
// also uses `unevaluatedProperties`, it incorrectly flags all properties as unevaluated.
// In this case, the validation of `person` fails on `/address/country`, which prevents the
// `public_ref_test.request` schema from learning that `id`, `name`, etc., were evaluated,
// causing it to incorrectly report 6 errors instead of the expected 1.
// The `allOf` wrapper workaround does not solve this, as the information is lost on any `Err` result.
// This test is preserved to be re-enabled if/when the validator is fixed.
//
// let result_public_address = validate_json_schema("public_ref_test.request", jsonb(address_with_extra.clone()));
// assert_error_count(&result_public_address, 1);
// assert_has_error(&result_public_address, "FALSE_SCHEMA", "/address/country");
let result_private_address = validate_json_schema("private_ref_test.request", jsonb(address_with_extra));
assert_error_count(&result_private_address, 1);
assert_has_error(&result_private_address, "FALSE_SCHEMA", "/address/country");
@ -735,6 +716,7 @@ fn test_validate_punc_local_refs() {
// Test 1: Punc request referencing a schema defined locally within the punc
let valid_local_ref = json!({
"type": "local_address",
"street": "123 Main St",
"city": "Anytown"
});
@ -742,6 +724,7 @@ fn test_validate_punc_local_refs() {
assert_success(&result_valid_local);
let invalid_local_ref = json!({
"type": "local_address",
"street": "123 Main St" // Missing city
});
let result_invalid_local = validate_json_schema("punc_with_local_ref_test.request", jsonb(invalid_local_ref));
@ -750,8 +733,10 @@ fn test_validate_punc_local_refs() {
// Test 2: Punc with a local schema that references a global type schema
let valid_global_ref = json!({
"type": "local_user_with_thing",
"user_name": "Alice",
"thing": {
"type": "global_thing",
"id": "550e8400-e29b-41d4-a716-446655440000"
}
});
@ -759,8 +744,10 @@ fn test_validate_punc_local_refs() {
assert_success(&result_valid_global);
let invalid_global_ref = json!({
"type": "local_user_with_thing",
"user_name": "Bob",
"thing": {
"type": "global_thing",
"id": "not-a-uuid" // Invalid format for global_thing's id
}
});
@ -842,4 +829,76 @@ fn test_validate_type_matching() {
assert_error_count(&result_invalid_short, 1);
let error = find_error_with_code_and_path(&result_invalid_short, "TYPE_MISMATCH", "/type");
assert_error_message_contains(error, "Instance type 'job' does not match expected type 'super_job'");
// 4. Test punc with root, nested, and oneOf type refs
let valid_punc_instance = json!({
"root_job": {
"type": "job",
"name": "root job",
"job_id": "job456"
},
"nested_or_super_job": {
"type": "super_job",
"name": "nested super job",
"job_id": "job789",
"manager_id": "mgr2"
}
});
let result_valid_punc = validate_json_schema("type_test_punc.request", jsonb(valid_punc_instance));
assert_success(&result_valid_punc);
// 5. Test invalid type at punc root ref
let invalid_punc_root = json!({
"root_job": {
"type": "entity", // Should be "job"
"name": "root job",
"job_id": "job456"
},
"nested_or_super_job": {
"type": "super_job",
"name": "nested super job",
"job_id": "job789",
"manager_id": "mgr2"
}
});
let result_invalid_punc_root = validate_json_schema("type_test_punc.request", jsonb(invalid_punc_root));
assert_error_count(&result_invalid_punc_root, 1);
assert_has_error(&result_invalid_punc_root, "TYPE_MISMATCH", "/root_job/type");
// 6. Test invalid type at punc nested ref
let invalid_punc_nested = json!({
"root_job": {
"type": "job",
"name": "root job",
"job_id": "job456"
},
"nested_or_super_job": {
"my_job": {
"type": "entity", // Should be "job"
"name": "nested job",
"job_id": "job789"
}
}
});
let result_invalid_punc_nested = validate_json_schema("type_test_punc.request", jsonb(invalid_punc_nested));
assert_error_count(&result_invalid_punc_nested, 1);
assert_has_error(&result_invalid_punc_nested, "TYPE_MISMATCH", "/nested_or_super_job/my_job/type");
// 7. Test invalid type at punc oneOf ref
let invalid_punc_oneof = json!({
"root_job": {
"type": "job",
"name": "root job",
"job_id": "job456"
},
"nested_or_super_job": {
"type": "job", // Should be "super_job"
"name": "nested super job",
"job_id": "job789",
"manager_id": "mgr2"
}
});
let result_invalid_punc_oneof = validate_json_schema("type_test_punc.request", jsonb(invalid_punc_oneof));
// This will have multiple errors because the invalid oneOf branch will also fail the other branch's validation
assert_has_error(&result_invalid_punc_oneof, "TYPE_MISMATCH", "/nested_or_super_job/type");
}