Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
jsonschema_frame.h
1#ifndef SOURCEMETA_CORE_JSONSCHEMA_FRAME_H_
2#define SOURCEMETA_CORE_JSONSCHEMA_FRAME_H_
3
4#ifndef SOURCEMETA_CORE_JSONSCHEMA_EXPORT
5#include <sourcemeta/core/jsonschema_export.h>
6#endif
7
8// NOLINTBEGIN(misc-include-cleaner)
9#include <sourcemeta/core/jsonschema_error.h>
10#include <sourcemeta/core/jsonschema_types.h>
11// NOLINTEND(misc-include-cleaner)
12
13#include <sourcemeta/core/json.h>
14#include <sourcemeta/core/jsonpointer.h>
15
16#include <concepts> // std::invocable
17#include <cstdint> // std::uint8_t
18#include <deque> // std::deque
19#include <functional> // std::reference_wrapper
20#include <limits> // std::numeric_limits
21#include <map> // std::map
22#include <memory> // std::unique_ptr
23#include <optional> // std::optional
24#include <utility> // std::pair
25#include <vector> // std::vector
26
27namespace sourcemeta::core {
28
57class SOURCEMETA_CORE_JSONSCHEMA_EXPORT SchemaFrame {
58public:
71 enum class Mode : std::uint8_t {
73 Root,
75 Locations,
77 References,
79 Pointers
80 };
81
84 enum class IdentifierMode : std::uint8_t {
86 Additional,
88 Fallback
89 };
90
92
93 // We rely on internal caches that would be dangling otherwise
94 SchemaFrame(const SchemaFrame &) = delete;
95 auto operator=(const SchemaFrame &) -> SchemaFrame & = delete;
96 SchemaFrame(SchemaFrame &&) = delete;
97 auto operator=(SchemaFrame &&) -> SchemaFrame & = delete;
98
100 [[nodiscard]] auto mode() const noexcept -> Mode { return this->mode_; }
101
103 struct Reference {
105 std::string_view original;
106 // TODO: This one is tricky to turn into a view, as there is no
107 // location entry to point to if it is an external unresolved reference
114 std::string_view base;
116 std::optional<std::string_view> fragment;
117 };
118
119#if defined(__GNUC__)
120#pragma GCC diagnostic push
121// GCC believes that a member of an enum class (which is namespaced by
122// definition), can shadow an alias defined even on a different namespace.
123#pragma GCC diagnostic ignored "-Wshadow"
124#endif
127 enum class LocationType : std::uint8_t {
129 Resource,
131 Anchor,
135 Subschema
136 };
137#if defined(__GNUC__)
138#pragma GCC diagnostic pop
139#endif
140
142 struct Location {
144 std::optional<sourcemeta::core::WeakPointer> parent;
148 std::string_view base;
152 std::size_t relative_pointer;
154 std::string_view dialect;
160 bool orphan;
161 };
162
164 using Paths = std::vector<sourcemeta::core::WeakPointer>;
165
167 [[nodiscard]] auto to_json(
168 const SchemaResolver &resolver,
169 const std::optional<sourcemeta::core::PointerPositionTracker> &tracker =
170 std::nullopt) const -> sourcemeta::core::JSON;
171
203 const Mode mode, const sourcemeta::core::JSON &root,
204 const SchemaWalker &walker, const SchemaResolver &resolver,
205 std::string_view default_dialect = "", std::string_view default_id = "",
208 std::string_view default_base = "",
209 std::uint64_t max_locations = std::numeric_limits<std::uint64_t>::max());
210
212 [[nodiscard]] auto
213 reference(const SchemaReferenceType type,
214 const sourcemeta::core::WeakPointer &pointer) const
215 -> std::optional<std::reference_wrapper<const Reference>>;
216
218 [[nodiscard]] auto location(const SchemaReferenceType type,
219 const std::string_view uri) const
220 -> std::optional<std::reference_wrapper<const Location>>;
221
223 [[nodiscard]] auto location_count() const noexcept -> std::size_t;
224
226 [[nodiscard]] auto reference_count() const noexcept -> std::size_t;
227
229 [[nodiscard]] auto has_dynamic_references() const noexcept -> bool;
230
232 [[nodiscard]] auto standalone() const noexcept -> bool;
233
235 [[nodiscard]] auto root() const noexcept
236 -> const sourcemeta::core::JSON::String &;
237
240 [[nodiscard]] auto root_location() const
241 -> std::optional<std::reference_wrapper<const Location>>;
242
245 [[nodiscard]] auto metaschema(const SchemaResolver &resolver) const
246 -> const sourcemeta::core::JSON &;
247
253 [[nodiscard]] auto vocabularies(const Location &location,
254 const SchemaResolver &resolver) const
255 -> const SchemaVocabularies &;
256
258 [[nodiscard]] auto
260 const sourcemeta::core::WeakPointer &relative_schema_location =
261 sourcemeta::core::EMPTY_WEAK_POINTER) const
262 -> sourcemeta::core::JSON::String;
263
265 [[nodiscard]] auto
267 const sourcemeta::core::WeakPointer &relative_schema_location) const
268 -> const Location &;
269
271 [[nodiscard]] auto traverse(const std::string_view uri) const
272 -> std::optional<std::reference_wrapper<const Location>>;
273
275 [[nodiscard]] auto
276 traverse(const sourcemeta::core::WeakPointer &pointer) const
277 -> std::optional<std::reference_wrapper<const Location>>;
278
280 [[nodiscard]] auto traverse(const sourcemeta::core::WeakPointer &pointer,
281 const LocationType type) const
282 -> std::optional<std::reference_wrapper<const Location>>;
283
285 [[nodiscard]] auto uri(const sourcemeta::core::WeakPointer &pointer) const
286 -> std::optional<
287 std::reference_wrapper<const sourcemeta::core::JSON::String>>;
288
290 [[nodiscard]] auto
292 const sourcemeta::core::WeakPointer &relative_schema_location =
293 sourcemeta::core::EMPTY_WEAK_POINTER) const
294 -> std::pair<SchemaReferenceType,
295 std::optional<std::reference_wrapper<const Location>>>;
296
299 template <std::invocable<const Location &> F>
300 auto for_each_subschema(const F &callback) const -> void {
301 for (const auto &entry : this->locations_) {
302 if (entry.second.type == LocationType::Resource ||
303 entry.second.type == LocationType::Subschema) {
304 callback(entry.second);
305 }
306 }
307 }
308
311 template <std::invocable<const Location &> F>
313 const F &callback) const -> void {
314 for (const auto &entry : this->locations_) {
315 if ((entry.second.type == LocationType::Resource ||
316 entry.second.type == LocationType::Subschema) &&
317 entry.second.pointer.size() > pointer.size() &&
318 entry.second.pointer.starts_with(pointer)) {
319 callback(entry.second);
320 }
321 }
322 }
323
325 template <std::predicate<const Location &> F>
326 [[nodiscard]] auto any_subschema(const F &predicate) const -> bool {
327 for (const auto &entry : this->locations_) {
328 if ((entry.second.type == LocationType::Resource ||
329 entry.second.type == LocationType::Subschema) &&
330 predicate(entry.second)) {
331 return true;
332 }
333 }
334
335 return false;
336 }
337
340 template <std::predicate<const Location &> F>
341 [[nodiscard]] auto
343 const F &predicate) const -> bool {
344 for (const auto &entry : this->locations_) {
345 if ((entry.second.type == LocationType::Resource ||
346 entry.second.type == LocationType::Subschema) &&
347 entry.second.pointer.size() > pointer.size() &&
348 entry.second.pointer.starts_with(pointer) &&
349 predicate(entry.second)) {
350 return true;
351 }
352 }
353
354 return false;
355 }
356
358 template <std::invocable<std::string_view, const Location &> F>
359 auto for_each_anchor(const SchemaReferenceType type, const F &callback) const
360 -> void {
361 for (const auto &entry : this->locations_) {
362 if (entry.first.first == type &&
363 entry.second.type == LocationType::Anchor) {
364 callback(entry.first.second, entry.second);
365 }
366 }
367 }
368
370 template <std::predicate<std::string_view, const Location &> F>
371 [[nodiscard]] auto any_anchor(const SchemaReferenceType type,
372 const F &predicate) const -> bool {
373 for (const auto &entry : this->locations_) {
374 if (entry.first.first == type &&
375 entry.second.type == LocationType::Anchor &&
376 predicate(entry.first.second, entry.second)) {
377 return true;
378 }
379 }
380
381 return false;
382 }
383
385 template <std::invocable<std::string_view, const Location &> F>
386 auto for_each_resource(const F &callback) const -> void {
387 for (const auto &entry : this->locations_) {
388 if (entry.second.type == LocationType::Resource) {
389 callback(entry.first.second, entry.second);
390 }
391 }
392 }
393
395 template <
396 std::invocable<SchemaReferenceType, std::string_view, const Location &> F>
397 auto for_each_location(const F &callback) const -> void {
398 for (const auto &entry : this->locations_) {
399 callback(entry.first.first, entry.first.second, entry.second);
400 }
401 }
402
404 template <
405 std::predicate<SchemaReferenceType, std::string_view, const Location &> F>
406 [[nodiscard]] auto any_location(const F &predicate) const -> bool {
407 for (const auto &entry : this->locations_) {
408 if (predicate(entry.first.first, entry.first.second, entry.second)) {
409 return true;
410 }
411 }
412
413 return false;
414 }
415
417 template <
419 const Reference &>
420 F>
421 auto for_each_reference(const F &callback) const -> void {
422 for (const auto &entry : this->references_) {
423 callback(entry.first.first, entry.first.second, entry.second);
424 }
425 }
426
428 template <
430 const Reference &>
431 F>
432 [[nodiscard]] auto any_reference(const F &predicate) const -> bool {
433 for (const auto &entry : this->references_) {
434 if (predicate(entry.first.first, entry.first.second, entry.second)) {
435 return true;
436 }
437 }
438
439 return false;
440 }
441
443 template <
445 const Reference &>
446 F>
448 const F &callback) const -> void {
449 for (const auto &entry : this->references_) {
450 if (entry.first.second.starts_with(pointer)) {
451 callback(entry.first.first, entry.first.second, entry.second);
452 }
453 }
454 }
455
458 template <
460 const Reference &>
461 F>
462 [[nodiscard]] auto
464 const F &predicate) const -> bool {
465 for (const auto &entry : this->references_) {
466 if (entry.first.second.starts_with(pointer) &&
467 predicate(entry.first.first, entry.first.second, entry.second)) {
468 return true;
469 }
470 }
471
472 return false;
473 }
474
479 template <
481 const Reference &>
482 F>
484 const F &callback) const -> void {
485 for (const auto &entry : this->references_) {
486 const auto destination{this->traverse(entry.second.destination)};
487 if (destination.has_value() &&
488 destination.value().get().pointer.starts_with(pointer)) {
489 callback(entry.first.first, entry.first.second, entry.second);
490 }
491 }
492 }
493
496 template <
498 const Reference &>
499 F>
500 [[nodiscard]] auto
502 const F &predicate) const -> bool {
503 for (const auto &entry : this->references_) {
504 const auto destination{this->traverse(entry.second.destination)};
505 if (destination.has_value() &&
506 destination.value().get().pointer.starts_with(pointer) &&
507 predicate(entry.first.first, entry.first.second, entry.second)) {
508 return true;
509 }
510 }
511
512 return false;
513 }
514
516 template <std::invocable<std::string_view> F>
517 auto for_each_resource_uri(const F &callback) const -> void {
518 for (const auto &[key, location] : this->locations_) {
519 if (location.type == LocationType::Resource) {
520 callback(key.second);
521 }
522 }
523 }
524
527 template <
528 std::invocable<const sourcemeta::core::WeakPointer &, const Reference &>
529 F>
530 auto for_each_unresolved_reference(const F &callback) const -> void {
531 for (const auto &[key, reference] : this->references_) {
532 if (!this->traverse(reference.destination).has_value()) {
533 callback(key.second, reference);
534 }
535 }
536 }
537
539 [[nodiscard]] auto
541
543 [[nodiscard]] auto
545 -> bool;
548 [[nodiscard]] auto
550 const sourcemeta::core::WeakPointer::Token &tail) const
551 -> bool;
552
554 [[nodiscard]] auto relative_instance_location(const Location &location) const
556
558 [[nodiscard]] auto is_reachable(const Location &base,
559 const Location &location,
560 const SchemaWalker &walker,
561 const SchemaResolver &resolver) const -> bool;
562
563private:
571 using References =
572 std::map<std::pair<SchemaReferenceType, sourcemeta::core::WeakPointer>,
573 Reference>;
574
575 using Locations =
576 // While it might seem weird that we namespace the location URIs with a
577 // reference type, it is essential for distinguishing schema resource URIs
578 // from `$recursiveRef: true` on another place of the schema schema
579 // resource, as otherwise they would both have the exact same URI, but
580 // point to different places.
581 std::map<std::pair<SchemaReferenceType, sourcemeta::core::JSON::String>,
582 Location>;
583
584 Mode mode_;
585// Exporting symbols that depends on the standard C++ library is considered
586// safe.
587// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN
588#if defined(_MSC_VER)
589#pragma warning(disable : 4251 4275)
590#endif
592 // A reference may target a place that no schema location covers, in which
593 // case framing materialises one for it. Unlike every other location, the
594 // tokens of those pointers are not borrowed from the analysed document, so
595 // they have to be declared here to outlive the locations that borrow them
596 std::deque<sourcemeta::core::Pointer> reference_pointers_;
597 Locations locations_;
598 References references_;
599 // What the frame derives rather than is, kept out of line so that this
600 // declaration stays down to the schema it framed
601 struct Cache;
602 std::unique_ptr<Cache> cache_;
603#if defined(_MSC_VER)
604#pragma warning(default : 4251 4275)
605#endif
606};
607
608} // namespace sourcemeta::core
609
610#endif
std::basic_string< Char, CharTraits, Allocator< Char > > String
The string type used by the JSON document.
Definition json_value.h:52
Definition json_value.h:39
GenericToken< std::reference_wrapper< const std::string >, PropertyHashJSON< JSON::String > > Token
Definition jsonpointer_pointer.h:28
const WeakPointer EMPTY_WEAK_POINTER
Definition jsonpointer.h:54
GenericPointer< JSON::String, PropertyHashJSON< JSON::String > > Pointer
Definition jsonpointer.h:39
GenericPointer< std::reference_wrapper< const std::string >, PropertyHashJSON< JSON::String > > WeakPointer
Definition jsonpointer.h:44
std::vector< sourcemeta::core::WeakPointer > Paths
A list of paths to frame within a schema wrapper.
Definition jsonschema_frame.h:164
auto for_each_resource(const F &callback) const -> void
Iterate over every schema resource, along with its URI.
Definition jsonschema_frame.h:386
auto for_each_subschema_under(const sourcemeta::core::WeakPointer &pointer, const F &callback) const -> void
Definition jsonschema_frame.h:312
auto for_each_anchor(const SchemaReferenceType type, const F &callback) const -> void
Iterate over every anchor of the given kind, along with its URI.
Definition jsonschema_frame.h:359
auto vocabularies(const Location &location, const SchemaResolver &resolver) const -> const SchemaVocabularies &
auto for_each_reference(const F &callback) const -> void
Iterate over every reference, along with the pointer it originates from.
Definition jsonschema_frame.h:421
auto any_reference_from(const sourcemeta::core::WeakPointer &pointer, const F &predicate) const -> bool
Definition jsonschema_frame.h:463
auto standalone() const noexcept -> bool
Check whether the analysed schema has no external references.
auto metaschema(const SchemaResolver &resolver) const -> const sourcemeta::core::JSON &
auto for_each_location(const F &callback) const -> void
Iterate over every location, whatever kind it is.
Definition jsonschema_frame.h:397
auto any_anchor(const SchemaReferenceType type, const F &predicate) const -> bool
Check whether any anchor of the given kind satisfies the predicate.
Definition jsonschema_frame.h:371
auto reference_count() const noexcept -> std::size_t
The number of references in the frame.
auto any_reference_into(const sourcemeta::core::WeakPointer &pointer, const F &predicate) const -> bool
Definition jsonschema_frame.h:501
auto has_references_through(const sourcemeta::core::WeakPointer &pointer, const sourcemeta::core::WeakPointer::Token &tail) const -> bool
auto to_json(const SchemaResolver &resolver, const std::optional< sourcemeta::core::PointerPositionTracker > &tracker=std::nullopt) const -> sourcemeta::core::JSON
Export the frame as JSON.
IdentifierMode
Definition jsonschema_frame.h:84
@ Additional
Register the default identifier in addition to the schema's own.
Definition jsonschema_frame.h:86
auto for_each_reference_from(const sourcemeta::core::WeakPointer &pointer, const F &callback) const -> void
Iterate over every reference that originates at or below the given pointer.
Definition jsonschema_frame.h:447
auto has_dynamic_references() const noexcept -> bool
Check whether the analysed schema makes use of dynamic referencing at all.
auto dereference(const Location &location, const sourcemeta::core::WeakPointer &relative_schema_location=sourcemeta::core::EMPTY_WEAK_POINTER) const -> std::pair< SchemaReferenceType, std::optional< std::reference_wrapper< const Location > > >
Try to dereference a reference location into its destination location.
auto is_reachable(const Location &base, const Location &location, const SchemaWalker &walker, const SchemaResolver &resolver) const -> bool
Determines if a location could be evaluated during validation.
auto any_reference(const F &predicate) const -> bool
Check whether any reference satisfies the predicate.
Definition jsonschema_frame.h:432
SchemaFrame(const Mode mode, const sourcemeta::core::JSON &root, const SchemaWalker &walker, const SchemaResolver &resolver, std::string_view default_dialect="", std::string_view default_id="", IdentifierMode identifier_mode=IdentifierMode::Additional, const Paths &paths={sourcemeta::core::EMPTY_WEAK_POINTER}, std::string_view default_base="", std::uint64_t max_locations=std::numeric_limits< std::uint64_t >::max())
auto any_location(const F &predicate) const -> bool
Check whether any location satisfies the predicate.
Definition jsonschema_frame.h:406
Mode
Definition jsonschema_frame.h:71
auto mode() const noexcept -> Mode
Query the current mode that the schema frame was configured with.
Definition jsonschema_frame.h:100
auto for_each_subschema(const F &callback) const -> void
Definition jsonschema_frame.h:300
auto has_references_through(const sourcemeta::core::WeakPointer &pointer) const -> bool
Check if there are any references that go through a given location pointer.
auto any_subschema_under(const sourcemeta::core::WeakPointer &pointer, const F &predicate) const -> bool
Definition jsonschema_frame.h:342
auto for_each_unresolved_reference(const F &callback) const -> void
Definition jsonschema_frame.h:530
auto has_references_to(const sourcemeta::core::WeakPointer &pointer) const -> bool
Check if there are any references to a given location pointer.
auto root_location() const -> std::optional< std::reference_wrapper< const Location > >
auto any_subschema(const F &predicate) const -> bool
Check whether any schema resource or subschema satisfies the predicate.
Definition jsonschema_frame.h:326
auto uri(const Location &location, const sourcemeta::core::WeakPointer &relative_schema_location=sourcemeta::core::EMPTY_WEAK_POINTER) const -> sourcemeta::core::JSON::String
Get the URI associated with a location entry.
auto location(const SchemaReferenceType type, const std::string_view uri) const -> std::optional< std::reference_wrapper< const Location > >
Get a specific location entry by reference type and URI.
auto relative_instance_location(const Location &location) const -> sourcemeta::core::WeakPointer
Get the relative instance location pointer for a given location entry.
auto root() const noexcept -> const sourcemeta::core::JSON::String &
Get the root schema identifier (empty if none).
auto for_each_reference_into(const sourcemeta::core::WeakPointer &pointer, const F &callback) const -> void
Definition jsonschema_frame.h:483
auto location_count() const noexcept -> std::size_t
The number of locations in the frame.
auto for_each_resource_uri(const F &callback) const -> void
Iterate over all resource URIs in the frame.
Definition jsonschema_frame.h:517
auto traverse(const Location &location, const sourcemeta::core::WeakPointer &relative_schema_location) const -> const Location &
Get the location associated by traversing a pointer from another location.
Definition jsonschema_frame.h:57
LocationType
Definition jsonschema_frame.h:127
SchemaReferenceType
Definition jsonschema_types.h:45
std::function< SchemaResolverResult(std::string_view)> SchemaResolver
Definition jsonschema_types.h:41
std::function< const SchemaWalkerResult &( std::string_view, const SchemaVocabularies &)> SchemaWalker
Definition jsonschema_types.h:254
SchemaBaseDialect
Definition jsonschema_types.h:54
@ Subschema
A subschema that declares no identifier of its own.
Definition jsonschema_frame.h:135
@ Anchor
An anchor declared within a schema resource.
Definition jsonschema_frame.h:131
@ Resource
A schema resource, as introduced by an identifier.
Definition jsonschema_frame.h:129
@ Reference
The JSON Schema keyword is a reference.
Definition jsonschema_types.h:110
Definition jsonschema_vocabularies.h:30
A location entry.
Definition jsonschema_frame.h:142
std::size_t relative_pointer
How many tokens of the pointer fall inside the enclosing resource.
Definition jsonschema_frame.h:152
std::optional< sourcemeta::core::WeakPointer > parent
The location of the closest enclosing schema resource, if any.
Definition jsonschema_frame.h:144
std::string_view dialect
The meta-schema in effect at this entry.
Definition jsonschema_frame.h:154
std::string_view base
The base URI that this entry resolves relative references against.
Definition jsonschema_frame.h:148
bool orphan
Whether nothing in the document reaches this entry.
Definition jsonschema_frame.h:160
bool property_name
Whether this entry sits under a keyword that applies to property names.
Definition jsonschema_frame.h:158
LocationType type
What kind of place in the document this entry describes.
Definition jsonschema_frame.h:146
SchemaBaseDialect base_dialect
The base dialect in effect at this entry.
Definition jsonschema_frame.h:156
A reference that the schema declares, as found by framing.
Definition jsonschema_frame.h:103
std::string_view original
The reference exactly as the schema spells it.
Definition jsonschema_frame.h:105
sourcemeta::core::JSON::String destination
The absolute URI that the reference resolves to.
Definition jsonschema_frame.h:109
std::optional< std::string_view > fragment
The fragment of the destination, if it declares one.
Definition jsonschema_frame.h:116
std::string_view base
Definition jsonschema_frame.h:114