Skip to content

Commit 8ff60cf

Browse files
committed
Generalize relativize() as reference_visit()
Signed-off-by: Juan Cruz Viotti <[email protected]>
1 parent 1257bcd commit 8ff60cf

File tree

6 files changed

+191
-97
lines changed

6 files changed

+191
-97
lines changed

src/core/jsonschema/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME jsonschema
88
keywords.h transform.h
99
SOURCES jsonschema.cc default_walker.cc frame.cc
1010
resolver.cc walker.cc bundle.cc
11-
unevaluated.cc relativize.cc unidentify.cc
11+
unevaluated.cc unidentify.cc
1212
transform_rule.cc transformer.cc
1313
"${CMAKE_CURRENT_BINARY_DIR}/official_resolver.cc")
1414

src/core/jsonschema/include/sourcemeta/core/jsonschema.h

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@
1515
#include <sourcemeta/core/jsonschema_unevaluated.h>
1616
#include <sourcemeta/core/jsonschema_walker.h>
1717

18-
#include <map> // std::map
19-
#include <optional> // std::optional
20-
#include <string> // std::string
18+
#include <functional> // std::function
19+
#include <map> // std::map
20+
#include <optional> // std::optional
21+
#include <string> // std::string
2122

2223
/// @defgroup jsonschema JSON Schema
2324
/// @brief A set of JSON Schema utilities across draft versions.
@@ -323,8 +324,8 @@ auto schema_format_compare(const JSON::String &left, const JSON::String &right)
323324

324325
/// @ingroup jsonschema
325326
///
326-
/// Try to turn every possible absolute reference in a schema into a relative
327-
/// one. For example:
327+
/// Remove every identifer from a schema, rephrasing references (if any) as
328+
/// needed. For example:
328329
///
329330
/// ```cpp
330331
/// #include <sourcemeta/core/json.h>
@@ -335,32 +336,42 @@ auto schema_format_compare(const JSON::String &left, const JSON::String &right)
335336
/// sourcemeta::core::parse_json(R"JSON({
336337
/// "$id": "https://www.example.com/schema",
337338
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
338-
/// "$ref": "https://www.example.com/another",
339+
/// "$ref": "another",
339340
/// })JSON");
340341
///
341-
/// sourcemeta::core::relativize(schema,
342+
/// sourcemeta::core::unidentify(schema,
342343
/// sourcemeta::core::schema_official_walker,
343344
/// sourcemeta::core::official_resolver);
344345
///
345346
/// const sourcemeta::core::JSON expected =
346347
/// sourcemeta::core::parse_json(R"JSON({
347-
/// "$id": "https://www.example.com/schema",
348348
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
349-
/// "$ref": "another",
349+
/// "$ref": "https://www.example.com/another",
350350
/// })JSON");
351351
///
352352
/// assert(schema == expected);
353353
/// ```
354354
SOURCEMETA_CORE_JSONSCHEMA_EXPORT
355-
auto relativize(
355+
auto unidentify(
356356
JSON &schema, const SchemaWalker &walker, const SchemaResolver &resolver,
357-
const std::optional<std::string> &default_dialect = std::nullopt,
358-
const std::optional<std::string> &default_id = std::nullopt) -> void;
357+
const std::optional<std::string> &default_dialect = std::nullopt) -> void;
359358

360359
/// @ingroup jsonschema
361360
///
362-
/// Remove every identifer from a schema, rephrasing references (if any) as
363-
/// needed. For example:
361+
/// Visit every reference in a schema. The arguments are as follows:
362+
///
363+
/// - The current subschema
364+
/// - The base URI of the current subschema
365+
/// - The reference vocabulary
366+
/// - The reference keyword name
367+
/// - The reference reference destination
368+
using SchemaVisitorReference = std::function<void(
369+
JSON &, const URI &, const JSON::String &, const JSON::String &, URI &)>;
370+
371+
/// @ingroup jsonschema
372+
///
373+
/// A reference visitor to try to turn every possible absolute reference in a
374+
/// schema into a relative one. For example:
364375
///
365376
/// ```cpp
366377
/// #include <sourcemeta/core/json.h>
@@ -371,25 +382,70 @@ auto relativize(
371382
/// sourcemeta::core::parse_json(R"JSON({
372383
/// "$id": "https://www.example.com/schema",
373384
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
374-
/// "$ref": "another",
385+
/// "$ref": "https://www.example.com/another",
375386
/// })JSON");
376387
///
377-
/// sourcemeta::core::unidentify(schema,
388+
/// sourcemeta::core::reference_visit(schema,
378389
/// sourcemeta::core::schema_official_walker,
379-
/// sourcemeta::core::official_resolver);
390+
/// sourcemeta::core::official_resolver,
391+
/// sourcemeta::core::reference_visitor_relativize);
380392
///
381393
/// const sourcemeta::core::JSON expected =
382394
/// sourcemeta::core::parse_json(R"JSON({
395+
/// "$id": "https://www.example.com/schema",
383396
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
384-
/// "$ref": "https://www.example.com/another",
397+
/// "$ref": "another",
385398
/// })JSON");
386399
///
387400
/// assert(schema == expected);
388401
/// ```
389402
SOURCEMETA_CORE_JSONSCHEMA_EXPORT
390-
auto unidentify(
403+
auto reference_visitor_relativize(JSON &subschema, const URI &base,
404+
const JSON::String &vocabulary,
405+
const JSON::String &keyword, URI &value)
406+
-> void;
407+
408+
/// @ingroup jsonschema
409+
///
410+
/// A utility function to loop over every reference in a schema, allowing
411+
/// modifications to their subschemas if desired. Note that the consumer is
412+
/// responsible for not making the schema invalid. For example:
413+
///
414+
/// ```cpp
415+
/// #include <sourcemeta/core/json.h>
416+
/// #include <sourcemeta/core/jsonschema.h>
417+
///
418+
/// sourcemeta::core::JSON schema =
419+
/// sourcemeta::core::parse_json(R"JSON({
420+
/// "$id": "https://www.example.com/schema",
421+
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
422+
/// "$ref": "https://www.example.com/another",
423+
/// })JSON");
424+
///
425+
/// static auto visitor(JSON &subschema,
426+
/// const URI &base,
427+
/// const JSON::String &vocabulary,
428+
/// const JSON::String &keyword,
429+
/// URI &value) -> void {
430+
/// sourcemeta::core::prettify(subschema, std::cerr);
431+
/// std::cerr << "\n";
432+
/// std::cerr << base.recompose() << "\n";
433+
/// std::cerr << vocabulary << "\n";
434+
/// std::cerr << keyword << "\n";
435+
/// std::cerr << value.recompose() << "\n";
436+
/// }
437+
///
438+
/// sourcemeta::core::reference_visit(schema,
439+
/// sourcemeta::core::schema_official_walker,
440+
/// sourcemeta::core::official_resolver,
441+
/// visitor);
442+
/// ```
443+
SOURCEMETA_CORE_JSONSCHEMA_EXPORT
444+
auto reference_visit(
391445
JSON &schema, const SchemaWalker &walker, const SchemaResolver &resolver,
392-
const std::optional<std::string> &default_dialect = std::nullopt) -> void;
446+
const SchemaVisitorReference &callback,
447+
const std::optional<std::string> &default_dialect = std::nullopt,
448+
const std::optional<std::string> &default_id = std::nullopt) -> void;
393449

394450
} // namespace sourcemeta::core
395451

