diff --git a/DEPENDENCIES b/DEPENDENCIES index 0cce3fe..d6a42a3 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,5 +1,5 @@ vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4 noa https://github.com/sourcemeta/noa 7e26abce7a4e31e86a16ef2851702a56773ca527 -jsontoolkit https://github.com/sourcemeta/jsontoolkit 6c78f131029c647be79cc5da5874fa8d50415cde +jsontoolkit https://github.com/sourcemeta/jsontoolkit e86ad1e333c11b6fc2659a0f32ae883ef1040e20 hydra https://github.com/sourcemeta/hydra 3c53d3fdef79e9ba603d48470a508cc45472a0dc alterschema https://github.com/sourcemeta/alterschema a31722f04ae2d7e57f2fe5bbb0613670866c0840 diff --git a/src/command_bundle.cc b/src/command_bundle.cc index 2d0e791..9557508 100644 --- a/src/command_bundle.cc +++ b/src/command_bundle.cc @@ -26,14 +26,12 @@ auto sourcemeta::jsonschema::cli::bundle( sourcemeta::jsontoolkit::bundle( schema, sourcemeta::jsontoolkit::default_schema_walker, resolver(options, options.contains("h") || options.contains("http")), - sourcemeta::jsontoolkit::BundleOptions::WithoutIdentifiers) - .wait(); + sourcemeta::jsontoolkit::BundleOptions::WithoutIdentifiers); } else { sourcemeta::jsontoolkit::bundle( schema, sourcemeta::jsontoolkit::default_schema_walker, resolver(options, options.contains("h") || options.contains("http")), - sourcemeta::jsontoolkit::BundleOptions::Default) - .wait(); + sourcemeta::jsontoolkit::BundleOptions::Default); } sourcemeta::jsontoolkit::prettify( diff --git a/src/command_test.cc b/src/command_test.cc index 78ad3a3..f5888cd 100644 --- a/src/command_test.cc +++ b/src/command_test.cc @@ -144,7 +144,8 @@ auto sourcemeta::jsonschema::cli::test( schema.value(), sourcemeta::jsontoolkit::default_schema_walker, test_resolver, sourcemeta::jsontoolkit::default_schema_compiler); } catch (const sourcemeta::jsontoolkit::SchemaReferenceError &error) { - if (error.location().empty() && error.id() == schema_uri.recompose()) { + if (error.location() == sourcemeta::jsontoolkit::Pointer{"$ref"} && + error.id() == schema_uri.recompose()) { std::cout << "\n"; throw sourcemeta::jsontoolkit::SchemaResolutionError( test.at("target").to_string(), @@ -237,8 +238,9 @@ auto sourcemeta::jsonschema::cli::test( return EXIT_FAILURE; } + const std::string ref{"$ref"}; sourcemeta::jsontoolkit::SchemaCompilerErrorTraceOutput output{ - schema.value(), {"$ref"}}; + schema.value(), {std::cref(ref)}}; const auto case_result{sourcemeta::jsontoolkit::evaluate( schema_template, get_data(test_case, entry.first.parent_path(), verbose), diff --git a/test/compile/pass_1.sh b/test/compile/pass_1.sh index ac1b564..dcc1554 100755 --- a/test/compile/pass_1.sh +++ b/test/compile/pass_1.sh @@ -21,51 +21,19 @@ EOF cat << 'EOF' > "$TMP/expected.json" [ { - "category": "loop", - "type": "properties-match", + "category": "assertion", + "type": "property-type-strict", "value": { "category": "value", - "type": "named-indexes", - "value": { - "foo": 0 - } + "type": "type", + "value": "string" }, "schemaResource": "", - "absoluteKeywordLocation": "#/properties", - "relativeSchemaLocation": "/properties", - "relativeInstanceLocation": "", + "absoluteKeywordLocation": "#/properties/foo/type", + "relativeSchemaLocation": "/properties/foo/type", + "relativeInstanceLocation": "/foo", "report": true, - "dynamic": false, - "children": [ - { - "category": "logical", - "type": "and", - "value": null, - "schemaResource": "", - "absoluteKeywordLocation": "#/properties", - "relativeSchemaLocation": "", - "relativeInstanceLocation": "", - "report": false, - "dynamic": false, - "children": [ - { - "category": "assertion", - "type": "type-strict", - "value": { - "category": "value", - "type": "type", - "value": "string" - }, - "schemaResource": "", - "absoluteKeywordLocation": "#/properties/foo/type", - "relativeSchemaLocation": "/foo/type", - "relativeInstanceLocation": "/foo", - "report": true, - "dynamic": false - } - ] - } - ] + "dynamic": false } ] EOF diff --git a/test/metaschema/fail_directory.sh b/test/metaschema/fail_directory.sh index 5a7a0f3..92d8270 100755 --- a/test/metaschema/fail_directory.sh +++ b/test/metaschema/fail_directory.sh @@ -32,9 +32,6 @@ error: Schema validation failure The string value "object" was expected to equal one of the following values: "array", "boolean", "integer", "null", "number", "object", and "string" at instance location "/type" at evaluate path "/properties/type/anyOf/0/\$ref/enum" - The string value was expected to validate against the statically referenced schema - at instance location "/type" - at evaluate path "/properties/type/anyOf/0/\$ref" The value was expected to be of type array but it was of type string at instance location "/type" at evaluate path "/properties/type/anyOf/1/type" diff --git a/test/metaschema/fail_single.sh b/test/metaschema/fail_single.sh index 82799ef..3a2ed80 100755 --- a/test/metaschema/fail_single.sh +++ b/test/metaschema/fail_single.sh @@ -23,9 +23,6 @@ error: Schema validation failure The string value "object" was expected to equal one of the following values: "array", "boolean", "integer", "null", "number", "object", and "string" at instance location "/type" at evaluate path "/properties/type/anyOf/0/\$ref/enum" - The string value was expected to validate against the statically referenced schema - at instance location "/type" - at evaluate path "/properties/type/anyOf/0/\$ref" The value was expected to be of type array but it was of type string at instance location "/type" at evaluate path "/properties/type/anyOf/1/type" diff --git a/test/validate/fail_draft4.sh b/test/validate/fail_draft4.sh index ae43eef..46b2281 100755 --- a/test/validate/fail_draft4.sh +++ b/test/validate/fail_draft4.sh @@ -33,9 +33,6 @@ error: Schema validation failure The value was expected to be of type string but it was of type integer at instance location "/foo" at evaluate path "/properties/foo/type" - The object value was expected to validate against the single defined property subschema - at instance location "" - at evaluate path "/properties" EOF diff "$TMP/stderr.txt" "$TMP/expected.txt" diff --git a/test/validate/fail_draft6.sh b/test/validate/fail_draft6.sh index 98bc14b..27a4590 100755 --- a/test/validate/fail_draft6.sh +++ b/test/validate/fail_draft6.sh @@ -33,9 +33,6 @@ error: Schema validation failure The value was expected to be of type string but it was of type integer at instance location "/foo" at evaluate path "/properties/foo/type" - The object value was expected to validate against the single defined property subschema - at instance location "" - at evaluate path "/properties" EOF diff "$TMP/stderr.txt" "$TMP/expected.txt" diff --git a/test/validate/fail_draft7.sh b/test/validate/fail_draft7.sh index 0f34fe8..d5160ea 100755 --- a/test/validate/fail_draft7.sh +++ b/test/validate/fail_draft7.sh @@ -33,9 +33,6 @@ error: Schema validation failure The value was expected to be of type string but it was of type integer at instance location "/foo" at evaluate path "/properties/foo/type" - The object value was expected to validate against the single defined property subschema - at instance location "" - at evaluate path "/properties" EOF diff "$TMP/stderr.txt" "$TMP/expected.txt" diff --git a/test/validate/fail_many.sh b/test/validate/fail_many.sh index 4134715..8f48304 100755 --- a/test/validate/fail_many.sh +++ b/test/validate/fail_many.sh @@ -45,9 +45,6 @@ error: Schema validation failure The value was expected to be of type string but it was of type integer at instance location "/foo" at evaluate path "/properties/foo/type" - The object value was expected to validate against the single defined property subschema - at instance location "" - at evaluate path "/properties" EOF diff "$TMP/stderr.txt" "$TMP/expected.txt" diff --git a/test/validate/fail_many_verbose.sh b/test/validate/fail_many_verbose.sh index 90e41af..89afd42 100755 --- a/test/validate/fail_many_verbose.sh +++ b/test/validate/fail_many_verbose.sh @@ -47,9 +47,6 @@ error: Schema validation failure The value was expected to be of type string but it was of type integer at instance location "/foo" at evaluate path "/properties/foo/type" - The object value was expected to validate against the single defined property subschema - at instance location "" - at evaluate path "/properties" ok: $(realpath "$TMP")/instance_3.json matches $(realpath "$TMP")/schema.json EOF diff --git a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h index 1330eee..b9d2b77 100644 --- a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h +++ b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_object.h @@ -35,9 +35,27 @@ template class JSONObject { // Operators // We cannot default given that this class references // a JSON "value" as an incomplete type - auto operator<(const JSONObject &) const noexcept -> bool { + + auto operator<(const JSONObject &other) const noexcept -> bool { + // The `std::unordered_map` container, by definition, does not provide + // ordering. However, we still want some level of ordering to allow + // arrays of objects to be sorted. + + // First try a size comparison + if (this->data.size() != other.data.size()) { + return this->data.size() < other.data.size(); + } + + // Otherwise do value comparison for common properties + for (const auto &[key, value] : this->data) { + if (other.data.contains(key) && value < other.data.at(key)) { + return true; + } + } + return false; } + auto operator<=(const JSONObject &other) const noexcept -> bool { return this->data <= other.data; } diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h index fe789e6..73196bb 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer.h @@ -37,6 +37,10 @@ using WeakPointer = GenericPointer>; /// A global constant instance of the empty JSON Pointer. const Pointer empty_pointer; +/// @ingroup jsonpointer +/// A global constant instance of the empty JSON WeakPointer. +const WeakPointer empty_weak_pointer; + /// @ingroup jsonpointer /// Get a value from a JSON document using a JSON Pointer (`const` overload). /// @@ -59,6 +63,67 @@ const Pointer empty_pointer; SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT auto get(const JSON &document, const Pointer &pointer) -> const JSON &; +/// @ingroup jsonpointer +/// Get a value from a JSON document using a JSON WeakPointer (`const` +/// overload). +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"[ { \"foo\": 1 }, { \"bar\": 2 } ]"}; +/// const sourcemeta::jsontoolkit::JSON document = +/// sourcemeta::jsontoolkit::parse(stream); +/// +/// const std::string bar = "bar"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{1, std::cref(bar)}; +/// const sourcemeta::jsontoolkit::JSON &value{ +/// sourcemeta::jsontoolkit::get(document, pointer)}; +/// assert(value.is_integer()); +/// assert(value.to_integer() == 2); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto get(const JSON &document, const WeakPointer &pointer) -> const JSON &; + +/// @ingroup jsonpointer +/// Check that a path represented by a JSON Pointer exists in the given +/// document. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"[ { \"foo\": 1 }, { \"bar\": 2 } ]"}; +/// const auto document{sourcemeta::jsontoolkit::parse(stream)}; +/// const sourcemeta::jsontoolkit::Pointer pointer{1, "bar"}; +/// assert(sourcemeta::jsontoolkit::has(document, pointer)); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto has(const JSON &document, const Pointer &pointer) -> bool; + +/// @ingroup jsonpointer +/// Check that a path represented by a JSON WeakPointer exists in the given +/// document. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"[ { \"foo\": 1 }, { \"bar\": 2 } ]"}; +/// const auto document{sourcemeta::jsontoolkit::parse(stream)}; +/// const std::string bar = "bar"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{1, std::cref(bar)}; +/// assert(sourcemeta::jsontoolkit::has(document, pointer)); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto has(const JSON &document, const WeakPointer &pointer) -> bool; + /// @ingroup jsonpointer /// Get a value from a JSON document using a JSON Pointer (non-`const` /// overload). @@ -98,13 +163,38 @@ auto get(JSON &document, const Pointer &pointer) -> JSON &; /// sourcemeta::jsontoolkit::parse(stream); /// /// const sourcemeta::jsontoolkit::JSON &value{ -/// sourcemeta::jsontoolkit::get(document, "bar")}; +/// sourcemeta::jsontoolkit::get(document, +/// sourcemeta::jsontoolkit::Pointer{"foo"})}; /// assert(value.is_integer()); -/// assert(value.to_integer() == 2); +/// assert(value.to_integer() == 1); /// ``` SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT auto get(const JSON &document, const Pointer::Token &token) -> const JSON &; +/// @ingroup jsonpointer +/// Get a value from a JSON document using a JSON WeakPointer token (`const` +/// overload). +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::istringstream stream{"{ \"foo\": 1 }"}; +/// const sourcemeta::jsontoolkit::JSON document = +/// sourcemeta::jsontoolkit::parse(stream); +/// +/// const std::string foo = "foo"; +/// const sourcemeta::jsontoolkit::JSON &value{ +/// sourcemeta::jsontoolkit::get(document, +/// sourcemeta::jsontoolkit::WeakPointer{std::cref(foo)})}; +/// assert(value.is_integer()); +/// assert(value.to_integer() == 1); +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto get(const JSON &document, const WeakPointer::Token &token) -> const JSON &; + /// @ingroup jsonpointer /// Get a value from a JSON document using a JSON Pointer token (non-`const` /// overload). @@ -233,6 +323,27 @@ auto stringify(const Pointer &pointer, std::basic_ostream &stream) -> void; +/// @ingroup jsonpointer +/// +/// Stringify the input JSON WeakPointer into a given C++ standard output +/// stream. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// const std::string foo = "foo"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{std::cref(foo)}; +/// std::ostringstream stream; +/// sourcemeta::jsontoolkit::stringify(pointer, stream); +/// std::cout << stream.str() << std::endl; +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto stringify(const WeakPointer &pointer, + std::basic_ostream &stream) + -> void; + /// @ingroup jsonpointer /// /// Stringify the input JSON Pointer into a C++ standard string. For example: @@ -251,6 +362,26 @@ auto to_string(const Pointer &pointer) -> std::basic_string>; +/// @ingroup jsonpointer +/// +/// Stringify the input JSON WeakPointer into a C++ standard string. For +/// example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// const std::string foo = "foo"; +/// const sourcemeta::jsontoolkit::WeakPointer pointer{foo}; +/// const std::string result{sourcemeta::jsontoolkit::to_string(pointer)}; +/// std::cout << result << std::endl; +/// ``` +SOURCEMETA_JSONTOOLKIT_JSONPOINTER_EXPORT +auto to_string(const WeakPointer &pointer) + -> std::basic_string>; + /// @ingroup jsonpointer /// /// Stringify the input JSON Pointer into a properly escaped URI fragment. For diff --git a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h index 44c1ab0..801021d 100644 --- a/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h +++ b/vendor/jsontoolkit/src/jsonpointer/include/sourcemeta/jsontoolkit/jsonpointer_pointer.h @@ -5,10 +5,12 @@ #include // std::copy, std::equal #include // assert +#include // std::reference_wrapper #include // std::initializer_list #include // std::advance, std::back_inserter #include // std::basic_ostringstream #include // std::runtime_error +#include // std::enable_if_t, std::is_same_v #include // std::move #include // std::vector @@ -238,6 +240,53 @@ template class GenericPointer { std::back_inserter(this->data)); } + /// Push a JSON Pointer into the back of a JSON WeakPointer. Make sure that + /// the pointer you are pushing remains alive for the duration of the + /// WeakPointer. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const std::string foo{"foo"}; + /// sourcemeta::jsontoolkit::WeakPointer pointer{std::cref(foo)}; + /// const sourcemeta::jsontoolkit::Pointer other{"bar", "baz"}; + /// pointer.push_back(other); + /// assert(pointer.size() == 3); + /// + /// assert(pointer.at(0).is_property()); + /// assert(pointer.at(1).is_property()); + /// assert(pointer.at(2).is_property()); + /// + /// assert(pointer.at(0).to_property() == "foo"); + /// assert(pointer.at(1).to_property() == "bar"); + /// assert(pointer.at(2).to_property() == "baz"); + /// ``` + template >>> + auto push_back(const GenericPointer &other) -> void { + if (other.empty()) { + return; + } else if (other.size() == 1) { + const auto &token{other.back()}; + if (token.is_property()) { + this->data.emplace_back(token.to_property()); + } else { + this->data.emplace_back(token.to_index()); + } + } else { + this->data.reserve(this->data.size() + other.size()); + for (const auto &token : other) { + if (token.is_property()) { + this->data.emplace_back(token.to_property()); + } else { + this->data.emplace_back(token.to_index()); + } + } + } + } + /// Push a property token into the back of a JSON Pointer. /// For example: /// diff --git a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc index 9c98c07..9664ea0 100644 --- a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc +++ b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc @@ -13,16 +13,14 @@ #include // std::move namespace { -template