Skip to content

Commit f3487a0

Browse files
authored
Fix $id bug on Draft 6 canonicalizer (#710)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 7630451 commit f3487a0

3 files changed

Lines changed: 201 additions & 3 deletions

File tree

src/alterschema/canonicalizer/next/type_with_applicator_to_allof.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ class TypeWithApplicatorToAllOf final : public SchemaTransformRule {
124124
for (const auto &entry : schema.as_object()) {
125125
if (entry.first == "not" || entry.first == "anyOf" ||
126126
entry.first == "allOf" || entry.first == "oneOf" ||
127-
entry.first == "$schema" || entry.first == "id") {
127+
entry.first == "$schema" || entry.first == "id" ||
128+
entry.first == "$id") {
128129
continue;
129130
}
130131
typed_branch.assign(entry.first, entry.second);
@@ -186,6 +187,9 @@ class TypeWithApplicatorToAllOf final : public SchemaTransformRule {
186187
if (schema.defines("id")) {
187188
new_schema.assign("id", schema.at("id"));
188189
}
190+
if (schema.defines("$id")) {
191+
new_schema.assign("$id", schema.at("$id"));
192+
}
189193
new_schema.assign("allOf", std::move(new_allof));
190194
schema.into(std::move(new_schema));
191195
}

src/alterschema/common/enum_with_type.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,14 @@ class EnumWithType final : public SchemaTransformRule {
3030
schema.defines("enum") && schema.at("enum").is_array());
3131

3232
const auto current_types{parse_schema_type(schema.at("type"))};
33+
const bool integer_matches_integral{
34+
vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_6) &&
35+
current_types.test(std::to_underlying(JSON::Type::Integer))};
3336
ONLY_CONTINUE_IF(std::ranges::all_of(
34-
schema.at("enum").as_array(), [&current_types](const auto &item) {
35-
return current_types.test(std::to_underlying(item.type()));
37+
schema.at("enum").as_array(),
38+
[&current_types, integer_matches_integral](const auto &item) {
39+
return current_types.test(std::to_underlying(item.type())) ||
40+
(integer_matches_integral && item.is_integral());
3641
}));
3742

3843
return APPLIES_TO_KEYWORDS("enum", "type");

test/alterschema/alterschema_canonicalize_draft6_test.cc

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4476,3 +4476,192 @@ TEST_F(CanonicalizerDraft6Test, enum_assertion_uniqueItems_wrapped) {
44764476

44774477
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
44784478
}
4479+
4480+
TEST_F(CanonicalizerDraft6Test, type_applicator_preserves_dollar_id) {
4481+
auto document = sourcemeta::core::parse_json(R"JSON({
4482+
"$schema": "http://json-schema.org/draft-06/schema#",
4483+
"$id": "http://example.com/schema",
4484+
"type": "string",
4485+
"not": { "pattern": "^admin" }
4486+
})JSON");
4487+
4488+
const auto expected = sourcemeta::core::parse_json(R"JSON({
4489+
"$schema": "http://json-schema.org/draft-06/schema#",
4490+
"$id": "http://example.com/schema",
4491+
"allOf": [
4492+
{
4493+
"not": {
4494+
"anyOf": [
4495+
{ "enum": [ null ] },
4496+
{ "enum": [ false, true ] },
4497+
{
4498+
"type": "object",
4499+
"minProperties": 0,
4500+
"propertyNames": true,
4501+
"properties": {},
4502+
"patternProperties": {},
4503+
"additionalProperties": true
4504+
},
4505+
{
4506+
"type": "array",
4507+
"minItems": 0,
4508+
"uniqueItems": false,
4509+
"items": true
4510+
},
4511+
{
4512+
"type": "string",
4513+
"pattern": "^admin",
4514+
"minLength": 0
4515+
},
4516+
{ "type": "number" }
4517+
]
4518+
}
4519+
},
4520+
{ "type": "string", "minLength": 0 }
4521+
]
4522+
})JSON");
4523+
4524+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4525+
}
4526+
4527+
TEST_F(CanonicalizerDraft6Test, const_with_incompatible_type) {
4528+
auto document = sourcemeta::core::parse_json(R"JSON({
4529+
"$schema": "http://json-schema.org/draft-06/schema#",
4530+
"type": "string",
4531+
"const": 42
4532+
})JSON");
4533+
4534+
const auto expected = sourcemeta::core::parse_json(R"JSON(
4535+
false
4536+
)JSON");
4537+
4538+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4539+
}
4540+
4541+
TEST_F(CanonicalizerDraft6Test, const_null_with_string_type) {
4542+
auto document = sourcemeta::core::parse_json(R"JSON({
4543+
"$schema": "http://json-schema.org/draft-06/schema#",
4544+
"type": "string",
4545+
"const": null
4546+
})JSON");
4547+
4548+
const auto expected = sourcemeta::core::parse_json(R"JSON(
4549+
false
4550+
)JSON");
4551+
4552+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4553+
}
4554+
4555+
TEST_F(CanonicalizerDraft6Test, integer_enum_with_real_equivalent) {
4556+
auto document = sourcemeta::core::parse_json(R"JSON({
4557+
"$schema": "http://json-schema.org/draft-06/schema#",
4558+
"type": "integer",
4559+
"enum": [ 3, 3.0, "hello" ]
4560+
})JSON");
4561+
4562+
const auto expected = sourcemeta::core::parse_json(R"JSON({
4563+
"$schema": "http://json-schema.org/draft-06/schema#",
4564+
"allOf": [
4565+
{
4566+
"anyOf": [
4567+
{ "enum": [ null ] },
4568+
{ "enum": [ false, true ] },
4569+
{
4570+
"type": "object",
4571+
"minProperties": 0,
4572+
"propertyNames": true,
4573+
"properties": {},
4574+
"patternProperties": {},
4575+
"additionalProperties": true
4576+
},
4577+
{
4578+
"type": "array",
4579+
"minItems": 0,
4580+
"uniqueItems": false,
4581+
"items": true
4582+
},
4583+
{ "type": "string", "minLength": 0 },
4584+
{ "type": "number", "multipleOf": 1 }
4585+
]
4586+
},
4587+
{ "enum": [ 3, 3.0 ] }
4588+
]
4589+
})JSON");
4590+
4591+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4592+
}
4593+
4594+
TEST_F(CanonicalizerDraft6Test, property_names_false_restricts_objects) {
4595+
auto document = sourcemeta::core::parse_json(R"JSON({
4596+
"$schema": "http://json-schema.org/draft-06/schema#",
4597+
"type": "object",
4598+
"propertyNames": false
4599+
})JSON");
4600+
4601+
const auto expected = sourcemeta::core::parse_json(R"JSON({
4602+
"$schema": "http://json-schema.org/draft-06/schema#",
4603+
"type": "object",
4604+
"minProperties": 0,
4605+
"propertyNames": false,
4606+
"properties": {},
4607+
"patternProperties": {},
4608+
"additionalProperties": true
4609+
})JSON");
4610+
4611+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4612+
}
4613+
4614+
TEST_F(CanonicalizerDraft6Test, contains_false_restricts_arrays) {
4615+
auto document = sourcemeta::core::parse_json(R"JSON({
4616+
"$schema": "http://json-schema.org/draft-06/schema#",
4617+
"type": "array",
4618+
"contains": false
4619+
})JSON");
4620+
4621+
const auto expected = sourcemeta::core::parse_json(R"JSON({
4622+
"$schema": "http://json-schema.org/draft-06/schema#",
4623+
"type": "array",
4624+
"minItems": 0,
4625+
"uniqueItems": false,
4626+
"contains": false,
4627+
"items": true
4628+
})JSON");
4629+
4630+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4631+
}
4632+
4633+
TEST_F(CanonicalizerDraft6Test, exclusive_minimum_subsumes_minimum) {
4634+
auto document = sourcemeta::core::parse_json(R"JSON({
4635+
"$schema": "http://json-schema.org/draft-06/schema#",
4636+
"type": "number",
4637+
"minimum": 5,
4638+
"exclusiveMinimum": 5
4639+
})JSON");
4640+
4641+
const auto expected = sourcemeta::core::parse_json(R"JSON({
4642+
"$schema": "http://json-schema.org/draft-06/schema#",
4643+
"type": "number",
4644+
"exclusiveMinimum": 5
4645+
})JSON");
4646+
4647+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4648+
}
4649+
4650+
TEST_F(CanonicalizerDraft6Test, draft4_id_becomes_unknown_keyword) {
4651+
auto document = sourcemeta::core::parse_json(R"JSON({
4652+
"$schema": "http://json-schema.org/draft-06/schema#",
4653+
"id": "http://old.com",
4654+
"$id": "http://new.com",
4655+
"type": "string"
4656+
})JSON");
4657+
4658+
const auto expected = sourcemeta::core::parse_json(R"JSON({
4659+
"$schema": "http://json-schema.org/draft-06/schema#",
4660+
"$id": "http://new.com",
4661+
"x-id": "http://old.com",
4662+
"type": "string",
4663+
"minLength": 0
4664+
})JSON");
4665+
4666+
CANONICALIZE_NEXT(document, expected, *compiled_meta_);
4667+
}

0 commit comments

Comments
 (0)