Skip to content

Commit 6667b99

Browse files
authored
Detect unresolved fragments when bundling (#1416)
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 438dac1 commit 6667b99

File tree

6 files changed

+69
-108
lines changed

6 files changed

+69
-108
lines changed

src/jsonschema/bundle.cc

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ auto bundle_schema(sourcemeta::jsontoolkit::JSON &root,
5353
const std::string &container,
5454
const sourcemeta::jsontoolkit::JSON &subschema,
5555
sourcemeta::jsontoolkit::FrameLocations &frame,
56+
sourcemeta::jsontoolkit::FrameReferences &references,
5657
const sourcemeta::jsontoolkit::SchemaWalker &walker,
5758
const sourcemeta::jsontoolkit::SchemaResolver &resolver,
5859
const std::optional<std::string> &default_dialect) -> void {
59-
sourcemeta::jsontoolkit::FrameReferences references;
6060
sourcemeta::jsontoolkit::frame(subschema, frame, references, walker, resolver,
6161
default_dialect);
6262

@@ -72,6 +72,19 @@ auto bundle_schema(sourcemeta::jsontoolkit::JSON &root,
7272
continue;
7373
}
7474

75+
// If we can't find the destination but there is a base and we can
76+
// find base, then we are facing an unresolved fragment
77+
if (reference.base.has_value()) {
78+
if (frame.contains({sourcemeta::jsontoolkit::ReferenceType::Static,
79+
reference.base.value()}) ||
80+
frame.contains({sourcemeta::jsontoolkit::ReferenceType::Dynamic,
81+
reference.base.value()})) {
82+
throw sourcemeta::jsontoolkit::SchemaReferenceError(
83+
reference.destination, key.second,
84+
"Could not resolve schema reference");
85+
}
86+
}
87+
7588
root.assign_if_missing(container,
7689
sourcemeta::jsontoolkit::JSON::make_object());
7790

@@ -121,7 +134,7 @@ auto bundle_schema(sourcemeta::jsontoolkit::JSON &root,
121134
}
122135

123136
embed_schema(root.at(container), identifier, copy);
124-
bundle_schema(root, container, copy, frame, walker, resolver,
137+
bundle_schema(root, container, copy, frame, references, walker, resolver,
125138
default_dialect);
126139
}
127140
}
@@ -197,8 +210,9 @@ auto bundle(sourcemeta::jsontoolkit::JSON &schema, const SchemaWalker &walker,
197210
const auto vocabularies{
198211
sourcemeta::jsontoolkit::vocabularies(schema, resolver, default_dialect)};
199212
sourcemeta::jsontoolkit::FrameLocations frame;
213+
sourcemeta::jsontoolkit::FrameReferences references;
200214
bundle_schema(schema, definitions_keyword(vocabularies), schema, frame,
201-
walker, resolver, default_dialect);
215+
references, walker, resolver, default_dialect);
202216

203217
if (options == BundleOptions::WithoutIdentifiers) {
204218
remove_identifiers(schema, walker, resolver, default_dialect);

src/jsonschema/frame.cc

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,11 @@ auto sourcemeta::jsontoolkit::frame(
257257
metaschema.canonicalize();
258258
const std::string destination{metaschema.recompose()};
259259
assert(entry.common.value.defines("$schema"));
260-
references.insert(
261-
{{ReferenceType::Static, entry.common.pointer.concat({"$schema"})},
262-
{destination, metaschema.recompose_without_fragment(),
263-
fragment_string(metaschema)}});
260+
references.insert_or_assign(
261+
{ReferenceType::Static, entry.common.pointer.concat({"$schema"})},
262+
FrameReferencesEntry{destination,
263+
metaschema.recompose_without_fragment(),
264+
fragment_string(metaschema)});
264265
}
265266

266267
// Handle schema anchors
@@ -423,10 +424,11 @@ auto sourcemeta::jsontoolkit::frame(
423424
}
424425

425426
ref.canonicalize();
426-
references.insert(
427-
{{ReferenceType::Static, entry.common.pointer.concat({"$ref"})},
428-
{ref.recompose(), ref.recompose_without_fragment(),
429-
fragment_string(ref)}});
427+
references.insert_or_assign(
428+
{ReferenceType::Static, entry.common.pointer.concat({"$ref"})},
429+
FrameReferencesEntry{ref.recompose(),
430+
ref.recompose_without_fragment(),
431+
fragment_string(ref)});
430432
}
431433

432434
if (entry.common.vocabularies.contains(
@@ -454,10 +456,11 @@ auto sourcemeta::jsontoolkit::frame(
454456
: ReferenceType::Dynamic};
455457
const sourcemeta::jsontoolkit::URI anchor_uri{
456458
std::move(anchor_uri_string)};
457-
references.insert(
458-
{{reference_type, entry.common.pointer.concat({"$recursiveRef"})},
459-
{anchor_uri.recompose(), anchor_uri.recompose_without_fragment(),
460-
fragment_string(anchor_uri)}});
459+
references.insert_or_assign(
460+
{reference_type, entry.common.pointer.concat({"$recursiveRef"})},
461+
FrameReferencesEntry{anchor_uri.recompose(),
462+
anchor_uri.recompose_without_fragment(),
463+
fragment_string(anchor_uri)});
461464
}
462465

463466
if (entry.common.vocabularies.contains(
@@ -486,12 +489,12 @@ auto sourcemeta::jsontoolkit::frame(
486489
(has_fragment &&
487490
maybe_static_frame != frame.end() &&
488491
maybe_dynamic_frame == frame.end())};
489-
references.insert(
490-
{{behaves_as_static ? ReferenceType::Static
491-
: ReferenceType::Dynamic,
492-
entry.common.pointer.concat({"$dynamicRef"})},
493-
{std::move(ref_string), ref.recompose_without_fragment(),
494-
fragment_string(ref)}});
492+
references.insert_or_assign(
493+
{behaves_as_static ? ReferenceType::Static : ReferenceType::Dynamic,
494+
entry.common.pointer.concat({"$dynamicRef"})},
495+
FrameReferencesEntry{std::move(ref_string),
496+
ref.recompose_without_fragment(),
497+
fragment_string(ref)});
495498
}
496499
}
497500
}

test/jsonschema/jsonschema_bundle_2020_12_test.cc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,22 @@ TEST(JSONSchema_bundle_2020_12, schema_not_found) {
265265
sourcemeta::jsontoolkit::SchemaResolutionError);
266266
}
267267

