Skip to content

Commit 3276bb4

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

File tree

6 files changed

+194
-97
lines changed

6 files changed

+194
-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: 78 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,43 @@ 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 keyword value
368+
using SchemaVisitorReference =
369+
std::function<void(JSON &, const URI &, const JSON::String &,
370+
const JSON::String &, const JSON::String &)>;
371+
372+
/// @ingroup jsonschema
373+
///
374+
/// A reference visitor to try to turn every possible absolute reference in a
375+
/// schema into a relative one. For example:
364376
///
365377
/// ```cpp
366378
/// #include <sourcemeta/core/json.h>
@@ -371,25 +383,71 @@ auto relativize(
371383
/// sourcemeta::core::parse_json(R"JSON({
372384
/// "$id": "https://www.example.com/schema",
373385
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
374-
/// "$ref": "another",
386+
/// "$ref": "https://www.example.com/another",
375387
/// })JSON");
376388
///
377-
/// sourcemeta::core::unidentify(schema,
389+
/// sourcemeta::core::reference_visit(schema,
378390
/// sourcemeta::core::schema_official_walker,
379-
/// sourcemeta::core::official_resolver);
391+
/// sourcemeta::core::official_resolver,
392+
/// sourcemeta::core::reference_visitor_relativize);
380393
///
381394
/// const sourcemeta::core::JSON expected =
382395
/// sourcemeta::core::parse_json(R"JSON({
396+
/// "$id": "https://www.example.com/schema",
383397
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
384-
/// "$ref": "https://www.example.com/another",
398+
/// "$ref": "another",
385399
/// })JSON");
386400
///
387401
/// assert(schema == expected);
388402
/// ```
389403
SOURCEMETA_CORE_JSONSCHEMA_EXPORT
390-
auto unidentify(
404+
auto reference_visitor_relativize(JSON &subschema, const URI &base,
405+
const JSON::String &vocabulary,
406+
const JSON::String &keyword,
407+
const JSON::String &value) -> void;
408+
409+
/// @ingroup jsonschema
410+
///
411+
/// A utility function to loop over every reference in a schema, allowing
412+
/// modifications to their subschemas if desired. Note that the consumer is
413+
/// responsible for not making the schema invalid. For example:
414+
///
415+
/// ```cpp
416+
/// #include <sourcemeta/core/json.h>
417+
/// #include <sourcemeta/core/jsonschema.h>
418+
/// #include <cassert>
419+
///
420+
/// sourcemeta::core::JSON schema =
421+
/// sourcemeta::core::parse_json(R"JSON({
422+
/// "$id": "https://www.example.com/schema",
423+
/// "$schema": "https://json-schema.org/draft/2020-12/schema",
424+
/// "$ref": "https://www.example.com/another",
425+
/// })JSON");
426+
///
427+
/// static auto visitor(JSON &subschema,
428+
/// const URI &base,
429+
/// const JSON::String &vocabulary,
430+
/// const JSON::String &keyword,
431+
/// const JSON::String &value) -> void {
432+
/// sourcemeta::core::prettify(subschema, std::cerr);
433+
/// std::cerr << "\n";
434+
/// std::cerr << base.recompose() << "\n";
435+
/// std::cerr << vocabulary << "\n";
436+
/// std::cerr << keyword << "\n";
437+
/// std::cerr << value << "\n";
438+
/// }
439+
///
440+
/// sourcemeta::core::reference_visit(schema,
441+
/// sourcemeta::core::schema_official_walker,
442+
/// sourcemeta::core::official_resolver,
443+
/// visitor);
444+
/// ```
445+
SOURCEMETA_CORE_JSONSCHEMA_EXPORT
446+
auto reference_visit(
391447
JSON &schema, const SchemaWalker &walker, const SchemaResolver &resolver,
392-
const std::optional<std::string> &default_dialect = std::nullopt) -> void;
448+
const SchemaVisitorReference &callback,
449+
const std::optional<std::string> &default_dialect = std::nullopt,
450+
const std::optional<std::string> &default_id = std::nullopt) -> void;
393451

394452
} // namespace sourcemeta::core
395453

src/core/jsonschema/jsonschema.cc

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,3 +547,69 @@ 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+
callback(subschema, base, walker_result.vocabulary.value(),
591+
property.first, property.second.to_string());
592+
}
593+
}
594+
}
595+
596+
auto sourcemeta::core::reference_visitor_relativize(
597+
sourcemeta::core::JSON &subschema, const sourcemeta::core::URI &base,
598+
const sourcemeta::core::JSON::String &vocabulary,
599+
const sourcemeta::core::JSON::String &keyword,
600+
const sourcemeta::core::JSON::String &value) -> 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+
sourcemeta::core::URI reference{value};
609+
reference.relative_to(base);
610+
reference.canonicalize();
611+
612+
if (reference.is_relative()) {
613+
subschema.assign(keyword, sourcemeta::core::JSON{reference.recompose()});
614+
}
615+
}

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)