src/core/jsonschema/jsonschema.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,3 +547,68 @@ auto sourcemeta::core::schema_format_compare(
547547
return left < right;
548548
}
549549
}
550+
551+
auto sourcemeta::core::reference_visit(
552+
sourcemeta::core::JSON &schema,
553+
const sourcemeta::core::SchemaWalker &walker,
554+
const sourcemeta::core::SchemaResolver &resolver,
555+
const sourcemeta::core::SchemaVisitorReference &callback,
556+
const std::optional<std::string> &default_dialect,
557+
const std::optional<std::string> &default_id) -> void {
558+
sourcemeta::core::SchemaFrame frame;
559+
frame.analyse(schema, walker, resolver, default_dialect, default_id);
560+
for (const auto &entry : frame.locations()) {
561+
if (entry.second.type !=
562+
sourcemeta::core::SchemaFrame::LocationType::Resource &&
563+
entry.second.type !=
564+
sourcemeta::core::SchemaFrame::LocationType::Subschema) {
565+
continue;
566+
}
567+
568+
auto &subschema{sourcemeta::core::get(schema, entry.second.pointer)};
569+
assert(sourcemeta::core::is_schema(subschema));
570+
if (!subschema.is_object()) {
571+
continue;
572+
}
573+
574+
const sourcemeta::core::URI base{entry.second.base};
575+
// Assume the base is canonicalized already
576+
assert(
577+
sourcemeta::core::URI{entry.second.base}.canonicalize().recompose() ==
578+
base.recompose());
579+
for (const auto &property : subschema.as_object()) {
580+
const auto walker_result{
581+
walker(property.first, frame.vocabularies(entry.second, resolver))};
582+
if (walker_result.type !=
583+
sourcemeta::core::SchemaKeywordType::Reference ||
584+
!property.second.is_string()) {
585+
continue;
586+
}
587+
588+
assert(property.second.is_string());
589+
assert(walker_result.vocabulary.has_value());
590+
sourcemeta::core::URI reference{property.second.to_string()};
591+
callback(subschema, base, walker_result.vocabulary.value(),
592+
property.first, reference);
593+
}
594+
}
595+
}
596+
597+
auto sourcemeta::core::reference_visitor_relativize(
598+
sourcemeta::core::JSON &subschema, const sourcemeta::core::URI &base,
599+
const sourcemeta::core::JSON::String &vocabulary,
600+
const sourcemeta::core::JSON::String &keyword, URI &reference) -> void {
601+
// In 2019-09, `$recursiveRef` can only be `#`, so there
602+
// is nothing else we can possibly do
603+
if (vocabulary == "https://json-schema.org/draft/2019-09/vocab/core" &&
604+
keyword == "$recursiveRef") {
605+
return;
606+
}
607+
608+
reference.relative_to(base);
609+
reference.canonicalize();
610+
611+
if (reference.is_relative()) {
612+
subschema.assign(keyword, sourcemeta::core::JSON{reference.recompose()});
613+
}
614+
}

src/core/jsonschema/relativize.cc

Lines changed: 0 additions & 49 deletions
This file was deleted.

src/core/jsonschema/resolver.cc

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,9 +150,10 @@ auto SchemaFlatFileResolver::operator()(std::string_view identifier) const
150150
*this, result->second.default_dialect);
151151
// Because we allow re-identification, we can get into issues unless we
152152
// always try to relativize references
153-
sourcemeta::core::relativize(schema, schema_official_walker, *this,
154-
result->second.default_dialect,
155-
result->second.original_identifier);
153+
sourcemeta::core::reference_visit(
154+
schema, schema_official_walker, *this,
155+
sourcemeta::core::reference_visitor_relativize,
156+
result->second.default_dialect, result->second.original_identifier);
156157
sourcemeta::core::reidentify(schema, result->first, *this,
157158
result->second.default_dialect);
158159

0 commit comments

Comments
 (0)