268+
TEST(JSONSchema_bundle_2020_12, anchor_not_found) {
269+
sourcemeta::jsontoolkit::JSON document =
270+
sourcemeta::jsontoolkit::parse(R"JSON({
271+
"$id": "https://example.com",
272+
"$schema": "https://json-schema.org/draft/2020-12/schema",
273+
"properties": {
274+
"foo": { "$ref": "https://example.com/foo/bar#xxxxxxxx" }
275+
}
276+
})JSON");
277+
278+
EXPECT_THROW(sourcemeta::jsontoolkit::bundle(
279+
document, sourcemeta::jsontoolkit::default_schema_walker,
280+
test_resolver),
281+
sourcemeta::jsontoolkit::SchemaReferenceError);
282+
}
283+
268284
TEST(JSONSchema_bundle_2020_12, idempotency) {
269285
sourcemeta::jsontoolkit::JSON document =
270286
sourcemeta::jsontoolkit::parse(R"JSON({

test/jsonschema/jsonschema_bundle_draft4_test.cc

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88

99
static auto test_resolver(std::string_view identifier)
1010
-> std::optional<sourcemeta::jsontoolkit::JSON> {
11-
if (identifier == "https://example.com/foo/bar") {
12-
return sourcemeta::jsontoolkit::parse(R"JSON({
13-
"$schema": "http://json-schema.org/draft-04/schema#",
14-
"id": "https://example.com/foo/bar",
15-
"$anchor": "baz"
16-
})JSON");
17-
} else if (identifier == "https://www.sourcemeta.com/test-1") {
11+
if (identifier == "https://www.sourcemeta.com/test-1") {
1812
return sourcemeta::jsontoolkit::parse(R"JSON({
1913
"$schema": "http://json-schema.org/draft-04/schema#",
2014
"id": "https://www.sourcemeta.com/test-1",
@@ -118,9 +112,7 @@ TEST(JSONSchema_bundle_draft4, simple_with_id) {
118112
"bar": {
119113
"id": "https://www.sourcemeta.com",
120114
"allOf": [ { "$ref": "test-2" } ]
121-
},
122-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
123-
"qux": { "$ref": "https://example.com/foo/bar" }
115+
}
124116
}
125117
})JSON");
126118

@@ -136,9 +128,7 @@ TEST(JSONSchema_bundle_draft4, simple_with_id) {
136128
"bar": {
137129
"id": "https://www.sourcemeta.com",
138130
"allOf": [ { "$ref": "test-2" } ]
139-
},
140-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
141-
"qux": { "$ref": "https://example.com/foo/bar" }
131+
}
142132
},
143133
"definitions": {
144134
"https://www.sourcemeta.com/test-1": {
@@ -155,11 +145,6 @@ TEST(JSONSchema_bundle_draft4, simple_with_id) {
155145
"$schema": "http://json-schema.org/draft-04/schema#",
156146
"id": "https://www.sourcemeta.com/test-3",
157147
"allOf": [ { "$ref": "test-1" } ]
158-
},
159-
"https://example.com/foo/bar": {
160-
"id": "https://example.com/foo/bar",
161-
"$schema": "http://json-schema.org/draft-04/schema#",
162-
"$anchor": "baz"
163148
}
164149
}
165150
})JSON");
@@ -176,9 +161,7 @@ TEST(JSONSchema_bundle_draft4, simple_without_id) {
176161
"bar": {
177162
"id": "https://www.sourcemeta.com",
178163
"allOf": [ { "$ref": "test-2" } ]
179-
},
180-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
181-
"qux": { "$ref": "https://example.com/foo/bar" }
164+
}
182165
}
183166
})JSON");
184167

@@ -193,9 +176,7 @@ TEST(JSONSchema_bundle_draft4, simple_without_id) {
193176
"bar": {
194177
"id": "https://www.sourcemeta.com",
195178
"allOf": [ { "$ref": "test-2" } ]
196-
},
197-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
198-
"qux": { "$ref": "https://example.com/foo/bar" }
179+
}
199180
},
200181
"definitions": {
201182
"https://www.sourcemeta.com/test-1": {
@@ -212,11 +193,6 @@ TEST(JSONSchema_bundle_draft4, simple_without_id) {
212193
"$schema": "http://json-schema.org/draft-04/schema#",
213194
"id": "https://www.sourcemeta.com/test-3",
214195
"allOf": [ { "$ref": "test-1" } ]
215-
},
216-
"https://example.com/foo/bar": {
217-
"id": "https://example.com/foo/bar",
218-
"$schema": "http://json-schema.org/draft-04/schema#",
219-
"$anchor": "baz"
220196
}
221197
}
222198
})JSON");

test/jsonschema/jsonschema_bundle_draft6_test.cc

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88

99
static auto test_resolver(std::string_view identifier)
1010
-> std::optional<sourcemeta::jsontoolkit::JSON> {
11-
if (identifier == "https://example.com/foo/bar") {
12-
return sourcemeta::jsontoolkit::parse(R"JSON({
13-
"$schema": "http://json-schema.org/draft-06/schema#",
14-
"$id": "https://example.com/foo/bar",
15-
"$anchor": "baz"
16-
})JSON");
17-
} else if (identifier == "https://www.sourcemeta.com/test-1") {
11+
if (identifier == "https://www.sourcemeta.com/test-1") {
1812
return sourcemeta::jsontoolkit::parse(R"JSON({
1913
"$schema": "http://json-schema.org/draft-06/schema#",
2014
"$id": "https://www.sourcemeta.com/test-1",
@@ -118,9 +112,7 @@ TEST(JSONSchema_bundle_draft6, simple_with_id) {
118112
"bar": {
119113
"$id": "https://www.sourcemeta.com",
120114
"allOf": [ { "$ref": "test-2" } ]
121-
},
122-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
123-
"qux": { "$ref": "https://example.com/foo/bar" }
115+
}
124116
}
125117
})JSON");
126118

@@ -136,9 +128,7 @@ TEST(JSONSchema_bundle_draft6, simple_with_id) {
136128
"bar": {
137129
"$id": "https://www.sourcemeta.com",
138130
"allOf": [ { "$ref": "test-2" } ]
139-
},
140-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
141-
"qux": { "$ref": "https://example.com/foo/bar" }
131+
}
142132
},
143133
"definitions": {
144134
"https://www.sourcemeta.com/test-1": {
@@ -155,11 +145,6 @@ TEST(JSONSchema_bundle_draft6, simple_with_id) {
155145
"$schema": "http://json-schema.org/draft-06/schema#",
156146
"$id": "https://www.sourcemeta.com/test-3",
157147
"allOf": [ { "$ref": "test-1" } ]
158-
},
159-
"https://example.com/foo/bar": {
160-
"$id": "https://example.com/foo/bar",
161-
"$schema": "http://json-schema.org/draft-06/schema#",
162-
"$anchor": "baz"
163148
}
164149
}
165150
})JSON");
@@ -176,9 +161,7 @@ TEST(JSONSchema_bundle_draft6, simple_without_id) {
176161
"bar": {
177162
"$id": "https://www.sourcemeta.com",
178163
"allOf": [ { "$ref": "test-2" } ]
179-
},
180-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
181-
"qux": { "$ref": "https://example.com/foo/bar" }
164+
}
182165
}
183166
})JSON");
184167

@@ -193,9 +176,7 @@ TEST(JSONSchema_bundle_draft6, simple_without_id) {
193176
"bar": {
194177
"$id": "https://www.sourcemeta.com",
195178
"allOf": [ { "$ref": "test-2" } ]
196-
},
197-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
198-
"qux": { "$ref": "https://example.com/foo/bar" }
179+
}
199180
},
200181
"definitions": {
201182
"https://www.sourcemeta.com/test-1": {
@@ -212,11 +193,6 @@ TEST(JSONSchema_bundle_draft6, simple_without_id) {
212193
"$schema": "http://json-schema.org/draft-06/schema#",
213194
"$id": "https://www.sourcemeta.com/test-3",
214195
"allOf": [ { "$ref": "test-1" } ]
215-
},
216-
"https://example.com/foo/bar": {
217-
"$id": "https://example.com/foo/bar",
218-
"$schema": "http://json-schema.org/draft-06/schema#",
219-
"$anchor": "baz"
220196
}
221197
}
222198
})JSON");

test/jsonschema/jsonschema_bundle_draft7_test.cc

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,7 @@
88

99
static auto test_resolver(std::string_view identifier)
1010
-> std::optional<sourcemeta::jsontoolkit::JSON> {
11-
if (identifier == "https://example.com/foo/bar") {
12-
return sourcemeta::jsontoolkit::parse(R"JSON({
13-
"$schema": "http://json-schema.org/draft-07/schema#",
14-
"$id": "https://example.com/foo/bar",
15-
"$anchor": "baz"
16-
})JSON");
17-
} else if (identifier == "https://www.sourcemeta.com/test-1") {
11+
if (identifier == "https://www.sourcemeta.com/test-1") {
1812
return sourcemeta::jsontoolkit::parse(R"JSON({
1913
"$schema": "http://json-schema.org/draft-07/schema#",
2014
"$id": "https://www.sourcemeta.com/test-1",
@@ -118,9 +112,7 @@ TEST(JSONSchema_bundle_draft7, simple_with_id) {
118112
"bar": {
119113
"$id": "https://www.sourcemeta.com",
120114
"allOf": [ { "$ref": "test-2" } ]
121-
},
122-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
123-
"qux": { "$ref": "https://example.com/foo/bar" }
115+
}
124116
}
125117
})JSON");
126118

@@ -136,9 +128,7 @@ TEST(JSONSchema_bundle_draft7, simple_with_id) {
136128
"bar": {
137129
"$id": "https://www.sourcemeta.com",
138130
"allOf": [ { "$ref": "test-2" } ]
139-
},
140-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
141-
"qux": { "$ref": "https://example.com/foo/bar" }
131+
}
142132
},
143133
"definitions": {
144134
"https://www.sourcemeta.com/test-1": {
@@ -155,11 +145,6 @@ TEST(JSONSchema_bundle_draft7, simple_with_id) {
155145
"$schema": "http://json-schema.org/draft-07/schema#",
156146
"$id": "https://www.sourcemeta.com/test-3",
157147
"allOf": [ { "$ref": "test-1" } ]
158-
},
159-
"https://example.com/foo/bar": {
160-
"$id": "https://example.com/foo/bar",
161-
"$schema": "http://json-schema.org/draft-07/schema#",
162-
"$anchor": "baz"
163148
}
164149
}
165150
})JSON");
@@ -176,9 +161,7 @@ TEST(JSONSchema_bundle_draft7, simple_without_id) {
176161
"bar": {
177162
"$id": "https://www.sourcemeta.com",
178163
"allOf": [ { "$ref": "test-2" } ]
179-
},
180-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
181-
"qux": { "$ref": "https://example.com/foo/bar" }
164+
}
182165
}
183166
})JSON");
184167

@@ -193,9 +176,7 @@ TEST(JSONSchema_bundle_draft7, simple_without_id) {
193176
"bar": {
194177
"$id": "https://www.sourcemeta.com",
195178
"allOf": [ { "$ref": "test-2" } ]
196-
},
197-
"baz": { "$ref": "https://example.com/foo/bar#baz" },
198-
"qux": { "$ref": "https://example.com/foo/bar" }
179+
}
199180
},
200181
"definitions": {
201182
"https://www.sourcemeta.com/test-1": {
@@ -212,11 +193,6 @@ TEST(JSONSchema_bundle_draft7, simple_without_id) {
212193
"$schema": "http://json-schema.org/draft-07/schema#",
213194
"$id": "https://www.sourcemeta.com/test-3",
214195
"allOf": [ { "$ref": "test-1" } ]
215-
},
216-
"https://example.com/foo/bar": {
217-
"$id": "https://example.com/foo/bar",
218-
"$schema": "http://json-schema.org/draft-07/schema#",
219-
"$anchor": "baz"
220196
}
221197
}
222198
})JSON");

0 commit comments

Comments
 (0)