From 7ac0dcb5facddf45ef26751bcb91452551715a0d Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Wed, 29 Jan 2025 09:59:20 -0800 Subject: [PATCH 01/10] CMake: Enable link-time optimization I did this so the JSON Schema benchmark could be fully optimized. --- CMakeLists.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index fcf7b485..c914622d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,19 @@ else() message(FATAL_ERROR "Unsupported platform ${CMAKE_SYSTEM_NAME}!") endif() + +if(NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + include(CheckIPOSupported) + check_ipo_supported(RESULT supported OUTPUT error) + if( supported ) + message(STATUS "LTO enabled") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(STATUS "LTO not supported: <${error}>") + endif() +endif() + + set(FLEECE_CXX_WARNINGS "") if(FLEECE_WARNINGS_HARDCORE) if (CMAKE_CXX_COMPILER_ID MATCHES Clang) From 8d1d045a440de964bb6d4437de4682af1f9d06e2 Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Fri, 17 Jan 2025 10:20:04 -0800 Subject: [PATCH 02/10] API: Added slice::UTF8Length() Code adapted from Litecore's StringUtils.cc --- API/fleece/slice.hh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/API/fleece/slice.hh b/API/fleece/slice.hh index 0517f3a4..a756dca4 100644 --- a/API/fleece/slice.hh +++ b/API/fleece/slice.hh @@ -183,6 +183,12 @@ namespace fleece { will not overflow the buffer. Returns false if the slice was truncated. */ inline bool toCString(char *buf, size_t bufSize) const noexcept; + /** Computes the number of characters in a UTF-8 string, and detects invalid UTF-8. + @returns On success, a pair with the character count and `true`. + On failure, a pair with the byte offset of the error and `false`. */ + std::pair UTF8Length() const FLPURE; + + constexpr operator FLSlice () const noexcept {return {buf, size};} #ifdef __APPLE__ @@ -581,6 +587,30 @@ namespace fleece { } + inline std::pair pure_slice::UTF8Length() const { + // See + size_t length = 0; + uint8_t const* cp = begin(), *e = end(); + while (cp < e) { + ++length; + uint8_t c = *cp; + if ((c & 0x80) == 0) [[likely]] + cp += 1; + else if ((c & 0xE0) == 0xC0) + cp += 2; + else if ((c & 0xF0) == 0xE0) + cp += 3; + else if ((c & 0xF8) == 0xF0) + cp += 4; + else [[unlikely]] + return {cp - begin(), false}; + } + if (cp != e) [[unlikely]] + return {cp - begin(), false}; + return {length, true}; + } + + #pragma mark COMPARISON & FIND: From b3c78ef14d005f03e94d11d92447a41b83927fdd Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Tue, 21 Jan 2025 17:05:35 -0800 Subject: [PATCH 03/10] API: Added FLEvalJSONPointer. Improved JSONPointer parsing - FLEvalJSONPointer() exposes impl::Path::evalJSONPointer(). - It now properly handles an empty path. - It now properly handles a path with a trailing "/". - It now properly handles those weird "~" escapes (see RFC 6901.) --- API/fleece/FLExpert.h | 5 +++++ Fleece/API_Impl/Fleece.cc | 8 ++++++++ Fleece/Core/Path.cc | 40 ++++++++++++++++++++++++--------------- Fleece/Core/Path.hh | 36 +++++++++++++++++++---------------- Fleece/Fleece.exp | 1 + 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/API/fleece/FLExpert.h b/API/fleece/FLExpert.h index 7b76a6e2..c7653863 100644 --- a/API/fleece/FLExpert.h +++ b/API/fleece/FLExpert.h @@ -305,6 +305,11 @@ extern "C" { /** @} */ + /** Follows a JSON Pointer [RFC 6901] from a root Value + @returns the resolved Value, or NULL if not found (or the JSON Pointer is invalid.) */ + FLEECE_PUBLIC FLValue FL_NULLABLE FLEvalJSONPointer(FLString jsonPointer, + FLValue root, + FLError* outError) FLAPI; /** @} */ diff --git a/Fleece/API_Impl/Fleece.cc b/Fleece/API_Impl/Fleece.cc index c6c7dcad..3b431b99 100644 --- a/Fleece/API_Impl/Fleece.cc +++ b/Fleece/API_Impl/Fleece.cc @@ -619,6 +619,14 @@ void FLKeyPath_DropComponents(FLKeyPath path, size_t n) FLAPI { path->drop(n); } +FLValue FL_NULLABLE FLEvalJSONPointer(FLString jsonPointer, FLValue root, FLError* outError) FLAPI { + try { + *outError = kFLNoError; + return Path::evalJSONPointer(jsonPointer, root); + } catchError(outError) + return nullptr; +} + #pragma mark - ENCODER: diff --git a/Fleece/Core/Path.cc b/Fleece/Core/Path.cc index 3d86a05c..58ba6531 100644 --- a/Fleece/Core/Path.cc +++ b/Fleece/Core/Path.cc @@ -134,39 +134,49 @@ namespace fleece { namespace impl { } - /*static*/ const Value* Path::evalJSONPointer(slice specifier, const Value *root) - { + /*static*/ const Value* Path::evalJSONPointer(slice specifier, const Value *root) { + // https://datatracker.ietf.org/doc/html/rfc6901 + if (specifier.empty()) + return root; slice_istream in(specifier); auto current = root; - throwIf(in.readByte() != '/', PathSyntaxError, "JSONPointer does not start with '/'"); - while (!in.eof()) { - if (!current) - return nullptr; - + throwIf(in.peekByte() != '/', PathSyntaxError, "JSONPointer does not start with '/'"); + while (current) { + in.readByte(); // skip slash auto slash = in.findByteOrEnd('/'); - slice_istream param(in.buf, slash); + slice param(in.buf, slash); switch(current->type()) { case kArray: { - auto i = param.readDecimal(); - if (_usuallyFalse(param.size > 0 || i > INT32_MAX)) + slice_istream paramStream(param); + auto i = paramStream.readDecimal(); + if (_usuallyFalse(paramStream.size > 0 || i > INT32_MAX)) FleeceException::_throw(PathSyntaxError, "Invalid array index in JSONPointer"); current = ((const Array*)current)->get((uint32_t)i); break; } case kDict: { - string key = param.asString(); - current = ((const Dict*)current)->get(key); + if (_usuallyFalse(param.findByte('~') != nullptr)) { + // Screwy JSONPointer escaping: + string paramStr(param); + size_t p; + while (string::npos != (p = paramStr.find("~1"))) + paramStr.replace(p, 2, "/"); + while (string::npos != (p = paramStr.find("~0"))) + paramStr.replace(p, 2, "~"); + current = ((const Dict*)current)->get(paramStr); + } else { + current = ((const Dict*)current)->get(param); + } break; } default: - current = nullptr; - break; + return nullptr; } if (slash == in.end()) break; - in.setStart(slash+1); + in.setStart(slash); } return current; } diff --git a/Fleece/Core/Path.hh b/Fleece/Core/Path.hh index 769dafad..15bcdd43 100644 --- a/Fleece/Core/Path.hh +++ b/Fleece/Core/Path.hh @@ -23,23 +23,26 @@ namespace fleece { namespace impl { /** Describes a location in a Fleece object tree, as a path from the root that follows dictionary properties and array elements. - Similar to a JSONPointer or an Objective-C KeyPath, but simpler (so far.) - It looks like "foo.bar[2][-3].baz" -- that is, properties prefixed with a ".", and array - indexes in brackets. (Negative indexes count from the end of the array.) - A leading JSONPath-like "$." is allowed but ignored. - A '\' can be used to escape a special character ('.', '[' or '$') at the start of a - property name (but not yet in the middle of a name.) */ + + The specifier syntax is similar to JSONPath or a Swift/Objective-C KeyPath, but simpler. + It looks like `foo.bar[2][-3].baz` -- that is, properties prefixed with a ".", and array + indexes in brackets. + + - A leading JSONPath-like "$." is allowed but ignored. + - Negative array indexes count from the end of the array; i.e. `[-1]` is the last element. + - A '\' can be used to escape a special character ('.', '[' or '$') at the start of a + property name (but not yet in the middle of a name.) */ class Path { public: class Element; - //// Construction from a string: (throws FleeceException with code PathSyntaxError) - - Path(slice specifier) {addComponents(specifier);} + /// Construction from a path specifier string. + /// @throws FleeceException (with code PathSyntaxError) + explicit Path(slice specifier) {addComponents(specifier);} - //// Step-by-step construction: + //---- Step-by-step construction: - Path() =default; + Path() =default; void addProperty(slice key); void addIndex(int index); void addComponents(slice components); @@ -50,7 +53,7 @@ namespace fleece { namespace impl { Path& operator += (const Path&); void drop(size_t numToDropFromStart); - //// Path element accessors: + //---- Path element accessors: const smallVector& path() const {return _path;} smallVector& path() {return _path;} @@ -60,7 +63,7 @@ namespace fleece { namespace impl { const Element& operator[] (size_t i) const {return _path[i];} Element& operator[] (size_t i) {return _path[i];} - //// Evaluation: + //---- Evaluation: const Value* eval(const Value *root) const noexcept; @@ -69,12 +72,13 @@ namespace fleece { namespace impl { const Value *root NONNULL); /** Evaluates a JSONPointer string (RFC 6901), which has a different syntax. - This can only be done one-shot since JSONPointer path components are ambiguous unless - the actual JSON is present (a number could be an array index or dict key.) */ + @note This can only be done one-shot, since JSONPointer path components are ambiguous + unless the actual JSON is present (digits could be an array index or dict key.) + @throws FleeceException (with code PathSyntaxError) */ static const Value* evalJSONPointer(slice specifier, const Value* root NONNULL); - //// Converting to string: + //---- Converting to string: explicit operator std::string(); diff --git a/Fleece/Fleece.exp b/Fleece/Fleece.exp index 100e972a..f6e63003 100644 --- a/Fleece/Fleece.exp +++ b/Fleece/Fleece.exp @@ -83,6 +83,7 @@ _FLEncoder_BytesWritten _FLEncoder_Finish _FLEncoder_GetError _FLEncoder_GetErrorMessage +_FLEvalJSONPointer _FLKeyPath_New _FLKeyPath_Free _FLKeyPath_Eval From ca111254103e8a3dee8bb372d1b266caf7414bdb Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Mon, 27 Jan 2025 16:54:38 -0800 Subject: [PATCH 04/10] API: Added Dict::get(FLDictKey) Exposes existing internal method. --- API/fleece/Fleece.hh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/API/fleece/Fleece.hh b/API/fleece/Fleece.hh index 7a6e75ad..6a18ec94 100644 --- a/API/fleece/Fleece.hh +++ b/API/fleece/Fleece.hh @@ -206,6 +206,9 @@ namespace fleece { inline Value get(Key &key) const; inline Value operator[] (Key &key) const {return get(key);} + inline Value get(FLDictKey&) const; + inline Value operator[] (FLDictKey& key) const {return get(key);} + class iterator : private FLDictIterator { public: inline iterator(Dict); @@ -576,6 +579,7 @@ namespace fleece { inline bool Dict::empty() const {return FLDict_IsEmpty(*this);} inline Value Dict::get(slice_NONNULL key) const {return FLDict_Get(*this, key);} inline Value Dict::get(Dict::Key &key) const{return FLDict_GetWithKey(*this, &key._key);} + inline Value Dict::get(FLDictKey &key) const{return FLDict_GetWithKey(*this, &key);} inline Dict::Key::Key(alloc_slice s) :_str(std::move(s)), _key(FLDictKey_Init(_str)) { } inline Dict::Key::Key(slice_NONNULL s) :Key(alloc_slice(s)) { } From fa857b0a9327f186c7deaee3f8e509e089a7aa78 Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Tue, 28 Jan 2025 13:27:59 -0800 Subject: [PATCH 05/10] API: Added FLDictIterator_BeginSK() As an optimization, allow a Dict iterator to be created with its SharedKeys already known; saves a lookup. --- API/fleece/Expert.hh | 2 ++ API/fleece/FLExpert.h | 10 +++++++++- API/fleece/Fleece.hh | 3 ++- Fleece/API_Impl/Fleece.cc | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/API/fleece/Expert.hh b/API/fleece/Expert.hh index 04063096..92eb543a 100644 --- a/API/fleece/Expert.hh +++ b/API/fleece/Expert.hh @@ -127,6 +127,8 @@ namespace fleece { FLSharedKeys FL_NULLABLE _sk {nullptr}; }; + inline Dict::iterator::iterator(Dict d, FLSharedKeys sk) {FLDictIterator_BeginShared(d, sk, this);} + //====== DEPRECATED: diff --git a/API/fleece/FLExpert.h b/API/fleece/FLExpert.h index c7653863..5c4d0d55 100644 --- a/API/fleece/FLExpert.h +++ b/API/fleece/FLExpert.h @@ -14,7 +14,7 @@ #ifndef _FLOBSCURE_H #define _FLOBSCURE_H -#include "FLValue.h" +#include "FLCollections.h" FL_ASSUME_NONNULL_BEGIN @@ -165,6 +165,14 @@ extern "C" { FLEECE_PUBLIC void FLSharedKeys_Release(FLSharedKeys FL_NULLABLE) FLAPI; + /** Initializes a Dict iterator, providing the Dict's FLSharedKeys instance. + This is an optimization that saves FLDictIterator_GetKeyString from the overhead of having + to look up the SharedKeys when the first shared key is encountered. + @warning The FLSharedKeys MUST be the same instance associated with the Dict, or incorrect + key strings will be returned. */ + FLEECE_PUBLIC void FLDictIterator_BeginShared(FLDict FL_NULLABLE, FLSharedKeys, FLDictIterator*) FLAPI; + + typedef struct _FLSharedKeyScope* FLSharedKeyScope; /** Registers a range of memory containing Fleece data that uses the given shared keys. diff --git a/API/fleece/Fleece.hh b/API/fleece/Fleece.hh index 6a18ec94..cee24bc6 100644 --- a/API/fleece/Fleece.hh +++ b/API/fleece/Fleece.hh @@ -211,7 +211,8 @@ namespace fleece { class iterator : private FLDictIterator { public: - inline iterator(Dict); + inline explicit iterator(Dict); + inline explicit iterator(Dict, FLSharedKeys); inline iterator(const FLDictIterator &i) :FLDictIterator(i) { } inline uint32_t count() const {return FLDictIterator_GetCount(this);} inline Value key() const; diff --git a/Fleece/API_Impl/Fleece.cc b/Fleece/API_Impl/Fleece.cc index 3b431b99..c629b777 100644 --- a/Fleece/API_Impl/Fleece.cc +++ b/Fleece/API_Impl/Fleece.cc @@ -332,6 +332,10 @@ void FLDictIterator_Begin(FLDict FL_NULLABLE d, FLDictIterator* i) FLAPI { // Note: this is safe even if d is null. } +void FLDictIterator_BeginShared(FLDict FL_NULLABLE d, FLSharedKeys sk, FLDictIterator* i) FLAPI { + new (i) Dict::iterator(d, sk); +} + FLValue FL_NULLABLE FLDictIterator_GetKey(const FLDictIterator* i) FLAPI { return ((Dict::iterator*)i)->key(); } From b8c00e1dac73486463c78e885d53a5224dba1a8e Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Thu, 23 Jan 2025 12:28:11 -0800 Subject: [PATCH 06/10] JSONConverter can now parse plain scalars The jsonsl parser we use follows an older version of the JSON spec which didn't allow a document to be a scalar value, only an array or object. I've run into this issue a few times lately -- for example, JSON Schema considers `true` or `false` a valid schema, but we can't parse it, so a few tests in their test suite break. I made a small change to JSONConverter to detect when the input isn't an array or object, and wrap it in `[...]` so that jsonsl can parse it. (But meanwhile it ignores the outer array when parsing.) --- Fleece/Core/JSONConverter.cc | 22 +++++++++++++++++++--- Fleece/Core/JSONConverter.hh | 3 ++- Tests/EncoderTests.cc | 28 ++++++++++++++++++++++++---- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/Fleece/Core/JSONConverter.cc b/Fleece/Core/JSONConverter.cc index 59f9059c..2daf2f4a 100644 --- a/Fleece/Core/JSONConverter.cc +++ b/Fleece/Core/JSONConverter.cc @@ -75,6 +75,17 @@ namespace fleece { namespace impl { _errorCode = NoError; _jsonError = JSONSL_ERROR_SUCCESS; _errorPos = 0; + _ignoreOuterBrace = false; + + std::string wrapped; + if (auto cp = json.findByteNotIn(" \t\r\n"); cp && *cp != '{' && *cp != '[') { + // Input is not a valid object or array, but it could be a scalar value. + // jsonsl does not support top-level scalars. Work around this by wrapping the input + // in `[...]` to make it an array, but tell my callbacks to ignore the outer braces. + wrapped = std::string("[").append(json).append("]"); + _input = wrapped; + _ignoreOuterBrace = true; + } _jsn->data = this; _jsn->action_callback_PUSH = writePushCallback; @@ -82,12 +93,15 @@ namespace fleece { namespace impl { _jsn->error_callback = errorCallback; jsonsl_enable_all_callbacks(_jsn); - jsonsl_feed(_jsn, (char*)json.buf, json.size); + jsonsl_feed(_jsn, (char*)_input.buf, _input.size); + if (_jsn->level > 0 && !_jsonError) { // Input is valid JSON so far, but truncated: _jsonError = kErrTruncatedJSON; _errorCode = JSONError; _errorPos = json.size; + } else if (_ignoreOuterBrace && _jsonError) { + --_errorPos; } jsonsl_reset(_jsn); return (_jsonError == JSONSL_ERROR_SUCCESS); @@ -104,7 +118,8 @@ namespace fleece { namespace impl { inline void JSONConverter::push(struct jsonsl_state_st *state) { switch (state->type) { case JSONSL_T_LIST: - _encoder.beginArray(); + if (!_ignoreOuterBrace || state->level != 1) [[likely]] + _encoder.beginArray(); break; case JSONSL_T_OBJECT: _encoder.beginDictionary(); @@ -187,7 +202,8 @@ namespace fleece { namespace impl { break; } case JSONSL_T_LIST: - _encoder.endArray(); + if (!_ignoreOuterBrace || state->level != 1) [[likely]] + _encoder.endArray(); break; case JSONSL_T_OBJECT: _encoder.endDictionary(); diff --git a/Fleece/Core/JSONConverter.hh b/Fleece/Core/JSONConverter.hh index b8af331e..e97b0c15 100644 --- a/Fleece/Core/JSONConverter.hh +++ b/Fleece/Core/JSONConverter.hh @@ -27,7 +27,7 @@ namespace fleece { namespace impl { /** Parses JSON data and writes the values in it to a Fleece encoder. */ class JSONConverter { public: - JSONConverter(Encoder&) noexcept; + explicit JSONConverter(Encoder&) noexcept; ~JSONConverter(); /** Parses JSON data and writes the values to the encoder. @@ -71,6 +71,7 @@ namespace fleece { namespace impl { std::string _errorMessage; size_t _errorPos {0}; // Byte index where parse error occurred slice _input; // Current JSON being parsed + bool _ignoreOuterBrace {false}; // Part of workaround for parsing scalars }; } } diff --git a/Tests/EncoderTests.cc b/Tests/EncoderTests.cc index 0b8ecc81..adca40c6 100644 --- a/Tests/EncoderTests.cc +++ b/Tests/EncoderTests.cc @@ -1162,7 +1162,7 @@ class EncoderTests { } } - TEST_CASE("Truncated JSON") { + TEST_CASE("Truncated JSON", "[JSON]") { // https://issues.couchbase.com/browse/CBL-1763 fleece::Encoder enc; REQUIRE(!FLEncoder_ConvertJSON(enc, "{"_sl)); @@ -1171,19 +1171,19 @@ class EncoderTests { CHECK(msg == "Truncated JSON"); } - TEST_CASE("Good JSON") { + TEST_CASE("Good JSON", "[JSON]") { fleece::Encoder enc; REQUIRE(FLEncoder_ConvertJSON(enc, "{}"_sl)); CHECK(enc.error() == kFLNoError); } - TEST_CASE("Good JSON with EncodeJSON") { + TEST_CASE("Good JSON with EncodeJSON", "[JSON]") { fleece::Encoder enc(kFLEncodeJSON); REQUIRE(FLEncoder_ConvertJSON(enc, "{}"_sl)); CHECK(enc.error() == kFLNoError); } - TEST_CASE("EncodeJSON: reset the Encoder") { + TEST_CASE("EncodeJSON: reset the Encoder", "[JSON]") { fleece::Encoder enc(kFLEncodeJSON); for (int c = 0; c < 4; ++c) { if (c > 0) { @@ -1200,4 +1200,24 @@ class EncoderTests { } } + TEST_CASE("Parse JSON Scalars", "[JSON]") { + auto roundtrip = [](slice input) -> std::string { + Encoder enc; + JSONConverter conv(enc); + if (conv.encodeJSON(input)) + return Value::fromData(enc.finish())->toJSONString(); + else + return std::string("Error: ") + conv.errorMessage() + " at " + std::to_string(conv.errorPos()); + }; + CHECK(roundtrip("null") == "null"); + CHECK(roundtrip("\t \r\n null\n") == "null"); + CHECK(roundtrip(" true") == "true"); + CHECK(roundtrip("\t123.25") == "123.25"); + CHECK(roundtrip("\"hello\"") == "\"hello\""); + + CHECK(roundtrip("foo") == "Error: JSON parse error: SPECIAL_EXPECTED at 1"); + CHECK(roundtrip(" ]") == "Error: JSON parse error: BRACKET_MISMATCH at 3"); + CHECK(roundtrip("\"hello") == "Error: Truncated JSON at 6"); + } + } } From ad5e917512e491b12a23dc04c32e84a9c03a65cc Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Mon, 27 Jan 2025 17:01:56 -0800 Subject: [PATCH 07/10] Fix: SharedKeys::encode() doesn't work on empty key A weird edge case that showed up when running the JSON Schema test suite, which includes some JSONPointer paths with empty-string components. The bug is that `_table.find(str)` finds the entry for the empty string, but the following test `entry.key != nullslice` fails because an empty slice compares as == nullslice since they're both empty. The proper way to test whether a slice is not nullslice is to use `if(entry.key)`, which doesn't work here because `__usuallyTrue` requires a boolean, or `entry.key.buf != nullptr`. --- Fleece/Core/SharedKeys.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fleece/Core/SharedKeys.cc b/Fleece/Core/SharedKeys.cc index 8c35e81c..8fd8f99e 100644 --- a/Fleece/Core/SharedKeys.cc +++ b/Fleece/Core/SharedKeys.cc @@ -106,7 +106,7 @@ namespace fleece { namespace impl { bool SharedKeys::encode(slice str, int &key) const { // Is this string already encoded? auto entry = _table.find(str); - if (_usuallyTrue(entry.key != nullslice)) { + if (_usuallyTrue(entry.key.buf != nullptr)) { key = entry.value; return true; } From 7fca172c02c86549e5872747f9f6e68187da373a Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Mon, 27 Jan 2025 17:03:09 -0800 Subject: [PATCH 08/10] Make SharedKeys::isEligibleToEncode() static There's no reason for it to be an instance method, and the optimizations I'm making to Dict require calling it without having a SharedKeys instance. --- Fleece/Core/SharedKeys.cc | 2 +- Fleece/Core/SharedKeys.hh | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Fleece/Core/SharedKeys.cc b/Fleece/Core/SharedKeys.cc index 8fd8f99e..741851e7 100644 --- a/Fleece/Core/SharedKeys.cc +++ b/Fleece/Core/SharedKeys.cc @@ -145,7 +145,7 @@ namespace fleece { namespace impl { } - __hot bool SharedKeys::isEligibleToEncode(slice str) const { + __hot bool SharedKeys::isEligibleToEncode(slice str) { for (size_t i = 0; i < str.size; ++i) if (_usuallyFalse(!isalnum(str[i]) && str[i] != '_' && str[i] != '-')) return false; diff --git a/Fleece/Core/SharedKeys.hh b/Fleece/Core/SharedKeys.hh index 44d3f390..48744d30 100644 --- a/Fleece/Core/SharedKeys.hh +++ b/Fleece/Core/SharedKeys.hh @@ -106,6 +106,10 @@ namespace fleece { namespace impl { bool isUnknownKey(int key) const FLPURE; + /** Determines whether a new string can be added. + * Returns true if the string contains only alphanumeric characters, '_' or '-'. */ + static bool isEligibleToEncode(slice str) FLPURE; + virtual bool refresh() {return false;} static const size_t kMaxCount = 2048; // Max number of keys to store @@ -130,10 +134,6 @@ namespace fleece { namespace impl { virtual ~SharedKeys(); - /** Determines whether a new string should be added. Default implementation returns true - if the string contains only alphanumeric characters, '_' or '-'. */ - virtual bool isEligibleToEncode(slice str) const FLPURE; - private: friend class PersistentSharedKeys; From cb859d8fe81ba9a5f2bc9d6fe62f480c3ad44740 Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Mon, 27 Jan 2025 17:11:02 -0800 Subject: [PATCH 09/10] Optimized Dict::key lookups These came about from profiling the JSONSchema validator, and speed up the travel-sample validation benchmark by about 10%. (1) `DictImpl::get(int)` uses a binary search, but for small Dicts it's faster to just scan all the keys, especially since we can precompute what the two bytes of the matching key must be. (2) `DictImpl::get(slice,SharedKeys*)` can skip looking up the Dict's SharedKeys (which is slow) if the key isn't alphanumeric since it won't ever be a shared key. (2) `DictImpl::get(Dict::key&)` shouldn't waste any time looking up sharedKeys if it already knows the numeric key. I also changed its boolean `_hasNumericKey` flag into an int8_t with three states: 0 for unknown, 1 for true, and -1 for "can't be a shared key". This latter state avoids trying to look up the key every single time when it won't ever succeed. --- Fleece/Core/Dict.cc | 68 ++++++++++++++++++++++++++------------- Fleece/Core/Dict.hh | 4 +-- Fleece/Core/SharedKeys.hh | 1 + 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Fleece/Core/Dict.cc b/Fleece/Core/Dict.cc index 32fa8455..3f6544ca 100644 --- a/Fleece/Core/Dict.cc +++ b/Fleece/Core/Dict.cc @@ -97,12 +97,24 @@ namespace fleece { namespace impl { __hot inline const Value* get(int keyToFind) const noexcept { assert_precondition(keyToFind >= 0); - return finishGet(search(keyToFind), keyToFind); + if (auto n = _count; n <= 8) { + // For smallish Dicts, a linear search is faster: + uint16_t encodedKey = endian::enc16(uint16_t(keyToFind)); + const Value* item = _first; + while (n-- > 0) { + if (*reinterpret_cast(item) == encodedKey) + return finishGet(item, keyToFind); + item = offsetby(item, 2 * kWidth); + } + return finishGet(nullptr, keyToFind); + } else { + return finishGet(search(keyToFind), keyToFind); + } } __hot inline const Value* get(slice keyToFind, SharedKeys *sharedKeys =nullptr) const noexcept { - if (!sharedKeys && usesSharedKeys()) { + if (!sharedKeys && usesSharedKeys() && SharedKeys::isEligibleToEncode(keyToFind)) { sharedKeys = findSharedKeys(); assert_precondition(sharedKeys || gDisableNecessarySharedKeysCheck); } @@ -114,26 +126,38 @@ namespace fleece { namespace impl { __hot const Value* get(Dict::key &keyToFind) const noexcept { - auto sharedKeys = keyToFind._sharedKeys; - if (!sharedKeys && usesSharedKeys()) { - sharedKeys = findSharedKeys(); - keyToFind.setSharedKeys(sharedKeys); - assert_precondition(sharedKeys || gDisableNecessarySharedKeysCheck); - } - if (_usuallyTrue(sharedKeys != nullptr)) { - // Look for a numeric key first: - if (_usuallyTrue(keyToFind._hasNumericKey)) - return get(keyToFind._numericKey); - // Key was not registered last we checked; see if dict contains any new keys: - if (_usuallyFalse(_count == 0)) - return nullptr; - if (lookupSharedKey(keyToFind._rawString, sharedKeys, keyToFind._numericKey)) { - // If the SharedKeys are in a transaction we don't mark the key as having a - // shared key, because the transaction might be rolled back. If the found - // shared key is rolled back as part of rolling back the transaction, continuing - // to use it would lead to incorrect lookup results. - keyToFind._hasNumericKey = sharedKeys->isCacheable(); - return get(keyToFind._numericKey); + if (_usuallyTrue(keyToFind._hasNumericKey > 0)) { + // Already know the numeric key: + return get(keyToFind._numericKey); + } else if (_usuallyTrue(keyToFind._hasNumericKey == 0)) { + // Haven't found a numeric key (nor given up) yet: + if (_usuallyTrue(SharedKeys::isEligibleToEncode(keyToFind._rawString))) { + auto sharedKeys = keyToFind._sharedKeys; + if (!sharedKeys && usesSharedKeys()) { + // Look up this Dict's associated SharedKeys (slow!) and save in keyToFind: + sharedKeys = findSharedKeys(); + keyToFind.setSharedKeys(sharedKeys); + assert_precondition(sharedKeys || gDisableNecessarySharedKeysCheck); + } + if (_usuallyTrue(sharedKeys != nullptr)) { + if (_usuallyFalse(keyToFind._rawString.size > sharedKeys->maxKeyLength())) + keyToFind._hasNumericKey = -1; // this key cannot be a shared key + else if (_usuallyFalse(_count == 0)) + return nullptr; + // Key was not registered last we checked; see if dict contains any new keys: + else if (lookupSharedKey(keyToFind._rawString, sharedKeys, + keyToFind._numericKey)) { + // If the SharedKeys are in a transaction we don't mark the key as having a + // shared key, because the transaction might be rolled back. If the found + // shared key is rolled back as part of rolling back the transaction, continuing + // to use it would lead to incorrect lookup results. + keyToFind._hasNumericKey = sharedKeys->isCacheable(); + return get(keyToFind._numericKey); + } + } + } else { + // This key can't be used as a shared key (not alphanumeric): + keyToFind._hasNumericKey = -1; // this key cannot be a shared key } } diff --git a/Fleece/Core/Dict.hh b/Fleece/Core/Dict.hh index e416e878..fd65f353 100644 --- a/Fleece/Core/Dict.hh +++ b/Fleece/Core/Dict.hh @@ -70,7 +70,7 @@ namespace fleece { namespace impl { slice string() const noexcept {return _rawString;} int compare(const key &k) const noexcept {return _rawString.compare(k._rawString);} key(const key&) =delete; - bool isShared() const noexcept {return _hasNumericKey;} + bool isShared() const noexcept {return _hasNumericKey > 0;} private: void setSharedKeys(SharedKeys*); @@ -78,7 +78,7 @@ namespace fleece { namespace impl { SharedKeys* _sharedKeys {nullptr}; uint32_t _hint {0xFFFFFFFF}; int32_t _numericKey; - bool _hasNumericKey {false}; + int8_t _hasNumericKey {0}; template friend struct dictImpl; }; diff --git a/Fleece/Core/SharedKeys.hh b/Fleece/Core/SharedKeys.hh index 48744d30..fd2dfe80 100644 --- a/Fleece/Core/SharedKeys.hh +++ b/Fleece/Core/SharedKeys.hh @@ -77,6 +77,7 @@ namespace fleece { namespace impl { /** Sets the maximum length of string that can be mapped. (Defaults to 16 bytes.) */ void setMaxKeyLength(size_t m) {_maxKeyLength = m;} + size_t maxKeyLength() const FLPURE {return _maxKeyLength;} /** The number of stored keys. */ size_t count() const FLPURE; From f5365365f6329dbf8f1550d97e3baf199a22826d Mon Sep 17 00:00:00 2001 From: Jens Alfke Date: Thu, 16 Jan 2025 11:05:31 -0800 Subject: [PATCH 10/10] API: JSON Schema validator class --- API/fleece/JSONSchema.hh | 189 ++ CMakeLists.txt | 2 + Experimental/JSONSchema.cc | 0 Fleece.xcodeproj/project.pbxproj | 24 +- Fleece/API_Impl/JSONSchema.cc | 1133 ++++++++++++ Tests/SchemaTests.cc | 253 +++ Tests/travel-schema.json | 207 +++ cmake/platform_base.cmake | 1 + vendor/JSON-Schema-Test-Suite/LICENSE | 19 + vendor/JSON-Schema-Test-Suite/README.md | 355 ++++ .../draft2020-12/additionalProperties.json | 219 +++ .../tests/draft2020-12/allOf.json | 312 ++++ .../tests/draft2020-12/anchor.json | 120 ++ .../tests/draft2020-12/anyOf.json | 203 +++ .../tests/draft2020-12/boolean_schema.json | 104 ++ .../tests/draft2020-12/const.json | 387 ++++ .../tests/draft2020-12/contains.json | 176 ++ .../tests/draft2020-12/content.json | 131 ++ .../tests/draft2020-12/default.json | 82 + .../tests/draft2020-12/defs.json | 21 + .../tests/draft2020-12/dependentRequired.json | 152 ++ .../tests/draft2020-12/dependentSchemas.json | 171 ++ .../tests/draft2020-12/dynamicRef.json | 815 +++++++++ .../tests/draft2020-12/enum.json | 358 ++++ .../tests/draft2020-12/exclusiveMaximum.json | 31 + .../tests/draft2020-12/exclusiveMinimum.json | 31 + .../tests/draft2020-12/format.json | 838 +++++++++ .../tests/draft2020-12/if-then-else.json | 268 +++ .../draft2020-12/infinite-loop-detection.json | 37 + .../tests/draft2020-12/items.json | 304 ++++ .../tests/draft2020-12/maxContains.json | 102 ++ .../tests/draft2020-12/maxItems.json | 50 + .../tests/draft2020-12/maxLength.json | 55 + .../tests/draft2020-12/maxProperties.json | 79 + .../tests/draft2020-12/maximum.json | 60 + .../tests/draft2020-12/minContains.json | 224 +++ .../tests/draft2020-12/minItems.json | 50 + .../tests/draft2020-12/minLength.json | 55 + .../tests/draft2020-12/minProperties.json | 60 + .../tests/draft2020-12/minimum.json | 75 + .../tests/draft2020-12/multipleOf.json | 97 + .../tests/draft2020-12/not.json | 301 ++++ .../tests/draft2020-12/oneOf.json | 293 +++ .../tests/draft2020-12/pattern.json | 65 + .../tests/draft2020-12/patternProperties.json | 176 ++ .../tests/draft2020-12/prefixItems.json | 104 ++ .../tests/draft2020-12/properties.json | 242 +++ .../tests/draft2020-12/propertyNames.json | 85 + .../tests/draft2020-12/ref.json | 1052 +++++++++++ .../tests/draft2020-12/refRemote.json | 342 ++++ .../tests/draft2020-12/required.json | 158 ++ .../tests/draft2020-12/type.json | 501 ++++++ .../tests/draft2020-12/unevaluatedItems.json | 798 ++++++++ .../draft2020-12/unevaluatedProperties.json | 1601 +++++++++++++++++ .../tests/draft2020-12/uniqueItems.json | 419 +++++ .../tests/draft2020-12/vocabulary.json | 57 + 56 files changed, 14042 insertions(+), 2 deletions(-) create mode 100644 API/fleece/JSONSchema.hh create mode 100644 Experimental/JSONSchema.cc create mode 100644 Fleece/API_Impl/JSONSchema.cc create mode 100644 Tests/SchemaTests.cc create mode 100644 Tests/travel-schema.json create mode 100644 vendor/JSON-Schema-Test-Suite/LICENSE create mode 100644 vendor/JSON-Schema-Test-Suite/README.md create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/additionalProperties.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/allOf.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anchor.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anyOf.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/boolean_schema.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/const.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/contains.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/content.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/default.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/defs.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentRequired.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentSchemas.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dynamicRef.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/enum.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMaximum.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMinimum.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/format.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/if-then-else.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/infinite-loop-detection.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/items.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxContains.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxItems.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxLength.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxProperties.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maximum.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minContains.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minItems.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minLength.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minProperties.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minimum.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/multipleOf.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/not.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/oneOf.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/pattern.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/patternProperties.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/prefixItems.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/properties.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/propertyNames.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/ref.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/refRemote.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/required.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/type.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedItems.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedProperties.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/uniqueItems.json create mode 100644 vendor/JSON-Schema-Test-Suite/tests/draft2020-12/vocabulary.json diff --git a/API/fleece/JSONSchema.hh b/API/fleece/JSONSchema.hh new file mode 100644 index 00000000..2b82182c --- /dev/null +++ b/API/fleece/JSONSchema.hh @@ -0,0 +1,189 @@ +// +// JSONSchema.hh +// +// Copyright 2025-Present Couchbase, Inc. +// +// Use of this software is governed by the Business Source License included +// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified +// in that file, in accordance with the Business Source License, use of this +// software will be governed by the Apache License, Version 2.0, included in +// the file licenses/APL2.txt. +// + +#pragma once +#ifndef _FLEECE_JSONSCHEMA_HH +#define _FLEECE_JSONSCHEMA_HH +#include "fleece/Fleece.hh" +#include "fleece/Mutable.hh" +#include +#include +#include +#include + +FL_ASSUME_NONNULL_BEGIN + +namespace fleece { + + /** Validates Values against a JSON Schema. (See https://json-schema.org ) + * + * Unsupported features (will throw an `unsupported_schema` exception if detected): + * - Path-relative `$ref`s (URIs that start with `/`) + * - `$dynamicRef`, `$dynamicAnchor`, `$vocabulary` + * - `format`, `contentEncoding`, `contentMediaType` + * - `dependencies`, `dependentRequired`, `dependentSchemas`, `extends` + * - `unevaluatedItems`, `unevaluatedProperties` + * + * Known bugs: + * - JSON Schema's equality comparisons do not distinguish between integers and floats, + * so `7` is equal to `7.0`. However, Fleece considers ints and floats distinct types. + * This implementation conforms to JSON Schema equality when making direct comparisons + * between numeric Values, bbut _not_ when the numbers are nested in collections. + * So for example `[7]` will not match `[7.0]`. + * + * @note This class does not download schemas on demand; it does no I/O at all. + * See the docs of \ref unknownSchemaID to see how to handle external schema refs. + * @note This class is thread-safe. + */ + class JSONSchema { + public: + + /** Thrown if errors are discovered in a schema. */ + class invalid_schema : public std::runtime_error { using runtime_error::runtime_error; }; + /** Thrown if a schema is found to use unsupported/unimplemented features. */ + class unsupported_schema : public std::runtime_error { using runtime_error::runtime_error; }; + + class Validation; + + + /// Constructor that takes a parsed JSON schema object. + /// @note The Value will be retained, so the caller doesn't need to keep a reference. + /// @param schemaRoot The parsed schema. + /// @param id_uri The absolute URI identifying this schema. Optional. + /// @throws invalid_schema if the schema is invalid. + /// @throws unsupported_schema if the schema uses unsupported features. + explicit JSONSchema(Value schemaRoot, std::string_view id_uri = ""); + + /// Convenience constructor that takes a JSON schema string and parses it. + /// @param json The schema as JSON data. + /// @param id_uri The absolute URI identifying this schema. Optional. + /// @throws invalid_schema if the schema is invalid. + /// @throws unsupported_schema if the schema uses unsupported features. + explicit JSONSchema(std::string_view json, std::string_view id_uri = ""); + + ~JSONSchema(); + + /// The root of the parsed schema. (Almost always a Dict.) + Value schema() const; + + /// Registers an external schema that the main schema may refer to. + /// @note The Dict will be retained, so the caller doesn't need to keep a reference. + /// @param schemaRoot The parsed schema. + /// @param id_uri The absolute URI identifying this schema. + /// @throws invalid_schema if the schema is invalid. + /// @throws unsupported_schema if the schema uses unsupported features. + void addSchema(Dict schemaRoot, std::string_view id_uri); + + /// Validates a parsed Fleece value against the schema. + /// @returns A \ref Validation object describing the result. + /// @throws invalid_schema if the schema itself is invalid. + /// @throws unsupported_schema if the schema uses unsupported features. + Validation validate(Value value) const LIFETIMEBOUND; + + /// Convenience method that parses JSON and then validates it against the schema. + /// @returns A \ref Validation object describing the result. + /// @throws std::invalid_argument if the JSON fails to parse. + /// @throws invalid_schema if the schema itself is invalid. + /// @throws unsupported_schema if the schema uses unsupported features. + Validation validate(std::string_view json) const LIFETIMEBOUND; + Validation validate(std::string_view json, SharedKeys) const LIFETIMEBOUND; + + + /** Errors that can occur during validation. */ + enum class Error : unsigned { + ok = 0, + invalid, // value matched against a "false" in the schema + typeMismatch, // value doesn't match "type" property in schema + outOfRange, // Number is out of range of "minimum", etc. + notMultiple, // Number is not a multiple of the "multipleOf" + tooShort, // String is too short or collection has too few items + tooLong, // String is too long or collection has too many items + patternMismatch, // String doesn't match regex pattern + missingProperty, // Dict is missing a required property + unknownProperty, // Dict has an invalid property + notEnum, // Value doesn't match any "enum" or "const" value + tooFew, // Value doesn't match anything in an "anyOf" or "oneOf" array + tooMany, // "oneOf" or "maxContains" failed + notNot, // Value matched a "not" schema + notUnique, // Array items are not unique + invalidUTF8, // A string's length could not be checked because of invalid UTF-8 + unknownSchemaRef, // Reference to a schema URI that's not registered + }; + + static bool ok(Error e) noexcept {return e == Error::ok;} + static std::string_view errorString(Error) noexcept; + + private: + struct Impl; + std::unique_ptr _impl; + }; + + + /** The result of validating against a JSONSchema. */ + class JSONSchema::Validation { + public: + /// True if validation succeeded. + bool ok() const noexcept {return _result.error == Error::ok;} + explicit operator bool() const {return ok();} + + /// The specific error. (Will be `Error::ok` if there was no error.) + Error error() const noexcept { return _result.error; } + + /// The specific error, as a string. + std::string errorString() const noexcept; + + /// The detected invalid Value; either the one passed to \ref validate + /// or something nested in it. (Will be nullptr if there was no error.) + Value errorValue() const noexcept {return _result.value;} + + /// On error, this is the path to the detected invalid Value, in \ref KeyPath syntax. + std::string errorPath() const noexcept; + + /// The key and value of the item in the schema that caused the failure; + /// e.g. `{"maxLength", 5}`. + std::pair errorSchema() const noexcept; + + /// A URI pointing to the item in the schema that caused the failure. + std::string errorSchemaURI() const noexcept; + + /// If the error is `Error::unknownSchemaRef`, this is the URI of the unknown schema. + /// If you can download or otherwise look up the schema, you can call \ref addSchema + /// to register it, then call \ref validate again to retry. + std::string const& unknownSchemaID() const noexcept {return _unknownSchema;} + + struct Result {Error error; Value value; Value schema; slice schemaKey;}; + static bool ok(Result const& e) noexcept {return e.error == Error::ok;} + private: + friend class JSONSchema; + + Validation(JSONSchema const& schema, Value value); + Result check(Value value, Value schema, Dict schemaBase); + Result checkValue(Value value, Dict schema, Dict schemaBase); + Result checkNumber(Value value, Dict schema, Dict schemaBase); + Result checkString(Value value, Dict schema, Dict schemaBase); + Result checkArray(Array, Dict schema, Dict schemaBase); + Result checkDict(Dict, Dict schema, Dict schemaBase); + + static bool isType(Value value, Value typeVal); + static bool isType(Value value, slice schemaType); + + Impl const& _schemaImpl; // The guts of the owning JSONSchema + RetainedValue _value; // The root Value being validated (only after failure) + Result _result {}; // Details of validation error + std::string _unknownSchema; // Unknown schema ID found during validation + }; + +} + +FL_ASSUME_NONNULL_END + +#endif // _FLEECE_JSONSCHEMA_HH diff --git a/CMakeLists.txt b/CMakeLists.txt index c914622d..26ea3327 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,9 +150,11 @@ add_executable(FleeceTests EXCLUDE_FROM_ALL ${FLEECE_TEST_SRC} vendor/catch/catch_amalgamated.cpp vendor/catch/CaseListReporter.cc + Tests/SchemaTests.cc ) setup_test_build() target_include_directories(FleeceTests PRIVATE + Experimental Tests vendor/catch ) diff --git a/Experimental/JSONSchema.cc b/Experimental/JSONSchema.cc new file mode 100644 index 00000000..e69de29b diff --git a/Fleece.xcodeproj/project.pbxproj b/Fleece.xcodeproj/project.pbxproj index 41191987..f4671e19 100644 --- a/Fleece.xcodeproj/project.pbxproj +++ b/Fleece.xcodeproj/project.pbxproj @@ -47,6 +47,9 @@ 2739971725CDBD8E000C1C1B /* SmallVectorBase.hh in Headers */ = {isa = PBXBuildFile; fileRef = 2739971625CDBD8E000C1C1B /* SmallVectorBase.hh */; }; 273CD2D825E874CD00B93C59 /* Base64.hh in Headers */ = {isa = PBXBuildFile; fileRef = 273CD2D625E874CD00B93C59 /* Base64.hh */; }; 273CD2D925E874CD00B93C59 /* Base64.cc in Sources */ = {isa = PBXBuildFile; fileRef = 273CD2D725E874CD00B93C59 /* Base64.cc */; }; + 273F3BCE2D4AA26D00BFAD13 /* JSONSchema.hh in Headers */ = {isa = PBXBuildFile; fileRef = 273F3BC92D4AA26D00BFAD13 /* JSONSchema.hh */; }; + 273F3BD02D4AA26D00BFAD13 /* JSONSchema.cc in Sources */ = {isa = PBXBuildFile; fileRef = 273F3BCA2D4AA26D00BFAD13 /* JSONSchema.cc */; }; + 273F3BD42D4AA2A800BFAD13 /* SchemaTests.cc in Sources */ = {isa = PBXBuildFile; fileRef = 273F3BD22D4AA2A800BFAD13 /* SchemaTests.cc */; }; 274281A4262F7CBF00862700 /* slice+ObjC.mm in Sources */ = {isa = PBXBuildFile; fileRef = 274281A3262F7CBF00862700 /* slice+ObjC.mm */; }; 274D8244209A3A77008BB39F /* HeapDict.cc in Sources */ = {isa = PBXBuildFile; fileRef = 274D8242209A3A77008BB39F /* HeapDict.cc */; }; 274D8245209A3A77008BB39F /* HeapDict.hh in Headers */ = {isa = PBXBuildFile; fileRef = 274D8243209A3A77008BB39F /* HeapDict.hh */; }; @@ -86,7 +89,6 @@ 279AC5381C096B5C002C80DB /* libfleeceStatic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 270FA25C1BF53CAD005DCB13 /* libfleeceStatic.a */; }; 279AC53C1C097941002C80DB /* Value+Dump.cc in Sources */ = {isa = PBXBuildFile; fileRef = 279AC53B1C097941002C80DB /* Value+Dump.cc */; }; 27A0E3DF24DCD86900380563 /* ConcurrentArena.hh in Headers */ = {isa = PBXBuildFile; fileRef = 27A0E3DD24DCD86900380563 /* ConcurrentArena.hh */; }; - 27A0E3E024DCD86900380563 /* ConcurrentArena.cc in Sources */ = {isa = PBXBuildFile; fileRef = 27A0E3DE24DCD86900380563 /* ConcurrentArena.cc */; }; 27A1327B2C700D45008E84FA /* JSLexer.hh in Headers */ = {isa = PBXBuildFile; fileRef = 27A1327A2C700D45008E84FA /* JSLexer.hh */; }; 27A132812C73BF8C008E84FA /* FLEncoder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 27A132802C73BF8C008E84FA /* FLEncoder.cc */; }; 27A2F73B21248DA50081927B /* FLSlice.h in Headers */ = {isa = PBXBuildFile; fileRef = 27A2F73A21248DA40081927B /* FLSlice.h */; }; @@ -350,6 +352,10 @@ 2739971625CDBD8E000C1C1B /* SmallVectorBase.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SmallVectorBase.hh; sourceTree = ""; }; 273CD2D625E874CD00B93C59 /* Base64.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Base64.hh; sourceTree = ""; }; 273CD2D725E874CD00B93C59 /* Base64.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Base64.cc; sourceTree = ""; }; + 273F3BC92D4AA26D00BFAD13 /* JSONSchema.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JSONSchema.hh; sourceTree = ""; }; + 273F3BCA2D4AA26D00BFAD13 /* JSONSchema.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = JSONSchema.cc; sourceTree = ""; }; + 273F3BD22D4AA2A800BFAD13 /* SchemaTests.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SchemaTests.cc; sourceTree = ""; }; + 273F3BD32D4AA2A800BFAD13 /* travel-schema.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "travel-schema.json"; sourceTree = ""; }; 274281A3262F7CBF00862700 /* slice+ObjC.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "slice+ObjC.mm"; sourceTree = ""; }; 2746DD3B1D931BE9000517BC /* Benchmark.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Benchmark.hh; sourceTree = ""; }; 2747D9841CFB9BC300C48211 /* 1person.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = 1person.json; sourceTree = ""; }; @@ -560,6 +566,7 @@ 274C948B2150175700F9AEA9 /* Doxyfile */, 274C948C215058BB00F9AEA9 /* Doxyfile_C++ */, 271507DE212225B3005FE6E8 /* API */, + 273F3BCD2D4AA26D00BFAD13 /* Experimental */, 270FA25E1BF53CAD005DCB13 /* Fleece */, 279AC5321C096872002C80DB /* Tool */, 272E5A441BF7FD1700848580 /* Tests */, @@ -686,11 +693,13 @@ 272E5A5E1BF91DBE00848580 /* ObjCTests.mm */, 27AEFAC4210913C500106ED8 /* DeltaTests.cc */, 271507F6212349B8005FE6E8 /* API_ValueTests.cc */, + 273F3BD22D4AA2A800BFAD13 /* SchemaTests.cc */, 278163B81CE6BB8C00B94E32 /* C_Test.c */, 27EC8D5B1CEBA72E00199FE6 /* mn_wordlist.h */, 2747D9841CFB9BC300C48211 /* 1person.json */, 2776AA232086C94B004ACE85 /* 1person-deepIterOutput.txt */, 2776AA242086CC1F004ACE85 /* 1person-shallowIterOutput.txt */, + 273F3BD32D4AA2A800BFAD13 /* travel-schema.json */, ); path = Tests; sourceTree = ""; @@ -712,6 +721,15 @@ path = Integration; sourceTree = ""; }; + 273F3BCD2D4AA26D00BFAD13 /* Experimental */ = { + isa = PBXGroup; + children = ( + 273F3BC92D4AA26D00BFAD13 /* JSONSchema.hh */, + 273F3BCA2D4AA26D00BFAD13 /* JSONSchema.cc */, + ); + path = Experimental; + sourceTree = ""; + }; 2760A4DA25E96DB000E2ECB2 /* wyhash */ = { isa = PBXGroup; children = ( @@ -969,6 +987,7 @@ 27AEFAC321090FF400106ED8 /* JSONDelta.hh in Headers */, 27298E661C00F8A9000CFBA8 /* jsonsl.h in Headers */, 277A06B420B36D1A00970354 /* FileUtils.hh in Headers */, + 273F3BCE2D4AA26D00BFAD13 /* JSONSchema.hh in Headers */, 27E3DD4D1DB6C32400F2872D /* CatchHelper.hh in Headers */, 27AEFAC921091A8C00106ED8 /* diff_match_patch.hh in Headers */, 2734B8A51F8583FF00BE5249 /* MDict.hh in Headers */, @@ -1000,7 +1019,6 @@ 278343132A675A7000621050 /* function_ref.hh in Headers */, 270FA2801BF53CEA005DCB13 /* Writer.hh in Headers */, 270FA27D1BF53CEA005DCB13 /* Encoder.hh in Headers */, - 27C8DF072084102900A99BFC /* MutableHashTree.hh in Headers */, 27A1327B2C700D45008E84FA /* JSLexer.hh in Headers */, 27A924D01D9C32E800086206 /* Path.hh in Headers */, 274D8245209A3A77008BB39F /* HeapDict.hh in Headers */, @@ -1325,6 +1343,7 @@ buildActionMask = 2147483647; files = ( 270FA27E1BF53CEA005DCB13 /* Encoder+ObjC.mm in Sources */, + 273F3BD02D4AA26D00BFAD13 /* JSONSchema.cc in Sources */, 270FA27B1BF53CEA005DCB13 /* Value+ObjC.mm in Sources */, 27C4ACAC1CE5146500938365 /* Array.cc in Sources */, 277A06B320B36D1A00970354 /* FileUtils.cc in Sources */, @@ -1380,6 +1399,7 @@ 272E5A5D1BF800A100848580 /* EncoderTests.cc in Sources */, 27E3DD531DB7DB1C00F2872D /* SharedKeysTests.cc in Sources */, 27AEFAC5210913C500106ED8 /* DeltaTests.cc in Sources */, + 273F3BD42D4AA2A800BFAD13 /* SchemaTests.cc in Sources */, 274D824F209A8D01008BB39F /* MutableTests.cc in Sources */, 271507F7212349B8005FE6E8 /* API_ValueTests.cc in Sources */, 27298E781C01A461000CFBA8 /* PerfTests.cc in Sources */, diff --git a/Fleece/API_Impl/JSONSchema.cc b/Fleece/API_Impl/JSONSchema.cc new file mode 100644 index 00000000..f0f7d9a5 --- /dev/null +++ b/Fleece/API_Impl/JSONSchema.cc @@ -0,0 +1,1133 @@ +// +// JSONSchema.cc +// +// Copyright 2025-Present Couchbase, Inc. +// +// Use of this software is governed by the Business Source License included +// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified +// in that file, in accordance with the Business Source License, use of this +// software will be governed by the Apache License, Version 2.0, included in +// the file licenses/APL2.txt. +// + +#include "fleece/JSONSchema.hh" +#include "fleece/Expert.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "betterassert.hh" + +#ifdef _MSC_VER +#include "asprintf.h" +#endif + +namespace fleece { + using namespace std; + + +#pragma mark - UTILITIES: + + + static constexpr slice kFLTypeNames[] = { // indexed by FLValueType + "null", "boolean", "number", "string", "data", "array", "object"}; + + + // Throws an exception with a printf-formatted message. + template + [[noreturn]] __printflike(1, 2) static + void fail(const char* format, ...) { + va_list ap; + va_start(ap, format); + char* formatted = nullptr; + vasprintf(&formatted, format, ap); + va_end(ap); + string message(formatted); + free(formatted); + throw X(message); + } + + + // If `value` is not of `type`, throws `invalid_schema`. (`name` is used in the message.) + static void requireType(Value value, slice name, FLValueType type) { + if (value.type() != type) [[unlikely]] + fail("type of \"%.*s\" must be %.*s", + FMTSLICE(name), FMTSLICE(kFLTypeNames[type])); + }; + + + // Returns true if a Value is a number with an integral value. + static bool isIntegral(Value v) { + if (v.isInteger()) + return true; + if (v.type() != kFLNumber) + return false; + double d = v.asDouble(); + return d == floor(d); + } + + + // Compare Values, treating ints and floats with the same value as equal: + static bool isEqual(Value a, Value b) { + //FIXME: This doesn't handle ints vs. floats in nested values. + if (a.isEqual(b)) + return true; + if (a.isInteger() != b.isInteger() && a.type() == kFLNumber && b.type() == kFLNumber) + return a.asDouble() == b.asDouble(); + return false; + } + + + // Validates the length in characters of a UTF-8 string. Avoids UTF-8 parsing if possible. + static JSONSchema::Error checkUTF8Length(slice str, size_t minLength, size_t maxLength) { + using enum JSONSchema::Error; + // If we know the answer without having to scan the UTF-8, return it: + auto mostChars = str.size, leastChars = (mostChars + 3) / 4; + if (leastChars >= minLength && mostChars <= maxLength) [[likely]] + return ok; + if (mostChars < minLength) + return tooShort; + if (leastChars > maxLength) + return tooLong; + // OK, we have to scan the string to count characters: + auto [length, valid] = str.UTF8Length(); + if (!valid) [[unlikely]] + return invalidUTF8; + if (length < minLength) + return tooShort; + if (length > maxLength) + return tooLong; + return ok; + } + + + // True if a URI appears to be a JSONPointer (equal to `#`, or starts with `#/`) + static bool isJSONPointerURI(string_view uri) { + return uri.starts_with('#') && (uri.size() == 1 || uri[1] == '/'); + } + + + // True if a URI is absolute. + static bool isAbsoluteURI(string_view uri) { + auto colon = uri.find(':'); + return colon != string_view::npos + && uri.substr(0, colon).find('/') == string_view::npos + && isalpha(uri[0]); + } + + + // Returns a prefix of an absolute URI up to but not including the first '/' of the path. + // Given "http://example.com/foo" or "http://example.com", returns "http://example.com". + static string_view rootOfAbsoluteURI(string_view uri) { + auto pos = uri.find("://"); + if (pos == string_view::npos) + return ""; + auto slash = uri.find('/', pos + 3); + if (slash == string_view::npos) + slash = uri.size(); + return uri.substr(0, slash); + } + + + // Interprets URI `rel` relative to `base`. + static string concatURIs(string_view base, string_view rel) { + if (base.empty() || isAbsoluteURI(rel)) { + return string(rel); + } else if (rel.starts_with('/')) { + if (!isAbsoluteURI(base)) + return string(rel); + string_view root = rootOfAbsoluteURI(base); + if (root.empty()) + fail("can't resolve <%.*s> relative to <%.*s>", + FMTSLICE(slice(rel)), FMTSLICE(slice(base))); + return string(root).append(rel); + } else { + string result(base); + if (auto hash = result.find('#'); hash != string_view::npos) + result = result.substr(0, hash); + if (!result.ends_with('/') && !rel.starts_with('#')) { + if (auto lastSlash = result.find_last_of('/'); lastSlash != string_view::npos) + result = result.substr(0, lastSlash + 1); + } + result += rel; + return result; + } + } + + + // Converts '%' escapes back into their original characters. + static void unescapeURI(string& uri) { + auto digittohex = [](uint8_t c) { + // ( precondition: isxdigit(c) ) + if (c >= 'a') return c - 'a' + 10; + else if (c >= 'A') return c - 'A' + 10; + else return c - '0'; + }; + size_t start = 0, pos = 0; + while (string::npos != (pos = uri.find('%', start)) && pos + 2 < uri.size()) { + auto d1 = uint8_t(uri[pos + 1]), d2 = uint8_t(uri[pos + 2]); + if (isxdigit(d1) && isxdigit(d2)) { // note: not locale-sensitive + uri[pos] = char(16 * digittohex(d1) + digittohex(d2)); + uri.erase(pos + 1, 2); + start = pos + 1; + } else { + start = pos + 3; + } + } + } + + + // Finds a target value within a container and returns the path to it. + // Warning: This can be ambiguous if `target` is a string, because the Fleece encoder + // de-dups strings. + static alloc_slice recoverPath(Value root, Value target, bool asJSONPointer = false) { + if (target && root) { + for (DeepIterator i(root); i; ++i) { + if (i.value() == target) + return asJSONPointer ? i.JSONPointer() : i.pathString(); + } + } + return nullslice; + } + + + // Parses a JSON string to Fleece, optionally using SharedKeys. + static Doc parseJSON(string_view json, FLSharedKeys sk) { + Encoder enc; + enc.setSharedKeys(sk); + enc.convertJSON(json); + FLError err; + Doc doc = enc.finishDoc(&err); + if (!doc) + throw invalid_argument("invalid JSON"); + if (auto type = doc.root().type(); type != kFLDict && type != kFLBoolean) + throw JSONSchema::invalid_schema("JSON Schema must be an object (Dict) or boolean"); + return doc; + } + + +#pragma mark - SHARED KEYS: + + + // All the keys in a schema that we look up. + namespace sharedkey { + enum { + AdditionalProperties, + AllOf, + AnyOf, + Const, + Contains, + Else, + Enum, + ExclusiveMaximum, + ExclusiveMinimum, + If, + Items, + MaxContains, + MaxItems, + MaxLength, + MaxProperties, + Maximum, + MinContains, + MinItems, + MinLength, + MinProperties, + Minimum, + MultipleOf, + Not, + OneOf, + Pattern, + PatternProperties, + PrefixItems, + Properties, + PropertyNames, + Ref, + Required, + Then, + Type, + UniqueItems, + + NKeys_ + }; + } + + // Names of the keys in the `sharedkey` enum. + static constexpr const char* kSharedKeyStrings[] = { + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "else", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "if", + "items", + "maxContains", + "maxItems", + "maxLength", + "maxProperties", + "maximum", + "minContains", + "minItems", + "minLength", + "minProperties", + "minimum", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "prefixItems", + "properties", + "propertyNames", + "$ref", + "required", + "then", + "type", + "uniqueItems", + }; + + static_assert(std::size(kSharedKeyStrings) == sharedkey::NKeys_); + + // The singleton SharedKeys instance used for all parsed schema. + static FLSharedKeys sSchemaSharedKeys; + + // Optimized Dict keys, indexed by `sharedkey`. + static FLDictKey sharedkeys[sharedkey::NKeys_]; + + #define SHARED_KEY(NAME) sharedkeys[sharedkey::NAME] + + + static void initSharedKeys() { + static once_flag sOnce; + call_once(sOnce, [] { + sSchemaSharedKeys = FLSharedKeys_New(); + for (unsigned i = 0; i < sharedkey::NKeys_; ++i) { + FLSharedKeys_Encode(sSchemaSharedKeys, slice(kSharedKeyStrings[i]), true); + sharedkeys[i] = FLDictKey_Init(slice(kSharedKeyStrings[i])); + } + }); + } + + + // Parses a JSON schema, using a singleton SharedKeys instance. + static RetainedValue parseSchema(string_view json) { + initSharedKeys(); + Doc doc = parseJSON(json, sSchemaSharedKeys); + if (!doc) + throw invalid_argument("invalid JSON in schema"); + return doc.root(); + } + + // Re-encodes a Value, if necessary, so that it uses our singleton SharedKeys instance. + static RetainedValue reencodeSchema(Value originalSchema) { + initSharedKeys(); + precondition(originalSchema); + if (auto doc = FLValue_FindDoc(originalSchema)) { + if (FLDoc_GetSharedKeys(doc) == sSchemaSharedKeys) + return originalSchema; + } + Encoder enc; + enc.setSharedKeys(sSchemaSharedKeys); + enc.writeValue(originalSchema); + return enc.finishDoc().root(); + } + + +#pragma mark - JSON SCHEMA: + + + struct JSONSchema::Impl { + std::shared_mutex mutable _mutex; // Read/write lock + RetainedValue const _schema; // Root of main schema + std::string _schemaURI; // URI of schema, if known + std::map _knownSchemas; // Known schemas, by URI + std::map _regexes; // Cached regex objects + + void scanSchema(Value schema, std::string_view parentID); + void registerSchema(Dict root, std::string id); + Value resolveSchemaRef(std::string_view ref, Dict schemaBase) const; + std::string schemaValueURI(Value schemaVal) const; + void addPattern(slice); + bool stringMatchesPattern(slice str, slice pattern) const; + }; + + + JSONSchema::JSONSchema(Value root, string_view uri) + :_impl(new Impl{._schema = reencodeSchema(root), ._schemaURI = string(uri)}) + { + _impl->scanSchema(_impl->_schema, uri); + } + + + JSONSchema::JSONSchema(string_view json, std::string_view uri) + :_impl(new Impl{._schema = parseSchema(json), ._schemaURI = string(uri)}) + { + _impl->scanSchema(_impl->_schema, uri); + } + + + JSONSchema::~JSONSchema() = default; + + + Value JSONSchema::schema() const { + return _impl->_schema; + } + + + // Traverses a parsed schema, finding errors or unsupported features. + // Also registers nested schemas and compiles regexes needed for pattern matching. + void JSONSchema::Impl::scanSchema(Value schema, string_view parentID) { + enum KeyType {Any, AString, ANumber, AnInteger, AnArray, Type, Pattern, PatternProperties, + Recurse, RecurseArray, RecurseOnValues, Unsupported}; + static const unordered_map sKeyMap { + // Meta stuff: + {"$id", AString}, + {"$anchor", AString}, + {"$schema", AString}, + {"$ref", AString}, + {"$defs", RecurseOnValues}, + + // Ignored for validation: + {"$comment", AString}, + {"description", AString}, + {"default", Any}, + + // Applies to any type: + {"type", Type}, + {"const", Any}, + {"allOf", RecurseArray}, + {"anyOf", RecurseArray}, + {"oneOf", RecurseArray}, + {"enum", AnArray}, + {"if", Recurse}, + {"then", Recurse}, + {"else", Recurse}, + {"not", Recurse}, + + // Numbers: + {"minimum", ANumber}, + {"maximum", ANumber}, + {"exclusiveMinimum", ANumber}, + {"exclusiveMaximum", ANumber}, + {"multipleOf", ANumber}, + + // Strings: + {"minLength", ANumber}, + {"maxLength", ANumber}, + {"pattern", Pattern}, + + // Arrays: + {"items", Recurse}, + {"prefixItems", RecurseArray}, + {"additionalItems", Recurse}, + {"minItems", AnInteger}, + {"maxItems", AnInteger}, + {"uniqueItems", Any}, + {"contains", Recurse}, + {"minContains", AnInteger}, + {"maxContains", AnInteger}, + + // Objects: + {"properties", RecurseOnValues}, + {"minProperties", AnInteger}, + {"maxProperties", AnInteger}, + {"propertyNames", Recurse}, + {"patternProperties", PatternProperties}, + {"additionalProperties",Recurse}, + {"required", AnArray}, + + // Unsupported: + {"$dynamicAnchor", Unsupported}, + {"$dynamicRef", Unsupported}, + {"$vocabulary", Unsupported}, + {"contentEncoding", Unsupported}, + {"contentMediaType", Unsupported}, + {"dependencies", Unsupported}, + {"dependentRequired", Unsupported}, + {"dependentSchemas", Unsupported}, + {"extends", Unsupported}, + {"format", Unsupported}, + {"unevaluatedItems", Unsupported}, + {"unevaluatedProperties",Unsupported}, + }; + + if (Dict dict = schema.asDict()) { + string newID; + // "$id" and "$anchor" register new schemas; do this first before recursing: + if (slice id = dict["$id"].asString()) { + // Register a nested schema "$id": + newID = concatURIs(parentID, id); + registerSchema(dict, newID); + parentID = newID; + } + if (slice anchor = dict["$anchor"].asString()) { + if (anchor.empty() || !isalpha(anchor[0])) + fail("invalid $anchor \"%.*s\"", FMTSLICE(anchor)); + registerSchema(dict, concatURIs(parentID, "#"s + string(anchor))); + } + + // Now look at each key and process it according to its type: + for (Dict::iterator i(dict); i; ++i) { + slice key = i.keyString(); + Value val = i.value(); + if (auto imap = sKeyMap.find(key); imap != sKeyMap.end()) { + switch (imap->second) { + case Any: + break; + case ANumber: + requireType(val, key, kFLNumber); + break; + case AString: + requireType(val, key, kFLString); + break; + case AnInteger: + if (!isIntegral(val)) + fail("value of \"%.*s\" must be an integer", FMTSLICE(key)); + break; + case AnArray: + requireType(val, key, kFLArray); + break; + case Type: + (void)Validation::isType(nullptr, val); // will throw if val is invalid + break; + case Pattern: + requireType(val, key, kFLString); + addPattern(val.asString()); + break; + case PatternProperties: + requireType(val, key, kFLDict); + for (Dict::iterator j(val.asDict()); j; ++j) + addPattern(j.keyString()); + break; + case Recurse: + if (auto type = val.type(); type != kFLDict && type != kFLBoolean) + fail("value of \"%.*s\" must be a schema", FMTSLICE(key)); + scanSchema(val, parentID); + break; + case RecurseArray: + requireType(val, key, kFLArray); + for (Array::iterator j(val.asArray()); j; ++j) + scanSchema(j.value(), parentID); + break; + case RecurseOnValues: + requireType(val, key, kFLDict); + for (Dict::iterator j(val.asDict()); j; ++j) + scanSchema(j.value(), parentID); + break; + case Unsupported: + fail("unsupported property \"%.*s\"", FMTSLICE(key)); + } + } else { + fail("unknown property \"%.*s\"", FMTSLICE(key)); + } + } + } else if (schema.type() != kFLBoolean) { + slice name = kFLTypeNames[schema.type()]; + fail("a %.*s cannot be a schema", FMTSLICE(name)); + } + } + + + void JSONSchema::addSchema(Dict schema, std::string_view id) { + unique_lock lock(_impl->_mutex); + string idstr(id); + if (!isAbsoluteURI(id)) + fail("schema id <\"%s\"> is not an absolute URI", idstr.c_str()); + _impl->registerSchema(schema, std::move(idstr)); + _impl->scanSchema(schema, id); + } + + + void JSONSchema::Impl::registerSchema(Dict schema, string id) { + precondition(schema); + precondition(id.starts_with('#') || isAbsoluteURI(id)); + if (auto i = _knownSchemas.find(id); i == _knownSchemas.end()) { + _knownSchemas.emplace(std::move(id), schema); + } else if (i->second.isEqual(schema)) { + fail("schema id <%s> is already registered as a different schema", id.c_str()); + } + } + + + Value JSONSchema::Impl::resolveSchemaRef(std::string_view ref, Dict schemaBase) const { + slice originalRef = ref; + auto failBadRef = [&](const char* msg) { + fail("%s: %.*s", msg, FMTSLICE(originalRef)); + }; + + Dict schema; + string absRefStr; + if (!isJSONPointerURI(ref)) { + if (!isAbsoluteURI(ref)) { + // Get the parent schema ID to resolve the ref: + string_view schemaID = schemaBase["$id"].asString(); + if (schemaID.empty()) + schemaID = _schemaURI; + if (!schemaID.empty()) { + absRefStr = concatURIs(schemaID, ref); + ref = absRefStr; + } + } + + if (auto i = _knownSchemas.find(string(ref)); i != _knownSchemas.end()) { + // Exact match: + return i->second; + } + + // Look at schemas to find one that's a prefix of ref: + for (auto& [uri, sch] : _knownSchemas) { + if (ref.starts_with(uri)) { + if (auto len = uri.size(); ref[len] == '#') { + // ref is relative to _schemaID, so make it a relative URI: + ref = ref.substr(len); + schema = sch.asDict(); + break; + } + } + } + if (!schema) { + // Reference to unknown schema URI + return nullptr; + } + } else { + schema = schemaBase; + } + + if (ref == "#") { + return schema; + } else if (ref.starts_with('#')) { + if (isJSONPointerURI(ref)) { + string ptr(ref.substr(1)); + unescapeURI(ptr); + FLError flErr; + Value dst = FLEvalJSONPointer(slice(ptr), schema, &flErr); + if (!dst) { + if (flErr) + failBadRef("invalid JSON pointer"); + else + failBadRef("schema reference JSON pointer doesn't resolve"); + } + return dst; + } else { + failBadRef("invalid relative schema reference"); + return nullptr; //unreachable + } + } else { + failBadRef("can't resolve reference"); + return nullptr; //unreachable + } + } + + + string JSONSchema::Impl::schemaValueURI(Value schemaVal) const { + string uri; + alloc_slice path = recoverPath(_schema, schemaVal, true); + if (path) { + uri = _schemaURI; + } else { + shared_lock lock(_mutex); + for (auto& [auri, aroot] : _knownSchemas) { + path = recoverPath(aroot, schemaVal, true); + if (path) { + uri = auri; + break; + } + } + if (!path) + return ""; // should never happen + } + return uri.append("#").append(path); + } + + + void JSONSchema::Impl::addPattern(slice pattern) { + if (!_regexes.contains(pattern)) { + try { + _regexes.emplace(pattern, regex((const char*)pattern.buf, pattern.size)); + } catch (regex_error const&) { + fail("invalid regular expression: %.*s", FMTSLICE(pattern)); + } + } + } + + + bool JSONSchema::Impl::stringMatchesPattern(slice str, slice pattern) const { + if (auto i = _regexes.find(pattern); i != _regexes.end()) [[likely]] + return regex_search(string(str), i->second); + throw logic_error("JSONSchema failed to pre-cache regex: " + string(pattern)); + } + + +#pragma mark - VALIDATION: + + + JSONSchema::Validation JSONSchema::validate(Value value) const { + assert_precondition(value); + // Lock allows concurrent validation, but blocks mutation (addSchema). + shared_lock lock(_impl->_mutex); + return Validation(*this, value); + } + + JSONSchema::Validation JSONSchema::validate(std::string_view json, SharedKeys sk) const { + return validate(parseJSON(json, sk)); + } + + JSONSchema::Validation JSONSchema::validate(std::string_view json) const { + return validate(parseJSON(json, SharedKeys{})); + } + + + JSONSchema::Validation::Validation(JSONSchema const& schema, Value value) + :_schemaImpl(*schema._impl) + { + Result result = check(value, _schemaImpl._schema, _schemaImpl._schema.asDict()); + if (!ok(result)) { + _result = result; + _value = value; // Retain it to save for later use + } + } + + + using Result = JSONSchema::Validation::Result; + + static Result mkResult(JSONSchema::Error error, Value value, Value schema, slice schemaKey) { + // cerr << "\tError: " << JSONSchema::errorString(error) << " for " << value.toJSONString() << " failed " << string_view(schemaKey) + // << ": " << schema[schemaKey].toJSONString() << endl; + return Result{error, value, schema, schemaKey}; + } + + + /// Checks a value against a schema. This is recursively called during validation. + Result JSONSchema::Validation::check(Value value, Value schemaVal, Dict schemaBase) { + if (auto schemaDict = schemaVal.asDict()) [[likely]] { + // Most schema nodes are Dicts: + if (schemaDict.empty()) [[unlikely]] + return {}; // Empty dict matches anything + + if (schemaDict["$id"].asString()) { + // This is a nested schema; it becomes the `schemaBase` for resolving references: + schemaBase = schemaDict; + } + + // First the checks that apply to any Values: + if (Result err = checkValue(value, schemaDict, schemaBase); !ok(err)) + return err; + + // Then type-specific checks: + switch (value.type()) { + case kFLNumber: return checkNumber(value, schemaDict, schemaBase); + case kFLString: return checkString(value, schemaDict, schemaBase); + case kFLArray: return checkArray(value.asArray(), schemaDict, schemaBase); + case kFLDict: return checkDict(value.asDict(), schemaDict, schemaBase); + default: return {}; + } + } else if (schemaVal.type() == kFLBoolean) [[likely]] { + // `true` matches anything, `false` matches nothing: + return mkResult(schemaVal.asBool() ? Error::ok : Error::invalid, value, schemaVal, nullslice); + } else { + fail("invalid value type in schema"); + } + } + + + /// Checks the generic schema constraints of a Value. + Result JSONSchema::Validation::checkValue(Value value, Dict schema, Dict schemaBase) { + // "type": + if (Value type = schema[SHARED_KEY(Type)]; type && !isType(value, type)) [[unlikely]] { + return mkResult(Error::typeMismatch, value, schema, "type"); + } + + // "const": + if (Value c = schema[SHARED_KEY(Const)]) { + if (!isEqual(value, c)) [[unlikely]] + return mkResult(Error::notEnum, value, schema, "const"); + } + + // "enum": + if (Array e = schema[SHARED_KEY(Enum)].asArray()) { + bool matches = false; + for (Array::iterator i(e); i; ++i) { + if (isEqual(value, i.value())) { + matches = true; + break; + } + } + if (!matches) [[unlikely]] + return mkResult(Error::notEnum, value, schema, "enum"); + } + + // "not": + if (Value n = schema[SHARED_KEY(Not)]) { + if (auto err = check(value, n, schemaBase); ok(err)) [[unlikely]] + return mkResult(Error::notNot, value, schema, "not"); + } + + // "allOf": + if (Array all = schema[SHARED_KEY(AllOf)].asArray()) { + for (Array::iterator i(all); i; ++i) { + if (auto err = check(value, i.value(), schemaBase); !ok(err)) [[unlikely]] + return err; + } + } + + // "anyOf": + if (Array any = schema[SHARED_KEY(AnyOf)].asArray()) { + bool matched = false; + for (Array::iterator i(any); i; ++i) { + if (auto err = check(value, i.value(), schemaBase); ok(err)) { + matched = true; + break; + } + } + if (!matched) [[unlikely]] + return mkResult(Error::tooFew, value, schema, "anyOf"); + } + + // "oneOf": + if (Array any = schema[SHARED_KEY(OneOf)].asArray()) { + unsigned matches = 0; + for (Array::iterator i(any); i; ++i) { + if (auto err = check(value, i.value(), schemaBase); ok(err)) + ++matches; + } + if (matches != 1) [[unlikely]] + return mkResult(matches ? Error::tooMany : Error::tooFew, value, schema, "oneOf"); + } + + // "if", "then", "else": + if (Value ifSchema = schema[SHARED_KEY(If)]) { + Value thenSchema = schema[SHARED_KEY(Then)], elseSchema = schema[SHARED_KEY(Else)]; + if (thenSchema || elseSchema) { + bool ifOK = ok(check(value, ifSchema, schemaBase)); + if (Value nextSchema = ifOK ? thenSchema : elseSchema) { + if (auto err = check(value, nextSchema, schemaBase); !ok(err)) [[unlikely]] + return err; + } + } + } + + if (slice ref = schema[SHARED_KEY(Ref)].asString()) { + Value refSchema = _schemaImpl.resolveSchemaRef(ref, schemaBase); + if (!refSchema) [[unlikely]] { + _unknownSchema = string(ref); + return mkResult(Error::unknownSchemaRef, value, schema, "$ref"); + } + if (auto err = check(value, refSchema, schemaBase); !ok(err)) [[unlikely]] + return err; + } + + return {}; + } + + + /// Checks a number value against a schema. + Result JSONSchema::Validation::checkNumber(Value value, Dict schema, Dict schemaBase) { + double n = value.asDouble(); + if (Value minV = schema[SHARED_KEY(Minimum)]) + if (n < minV.asDouble()) [[unlikely]] + return mkResult(Error::outOfRange, value, schema, "minimum"); + if (Value minV = schema[SHARED_KEY(ExclusiveMinimum)]) + if (n <= minV.asDouble()) [[unlikely]] + return mkResult(Error::outOfRange, value, schema, "exclusiveMinimum"); + if (Value maxV = schema[SHARED_KEY(Maximum)]) + if (n > maxV.asDouble()) [[unlikely]] + return mkResult(Error::outOfRange, value, schema, "maximum"); + if (Value maxV = schema[SHARED_KEY(ExclusiveMaximum)]) + if (n >= maxV.asDouble()) [[unlikely]] + return mkResult(Error::outOfRange, value, schema, "exclusiveMaximum"); + if (Value mult = schema[SHARED_KEY(MultipleOf)]) { + double d = n / mult.asDouble(); + if (d != floor(d) || isinf(d)) [[unlikely]] + return mkResult(Error::notMultiple, value, schema, "multipleOf"); + } + return {}; + } + + + /// Checks a string value against a schema. + Result JSONSchema::Validation::checkString(Value value, Dict schema, Dict schemaBase) { + slice str = value.asString(); + if (Value minV = schema[SHARED_KEY(MinLength)], maxV = schema[SHARED_KEY(MaxLength)]; minV || maxV) { + Error err = checkUTF8Length(str, minV ? minV.asUnsigned() : 0, + maxV ? maxV.asUnsigned() : SIZE_MAX); + if (err != Error::ok) [[unlikely]] { + slice prop = (err == Error::tooShort) ? "minLength" : "maxLength"; + return mkResult(err, value, schema, prop); + } + } + if (Value patV = schema[SHARED_KEY(Pattern)]) { + if (!_schemaImpl.stringMatchesPattern(str, patV.asString())) [[unlikely]] + return mkResult(Error::patternMismatch, value, schema, "pattern"); + } + return {}; + } + + + /// Checks an array value against a schema. + Result JSONSchema::Validation::checkArray(Array array, Dict schema, Dict schemaBase) { + auto count = array.count(); + if (Value minV = schema[SHARED_KEY(MinItems)]) + if (count < minV.asUnsigned()) [[unlikely]] + return mkResult(Error::tooShort, array, schema, "minItems"); + if (Value maxV = schema[SHARED_KEY(MaxItems)]) + if (count > maxV.asUnsigned()) [[unlikely]] + return mkResult(Error::tooLong, array, schema, "maxItems"); + + // "prefixItems": + int checkIndex = 0; + if (Array prefixItems = schema[SHARED_KEY(PrefixItems)].asArray()) { + for (Array::iterator i(prefixItems); i; ++i, ++checkIndex) { + if (checkIndex >= count) + break; + if (auto err = check(array[checkIndex], i.value(), schemaBase); !ok(err)) [[unlikely]] + return err; + } + } + + // "items": + if (Value items = schema[SHARED_KEY(Items)]) { + for (; checkIndex < count; ++checkIndex) { + if (auto err = check(array[checkIndex], items, schemaBase); !ok(err)) [[unlikely]] + return err; + } + } + + // "contains", "minContains", "maxContains": + if (Value contains = schema[SHARED_KEY(Contains)]) { + uint64_t minCount = 1, maxCount = count; + Value minV = schema[SHARED_KEY(MinContains)], maxV = schema[SHARED_KEY(MaxContains)]; + if (minV) + minCount = minV.asUnsigned(); + if (maxV) + maxCount = maxV.asUnsigned(); + if (count < minCount) [[unlikely]] + return mkResult(Error::tooFew, array, schema, (minV ? "minContains" : "contains")); + uint64_t matches = 0; + for (Array::iterator i(array); i; ++i) { + if (auto err = check(i.value(), contains, schemaBase); ok(err)) { + ++matches; + if (matches > maxCount) [[unlikely]] + return mkResult(Error::tooMany, array, schema, "maxContains"); + if (matches >= minCount && maxCount >= count) + break; + } + } + if (matches < minCount) [[unlikely]] + return mkResult(Error::tooFew, array, schema, (minV ? "minContains" : "contains")); + } + + // "uniqueItems": + if (schema[SHARED_KEY(UniqueItems)].asBool()) { + int index = 0; + for (Array::iterator i(array); i; ++i, ++index) { + Value v = i.value(); + for (int j = 0; j < index; ++j) { + if (isEqual(array[j], v)) [[unlikely]] + return mkResult(Error::notUnique, array, schema, "uniqueItems"); + } + } + } + + return {}; + } + + + /// Checks an object value against a schema. + Result JSONSchema::Validation::checkDict(Dict dict, Dict schema, Dict schemaBase) { + auto count = dict.count(); + if (Value minV = schema[SHARED_KEY(MinProperties)]) + if (count < minV.asUnsigned()) [[unlikely]] + return mkResult(Error::tooShort, dict, schema, "minProperties"); + if (Value maxV = schema[SHARED_KEY(MaxProperties)]) + if (count > maxV.asUnsigned()) [[unlikely]] + return mkResult(Error::tooLong, dict, schema, "maxProperties"); + + // Required properties: Fail if any of these are missing + if (Array required = schema[SHARED_KEY(Required)].asArray()) { + for (Array::iterator i(required); i; ++i) { + if (!dict[i->asString()]) [[unlikely]] + return mkResult(Error::missingProperty, dict, schema, "required"); + } + } + + // "propertyNames": Schema that all property _names_ must match + if (Value propertyNames = schema[SHARED_KEY(PropertyNames)]) { + for (Dict::iterator i(dict); i; ++i) { + slice key = i.keyString(); + RetainedValue keyVal = i.key(); + if (keyVal.type() != kFLString) + keyVal = RetainedValue::newString(key); + if (auto err = check(keyVal, propertyNames, schemaBase); !ok(err)) [[unlikely]] + return err; + } + } + + Dict properties = schema[SHARED_KEY(Properties)].asDict(); + Value additionalProperties = schema[SHARED_KEY(AdditionalProperties)]; + Dict patternProperties = schema[SHARED_KEY(PatternProperties)].asDict(); + + // If "additionalProperties" is present and its value is not "true", + // use a C++ set to track what properties have been matched: + optional> unmatchedProperties; + if (additionalProperties && !(additionalProperties.type() == kFLBoolean && + additionalProperties.asBool() == true)) { + unmatchedProperties.emplace(); + unmatchedProperties->reserve(dict.count()); + for (Dict::iterator i(dict); i; ++i) + unmatchedProperties->insert(i.keyString()); + } + + // "properties": Specific property names with their own sub-schemas + for (Dict::iterator i(properties, sSchemaSharedKeys); i; ++i) { + slice key = i.keyString(); + if (Value val = dict[key]) { + if (auto err = check(val, i.value(), schemaBase); !ok(err)) [[unlikely]] + return err; + if (unmatchedProperties) + unmatchedProperties->erase(key); + } + } + + // "patternProperties": Sub-schemas to apply to properties whose names match patterns + if (patternProperties) { + for (Dict::iterator i(patternProperties, sSchemaSharedKeys); i; ++i) { + slice pattern = i.keyString(); + for (Dict::iterator j(dict); j; ++j) { + slice dictKey = j.keyString(); + if (_schemaImpl.stringMatchesPattern(dictKey, pattern)) { + if (auto err = check(j.value(), i.value(), schemaBase); !ok(err)) [[unlikely]] + return err; + if (unmatchedProperties) + unmatchedProperties->erase(dictKey); + } + } + } + } + + // "additionalProperties": Schema for all properties not covered by the above + if (additionalProperties && unmatchedProperties) { + for (slice key : *unmatchedProperties) { + if (auto err = check(dict[key], additionalProperties, schemaBase); !ok(err)) [[unlikely]] + return err; + } + } + + return {}; + } + + +#pragma mark - TYPE CHECKING: + + + /// Checks the type of a Value against a schema "type" property (string or array). + bool JSONSchema::Validation::isType(Value value, Value typeVal) { + if (slice typeStr = typeVal.asString()) { + // String dictates the type of the value: + return isType(value, typeStr); + } else if (Array types = typeVal.asArray()) { + // Array means any of the types may match: + bool matches = false; + for (Array::iterator i(types); i; ++i) { + typeStr = i->asString(); + if (!typeStr) [[unlikely]] + fail("'type' array must contain only strings"); + if (isType(value, typeStr)) { + matches = true; + break; + } + } + return matches; + } else { + fail("'type' must be a string or array of strings"); + } + } + + + /// Checks the type of a Value against a schema "type" string. + bool JSONSchema::Validation::isType(Value value, slice type) { + FLValueType valType = value.type(); + if (type == "integer") { + return valType == kFLNumber && isIntegral(value); + } else { + auto i = ranges::find(kFLTypeNames, type); + if (i == ranges::end(kFLTypeNames)) [[unlikely]] + fail("unknown type name \"%.*s\"", FMTSLICE(type)); + return FLValueType(i - ranges::begin(kFLTypeNames)) == valType; + } + } + + +#pragma mark - ERRORS: + + + std::string_view JSONSchema::errorString(Error error) noexcept { + static constexpr string_view kErrorStrings[] = { + "ok", + "invalid", + "typeMismatch", + "outOfRange", + "notMultiple", + "tooShort", + "tooLong", + "patternMismatch", + "missingProperty", + "unknownProperty", + "notEnum", + "tooFew", + "tooMany", + "notNot", + "notUnique", + "invalidUTF8", + "unknownSchemaRef" + }; + assert(unsigned(error) < std::size(kErrorStrings)); + return kErrorStrings[unsigned(error)]; + } + + + std::string JSONSchema::Validation::errorString() const noexcept { + string err(JSONSchema::errorString(error())); + if (error() == Error::unknownSchemaRef) + err.append(": \"").append(_unknownSchema).append("\""); + return err; + } + + + string JSONSchema::Validation::errorPath() const noexcept { + if (alloc_slice path = recoverPath(_value, errorValue()); !path) + return ""; + else + return string("$").append(path); + } + + + std::pair JSONSchema::Validation::errorSchema() const noexcept { + if (Dict dict = _result.schema.asDict()) + return {_result.schemaKey, dict[_result.schemaKey]}; + else if (_result.schema) + return {nullslice, _result.schema}; + else + return {}; + } + + + string JSONSchema::Validation::errorSchemaURI() const noexcept { + if (!_result.schema) + return ""; + string uri = _schemaImpl.schemaValueURI(_result.schema); + if (!uri.ends_with('/')) + uri.append("/"); + return uri.append(_result.schemaKey); + } + +} diff --git a/Tests/SchemaTests.cc b/Tests/SchemaTests.cc new file mode 100644 index 00000000..942e6502 --- /dev/null +++ b/Tests/SchemaTests.cc @@ -0,0 +1,253 @@ +// +// SchemaTests.cc +// +// Copyright 2025-Present Couchbase, Inc. +// +// Use of this software is governed by the Business Source License included +// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified +// in that file, in accordance with the Business Source License, use of this +// software will be governed by the Apache License, Version 2.0, included in +// the file licenses/APL2.txt. +// + +#include "fleece/JSONSchema.hh" +#include "JSON5.hh" +#include "FleeceTests.hh" +#include +#include +#include +#include + +using namespace std; +using namespace fleece; + + +struct SchemaTest { + void setSchema(string const& json5) { + schema.emplace(ConvertJSON5(json5)); + } + + void checkValid(string const& json5) { + Doc test = Doc::fromJSON(ConvertJSON5(json5)); + if (auto val = schema.value().validate(test.root()); !val) { + INFO("error = " << val.errorString() << ", path = " << val.errorPath()); + FAIL_CHECK("Failed to validate: " << json5); + CHECK(val.error() == JSONSchema::Error::ok); + CHECK(val.errorString() == "ok"); + CHECK(val.errorPath() == ""); + CHECK(val.errorValue() == nullptr); + } + } + + void checkInvalid(string const& json5, JSONSchema::Error expectedErr, + string_view path, string_view badJSON, string_view schemaJSON, string_view schemaURI) { + Doc test = Doc::fromJSON(ConvertJSON5(json5)); + if (auto val = schema.value().validate(test.root()); val) { + FAIL_CHECK("Failed to detect invalid: " << json5); + } else { + INFO("doc = " << json5 << ", path = " << val.errorPath() << ", val = " << val.errorValue().toJSONString()); + CHECK(val.error() == expectedErr); + CHECK(val.errorString() == JSONSchema::errorString(expectedErr)); + CHECK(val.errorPath() == path); + CHECK(val.errorValue().toJSONString() == badJSON); + CHECK(val.errorSchema().second.toJSONString() == schemaJSON); + CHECK(val.errorSchemaURI() == schemaURI); + } + } + + optional schema; +}; + +TEST_CASE_METHOD(SchemaTest, "JSON Schema", "[Schema]") { + using enum JSONSchema::Error; + setSchema("{type: 'object', properties: {'str': {type: 'string'}, 'arr': {items: {enum: [1,2]}} }}"); + + checkValid("{}"); + checkValid("{str: 'foo'}"); + checkValid("{xxx: false, yyy: true}"); + checkInvalid("[]", typeMismatch, + "$", "[]", "\"object\"", "#/type"); + checkInvalid("{str: 17}", typeMismatch, + "$.str", "17", "\"string\"", "#/properties/str/type"); + checkInvalid("{str: 'bar', arr: [1, 2, 3.5]}", notEnum, + "$.arr[2]", "3.5", "[1,2]", "#/properties/arr/items/enum"); +} + + +TEST_CASE_METHOD(SchemaTest,"JSON Schema Test Suite", "[Schema]") { + // https://github.com/json-schema-org/JSON-Schema-Test-Suite + // NOTE: Test files that exclusively test features we don't support are commented out below. +#ifdef _WIN32 + static constexpr const char* kTestSuitePath = "../vendor\\JSON-Schema-Test-Suite\\tests\\draft2020-12\\"; +#else + static constexpr const char* kTestSuitePath = "../vendor/JSON-Schema-Test-Suite/tests/draft2020-12/"; +#endif + string testsDir = string(kTestFilesDir) + kTestSuitePath; + + static constexpr const char* kTestFiles[] = { + "additionalProperties", + "allOf", + "anchor", + "anyOf", + "boolean_schema", + "const", + "contains", + "content", + "default", + // "defs", + "dependentRequired", + "dependentSchemas", + // "dynamicRef", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "format", + "if-then-else", + "infinite-loop-detection", + "items", + "maxContains", + "maxItems", + "maxLength", + "maxProperties", + "maximum", + "minContains", + "minItems", + "minLength", + "minProperties", + "minimum", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "prefixItems", + "properties", + "propertyNames", + "ref", + // "refRemote", + "required", + "type", + "unevaluatedItems", + "unevaluatedProperties", + "uniqueItems", + // "vocabulary", + }; + + // Some individual tests that are known to fail, so we skip them: + static constexpr string_view kSkipTests[] = { + "enum/enum with [0] does not match [false]/[0.0] is valid", + "enum/enum with [1] does not match [true]/[1.0] is valid", + "ref/remote ref, containing refs itself/remote ref valid", //TODO: Unsure why this fails + }; + + // In addition, any test that throws `unsupported_schema` is skipped. + + for (auto filename : kTestFiles) { + DYNAMIC_SECTION(filename) { + Doc tests = Doc::fromJSON(readFile((testsDir + filename + ".json").c_str())); + for (Array::iterator i(tests.asArray()); i; ++i) { + Dict group = i.value().asDict(); + string_view groupName(group["description"].asString()); + DYNAMIC_SECTION(groupName) { + INFO("Schema: " << group["schema"].toJSONString()); + try { + schema.emplace(group["schema"]); + for (Array::iterator j(group["tests"].asArray()); j; ++j) { + Dict test = j.value().asDict(); + string_view testName(test["description"].asString()); + DYNAMIC_SECTION(testName) { + string fullTestName = string(filename) + '/' + string(groupName) + '/' + string(testName); + if (ranges::find(kSkipTests, fullTestName) != ranges::end(kSkipTests)) { + SKIP("Skipping known-bad test " << fullTestName); + } else { + Value data = test["data"]; + + auto val = schema->validate(data); // SHAZAM! + + if (val.ok() != test["valid"].asBool()) { + INFO("Test name: " << fullTestName); + INFO("Error: " << val.errorString() << " at " << val.errorPath()); + FAIL_CHECK( + (val ? "Should have rejected " : "Should have accepted ") + << data.toJSONString() ); + } + } + } + } + } catch (JSONSchema::unsupported_schema const& x) { + // Skip tests that use unsupported JSON Schema features + SKIP("Skipping schema '" << filename << "/" << groupName << "': " << x.what()); + } + } + } + } + } +} + + +TEST_CASE("JSON Schema benchmark", "[.Perf]") { + static constexpr const char* kDataFile = "/Users/snej/Couchbase/DataSets/travel-sample/travel.json"; + vector database; + { + Benchmark bench; + ifstream in(kDataFile); + REQUIRE(in.good()); + string line; + while (!in.eof()) { + getline(in, line); + if (line.empty()) + continue; + INFO("JSON = " << line); + bench.start(); + auto doc = Doc::fromJSON(line); + bench.stop(); + REQUIRE(doc); + database.push_back(std::move(doc)); + #ifndef NDEBUG + //if (database.size() > 2000) {break;} // speeds up debugging + #endif + } + fprintf(stderr, "Read %zu documents: ", database.size()); + bench.printReport(1.0, "document"); + } + + JSONSchema schema(readFile((string(kTestFilesDir) + "travel-schema.json").c_str())); + + SECTION("Single-threaded") { + Benchmark bench; + for (auto& doc : database) { + bench.start(); + auto result = schema.validate(doc.root()); + bench.stop(); + if (!result) { + slice id = doc.asDict()["_id"].asString(); + FAIL("Doc " << id << " failed: " << result.errorString() << " at " << result.errorPath() + << " (" << result.errorValue().toJSONString() << "), schema at " << result.errorSchemaURI()); + } + } + fprintf(stderr, "Checked %zu documents: ", database.size()); + bench.printReport(1.0, "document"); + } + + SECTION("Parallel") { + static const size_t kBatchSize = (database.size() + 15) / 16; + size_t const n = database.size(); + vector> futures; + Benchmark bench; + bench.start(); + for (size_t taskFirst = 0; taskFirst < n; taskFirst += kBatchSize) { + futures.emplace_back( async(function([&](size_t first) { + size_t last = std::min(first + kBatchSize, n); + for (size_t i = first; i < last; ++i) { + auto result = schema.validate(database[i].root()); + if (!result) + throw runtime_error("Validation failed!"); + } + }), taskFirst)); + } + for (auto& f : futures) f.wait(); + bench.stop(); + fprintf(stderr, "Checked %zu documents: ", database.size()); + bench.printReport(1.0 / n, "document"); + } +} diff --git a/Tests/travel-schema.json b/Tests/travel-schema.json new file mode 100644 index 00000000..3ad316c3 --- /dev/null +++ b/Tests/travel-schema.json @@ -0,0 +1,207 @@ +{ + "$comment": "JSON Schema for Couchbase's travel-sample database.", + "$schema": "https://json-schema.org/draft/2020-12/schema", + + "type": "object", + + "if": { + "properties": {"type": {"const": "route"}} + }, + "then": + {"$ref": "#/$defs/route"}, + "else": { + "if": { + "properties": {"type": {"const": "airline"}} + }, + "then": { + "$ref": "#/$defs/airline" + }, + "else": { + "if": { + "properties": {"type": {"const": "airport"}} + }, + "then": { + "$ref": "#/$defs/airport" + }, + "else": { + "if": { + "properties": {"type": {"const": "hotel"}} + }, + "then": { + "$ref": "#/$defs/hotel" + }, + "else": { + "$ref": "#/$defs/landmark" + } + } + } + }, + + + "$defs": { + "airline": { + "properties": { + "type": {"const": "airline"}, + "id": {"type": "number"}, + "_id": {"type": "string", "$comment": "Used in some JSON dumps"}, + "callsign": {"type": ["string", "null"], "minLength": 2, "maxLength": 20}, + "country": {"type": "string"}, + "iata": {"type": ["string", "null"], "minLength": 2, "maxLength": 4}, + "icao": {"type": "string", "minLength": 2, "maxLength": 4}, + "name": {"type": "string"} + }, + "required": ["id", "type", "name", "callsign", "country"], + "additionalProperties": false + }, + + "airport": { + "properties": { + "type": {"const": "airport"}, + "id": {"type": "number"}, + "_id": {"type": "string"}, + "airportname": {"type": "string"}, + "city": {"type": "string"}, + "country": {"type": "string"}, + "faa": {"type": ["string", "null"], "minLength": 3, "maxLength": 3}, + "geo": {"$ref": "#/$defs/geo"}, + "icao": {"type": ["string", "null"], "minLength": 2, "maxLength": 4}, + "tz": {"type": "string"} + }, + "required": ["id", "type", "airportname", "city", "country", "icao", "tz", "geo"], + "additionalProperties": false + }, + + "hotel": { + "properties": { + "type": {"const": "hotel"}, + "id": {"type": "number"}, + "_id": {"type": "string"}, + "address": {"type": ["string", "null"]}, + "alias": {"type": ["string", "null"]}, + "checkin": {"type": ["string", "null"]}, + "checkout": {"type": ["string", "null"]}, + "city": {"type": ["string", "null"]}, + "country": {"type": "string"}, + "description": {"type": "string"}, + "directions": {"type": ["string", "null"]}, + "email": {"$ref": "#/$defs/email"}, + "fax": {"type": ["string", "null"]}, + "free_breakfast": {"type": "boolean"}, + "free_internet": {"type": "boolean"}, + "free_parking": {"type": "boolean"}, + "geo": {"$ref": "#/$defs/geo"}, + "name": {"type": "string"}, + "pets_ok": {"type": "boolean"}, + "phone": {"type": ["string", "null"]}, + "price": {"type": ["string", "null"]}, + "public_likes": { + "type": ["array"], + "items": {"type": "string"} + }, + "reviews": { + "type": "array", + "items": {"$ref": "#/$defs/review"} + }, + "state": {"type": ["string", "null"]}, + "title": {"type": "string"}, + "tollfree": {"type": ["string", "null"]}, + "url": {"$ref": "#/$defs/url"}, + "vacancy": {"type": "boolean"} + }, + "required": ["id", "type", "name", "country", "city", "geo"], + "additionalProperties": false + }, + + "landmark": { + "properties": { + "type": {"const": "landmark"}, + "id": {"type": "number"}, + "_id": {"type": "string"}, + "activity": {"enum": ["buy", "do", "drink", "eat", "listing", "see"]}, + "address": {"type": ["string", "null"]}, + "alt": {"type": ["string", "null"]}, + "city": {"type": ["string", "null"]}, + "content": {"type": "string"}, + "country": {"type": "string"}, + "directions": {"type": ["string", "null"]}, + "email": {"$ref": "#/$defs/email"}, + "geo": {"$ref": "#/$defs/geo"}, + "hours": {"type": ["string", "null"]}, + "image": {"type": ["string", "null"]}, + "image_direct_url": {"$ref": "#/$defs/url"}, + "name": {"type": "string"}, + "phone": {"type": ["string", "null"]}, + "price": {"type": ["string", "null"]}, + "state": {"type": ["string", "null"]}, + "title": {"type": "string"}, + "tollfree": {"type": ["string", "null"]}, + "url": {"$ref": "#/$defs/url"} + }, + "required": ["id", "type", "name", "country", "city", "geo", "content", "activity"], + "additionalProperties": false + }, + + "route": { + "properties": { + "type": {"const": "route"}, + "id": {"type": "number"}, + "_id": {"type": "string"}, + "airline": {"type": "string"}, + "airlineid": {"type": "string"}, + "destinationairport": {"type": "string"}, + "distance": {"type": "number", "exclusiveMinimum": 0.0}, + "equipment": {"type": ["string", "null"]}, + "sourceairport": {"type": "string"}, + "stops": {"type": "integer", "minimum": 0}, + "schedule": { + "type": "array", + "items": { + "type": "object", + "properties": { + "day": {"type": "integer", "minimum": 0, "maximum": 6}, + "utc": {"type": "string", "pattern": "^\\d\\d:\\d\\d:\\d\\d$"}, + "flight": {"type": "string"} + }, + "required": ["day", "utc", "flight"], + "additionalProperties": false + }, + "minItems": 1 + } + }, + "required": ["id", "type", "airline", "airlineid", "distance", "equipment", + "sourceairport", "destinationairport", "schedule", "stops"], + "additionalProperties": false + }, + + "geo": { + "type": "object", + "properties": { + "lat": {"type": "number", "minimum": -90.0, "maximum": 90.0}, + "lon": {"type": "number", "minimum": -180.0, "maximum": 180.0}, + "alt": {"type": "number"}, + "accuracy": {"enum": ["ROOFTOP", "RANGE_INTERPOLATED", "APPROXIMATE"]} + }, + "required": ["lat", "lon"], + "additionalProperties": false + }, + + "review": { + "type": "object", + "properties": { + "author": {"type": "string"}, + "content": {"type": "string"}, + "date": {"type": "string"}, + "ratings": { + "type": "object", + "additionalProperties": {"type": "integer", "minimum": -1, "maximum": 5} + } + }, + "required": ["author", "content", "date"], + "additionalProperties": false + }, + + "email": {"type": ["string", "null"], "pattern": "@"}, + + "url": {"type": ["string", "null"], "pattern": "^([hH]ttps?://.*)?$"} + } +} diff --git a/cmake/platform_base.cmake b/cmake/platform_base.cmake index 4cf2e060..cafc887c 100644 --- a/cmake/platform_base.cmake +++ b/cmake/platform_base.cmake @@ -10,6 +10,7 @@ function(set_source_files_base) Fleece/API_Impl/Fleece.cc Fleece/API_Impl/FLEncoder.cc Fleece/API_Impl/FLSlice.cc + Fleece/API_Impl/JSONSchema.cc Fleece/Core/Array.cc Fleece/Core/Builder.cc Fleece/Core/DeepIterator.cc diff --git a/vendor/JSON-Schema-Test-Suite/LICENSE b/vendor/JSON-Schema-Test-Suite/LICENSE new file mode 100644 index 00000000..c28adbad --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Julian Berman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/JSON-Schema-Test-Suite/README.md b/vendor/JSON-Schema-Test-Suite/README.md new file mode 100644 index 00000000..bfdcb501 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/README.md @@ -0,0 +1,355 @@ +# JSON Schema Test Suite + +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](https://github.com/json-schema-org/.github/blob/main/CODE_OF_CONDUCT.md) +[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) +[![Financial Contributors on Open Collective](https://opencollective.com/json-schema/all/badge.svg?label=financial+contributors)](https://opencollective.com/json-schema) + +[![DOI](https://zenodo.org/badge/5952934.svg)](https://zenodo.org/badge/latestdoi/5952934) +[![Build Status](https://github.com/json-schema-org/JSON-Schema-Test-Suite/workflows/Test%20Suite%20Sanity%20Checking/badge.svg)](https://github.com/json-schema-org/JSON-Schema-Test-Suite/actions?query=workflow%3A%22Test+Suite+Sanity+Checking%22) + +This repository contains a set of JSON objects that implementers of JSON Schema validation libraries can use to test their validators. + +It is meant to be language agnostic and should require only a JSON parser. +The conversion of the JSON objects into tests within a specific language and test framework of choice is left to be done by the validator implementer. + +The recommended workflow of this test suite is to clone the `main` branch of this repository as a `git submodule` or `git subtree`. The `main` branch is always stable. + +## Coverage + +All JSON Schema specification releases should be well covered by this suite, including drafts 2020-12, 2019-09, 07, 06, 04 and 03. +Drafts 04 and 03 are considered "frozen" in that less effort is put in to backport new tests to these versions. + +Additional coverage is always welcome, particularly for bugs encountered in real-world implementations. +If you see anything missing or incorrect, please feel free to [file an issue](https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues) or [submit a PR](https://github.com/json-schema-org/JSON-Schema-Test-Suite). + +@gregsdennis has also started a separate [test suite](https://github.com/gregsdennis/json-schema-vocab-test-suites) that is modelled after this suite to cover third-party vocabularies. + +## Introduction to the Test Suite Structure + +The tests in this suite are contained in the `tests` directory at the root of this repository. +Inside that directory is a subdirectory for each released version of the specification. + +The structure and contents of each file in these directories is described below. + +In addition to the version-specific subdirectories, two additional directories are present: + +1. `draft-next/`: containing tests for the next version of the specification whilst it is in development +2. `latest/`: a symbolic link which points to the directory which is the most recent release (which may be useful for implementations providing specific entry points for validating against the latest version of the specification) + +Inside each version directory there are a number of `.json` files each containing a collection of related tests. +Often the grouping is by property under test, but not always. +In addition to the `.json` files, each version directory contains one or more special subdirectories whose purpose is [described below](#subdirectories-within-each-draft), and which contain additional `.json` files. + +Each `.json` file consists of a single JSON array of test cases. + +### Terminology + +For clarity, we first define this document's usage of some testing terminology: + +| term | definition | +|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **test suite** | the entirety of the contents of this repository, containing tests for multiple different releases of the JSON Schema specification | +| **test case** | a single schema, along with a description and an array of *test*s | +| **test** | within a *test case*, a single test example, containing a description, instance and a boolean indicating whether the instance is valid under the test case schema | +| **test runner** | a program, external to this repository and authored by a user of this suite, which is executing each of the tests in the suite | + +An example illustrating this structure is immediately below, and a JSON Schema containing a formal definition of the contents of test cases can be found [alongside this README](./test-schema.json). + +### Sample Test Case + +Here is a single *test case*, containing one or more tests: + +```json +{ + "description": "The test case description", + "schema": { "type": "string" }, + "tests": [ + { + "description": "a test with a valid instance", + "data": "a string", + "valid": true + }, + { + "description": "a test with an invalid instance", + "data": 15, + "valid": false + } + ] +} +``` + +### Subdirectories Within Each Draft + +There is currently only one additional subdirectory that may exist within each draft test directory. + +This is: + +1. `optional/`: Contains tests that are considered optional. + +Note, the `optional/` subdirectory today conflates many reasons why a test may be optional -- it may be because tests within a particular file are indeed not required by the specification but still potentially useful to an implementer, or it may be because tests within it only apply to programming languages with particular functionality (in +which case they are not truly optional in such a language). +In the future this directory structure will be made richer to reflect these differences more clearly. + +## Using the Suite to Test a Validator Implementation + +The test suite structure was described [above](#introduction-to-the-test-suite-structure). + +If you are authoring a new validator implementation, or adding support for an additional version of the specification, this section describes: + +1. How to implement a test runner which passes tests to your validator +2. Assumptions the suite makes about how the test runner will configure your validator +3. Invariants the test suite claims to hold for its tests + +### How to Implement a Test Runner + +Presented here is a possible implementation of a test runner. +The precise steps described do not need to be followed exactly, but the results of your own procedure should produce the same effects. + +To test a specific version: + +* For 2019-09 and later published drafts, implementations that are able to detect the draft of each schema via `$schema` SHOULD be configured to do so +* For draft-07 and earlier, draft-next, and implementations unable to detect via `$schema`, implementations MUST be configured to expect the draft matching the test directory name +* Load any remote references [described below](#additional-assumptions) and configure your implementation to retrieve them via their URIs +* Walk the filesystem tree for that version's subdirectory and for each `.json` file found: + + * if the file is located in the root of the version directory: + + * for each test case present in the file: + + * load the schema from the `"schema"` property + * load (or log) the test case description from the `"description"` property for debugging or outputting + * for each test in the `"tests"` property: + + * load the instance to be tested from the `"data"` property + * load (or log) the individual test description from the `"description"` property for debugging or outputting + + * use the schema loaded above to validate whether the instance is considered valid under your implementation + + * if the result from your implementation matches the value found in the `"valid"` property, your implementation correctly implements the specific example + * if the result does not match, or your implementation errors or crashes, your implementation does not correctly implement the specific example + + * otherwise it is located in a special subdirectory as described above. + Follow the additional assumptions and restrictions for the containing subdirectory, then run the test case as above. + +If your implementation supports multiple versions, run the above procedure for each version supported, configuring your implementation as appropriate to call each version individually. + +### Additional Assumptions + +1. The suite, notably in its `refRemote.json` file in each draft, expects a number of remote references to be configured. + These are JSON documents, identified by URI, which are used by the suite to test the behavior of the `$ref` keyword (and related keywords). + Depending on your implementation, you may configure how to "register" these *either*: + + * by directly retrieving them off the filesystem from the `remotes/` directory, in which case you should load each schema with a retrieval URI of `http://localhost:1234` followed by the relative path from the remotes directory -- e.g. a `$ref` to `http://localhost:1234/foo/bar/baz.json` is expected to resolve to the contents of the file at `remotes/foo/bar/baz.json` + + * or alternatively, by executing `bin/jsonschema_suite remotes` using the executable in the `bin/` directory, which will output a JSON object containing all of the remotes combined, e.g.: + + ``` + + $ bin/jsonschema_suite remotes + ``` + ```json + { + "http://localhost:1234/baseUriChange/folderInteger.json": { + "type": "integer" + }, + "http://localhost:1234/baseUriChangeFolder/folderInteger.json": { + "type": "integer" + } + } + ``` + +2. Test cases found within [special subdirectories](#subdirectories-within-each-draft) may require additional configuration to run. + In particular, when running tests within the `optional/format` subdirectory, test runners should configure implementations to enable format validation, where the implementation supports it. + +### Invariants & Guarantees + +The test suite guarantees a number of things about tests it defines. +Any deviation from the below is generally considered a bug. +If you suspect one, please [file an issue](https://github.com/json-schema-org/JSON-Schema-Test-Suite/issues/new): + +1. All files containing test cases are valid JSON. +2. The contents of the `"schema"` property in a test case are always valid + JSON Schemas under the corresponding specification. + + The rationale behind this is that we are testing instances in a test's `"data"` element, and not the schema itself. + A number of tests *do* test the validity of a schema itself, but do so by representing the schema as an instance inside a test, with the associated meta-schema in the `"schema"` property (via the `"$ref"` keyword): + + ```json + { + "description": "Test the \"type\" schema keyword", + "schema": { + "$ref": "https://json-schema.org/draft/2019-09/schema" + }, + "tests": [ + { + "description": "Valid: string", + "data": { + "type": "string" + }, + "valid": true + }, + { + "description": "Invalid: null", + "data": { + "type": null + }, + "valid": false + } + ] + } + ``` + See below for some [known limitations](#known-limitations). + +## Known Limitations + +This suite expresses its assertions about the behavior of an implementation *within* JSON Schema itself. +Each test is the application of a schema to a particular instance. +This means that the suite of tests can test against any behavior a schema can describe, and conversely cannot test against any behavior which a schema is incapable of representing, even if the behavior is mandated by the specification. + +For example, a schema can require that a string is a _URI-reference_ and even that it matches a certain pattern, but even though the specification contains [recommendations about URIs being normalized](https://json-schema.org/draft/2020-12/json-schema-core.html#name-the-id-keyword), a JSON schema cannot today represent this assertion within the core vocabularies of the specifications, so no test covers this behavior. + +## Who Uses the Test Suite + +This suite is being used by: + +### Clojure + +* [jinx](https://github.com/juxt/jinx) +* [json-schema](https://github.com/tatut/json-schema) + +### Coffeescript + +* [jsck](https://github.com/pandastrike/jsck) + +### Common Lisp + +* [json-schema](https://github.com/fisxoj/json-schema) + +### C++ + +* [Modern C++ JSON schema validator](https://github.com/pboettch/json-schema-validator) +* [Valijson](https://github.com/tristanpenman/valijson) + +### Dart + +* [json\_schema](https://github.com/patefacio/json_schema) + +### Elixir + +* [ex\_json\_schema](https://github.com/jonasschmidt/ex_json_schema) + +### Erlang + +* [jesse](https://github.com/for-GET/jesse) + +### Go + +* [gojsonschema](https://github.com/sigu-399/gojsonschema) +* [validate-json](https://github.com/cesanta/validate-json) + +### Haskell + +* [aeson-schema](https://github.com/timjb/aeson-schema) +* [hjsonschema](https://github.com/seagreen/hjsonschema) + +### Java + +* [json-schema-validation-comparison](https://www.creekservice.org/json-schema-validation-comparison/functional) (Comparison site for JVM-based validator implementations) +* [json-schema-validator](https://github.com/daveclayton/json-schema-validator) +* [everit-org/json-schema](https://github.com/everit-org/json-schema) +* [networknt/json-schema-validator](https://github.com/networknt/json-schema-validator) +* [Justify](https://github.com/leadpony/justify) +* [Snow](https://github.com/ssilverman/snowy-json) +* [jsonschemafriend](https://github.com/jimblackler/jsonschemafriend) +* [OpenAPI JSON Schema Generator](https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) + +### JavaScript + +* [json-schema-benchmark](https://github.com/Muscula/json-schema-benchmark) +* [direct-schema](https://github.com/IreneKnapp/direct-schema) +* [is-my-json-valid](https://github.com/mafintosh/is-my-json-valid) +* [jassi](https://github.com/iclanzan/jassi) +* [JaySchema](https://github.com/natesilva/jayschema) +* [json-schema-valid](https://github.com/ericgj/json-schema-valid) +* [Jsonary](https://github.com/jsonary-js/jsonary) +* [jsonschema](https://github.com/tdegrunt/jsonschema) +* [request-validator](https://github.com/bugventure/request-validator) +* [skeemas](https://github.com/Prestaul/skeemas) +* [tv4](https://github.com/geraintluff/tv4) +* [z-schema](https://github.com/zaggino/z-schema) +* [jsen](https://github.com/bugventure/jsen) +* [ajv](https://github.com/epoberezkin/ajv) +* [djv](https://github.com/korzio/djv) + +### Kotlin + +* [json-schema-validation-comparison](https://www.creekservice.org/json-schema-validation-comparison/functional) (Comparison site for JVM-based validator implementations) + +### Node.js + +For node.js developers, the suite is also available as an [npm](https://www.npmjs.com/package/@json-schema-org/tests) package. + +Node-specific support is maintained in a [separate repository](https://github.com/json-schema-org/json-schema-test-suite-npm) which also welcomes your contributions! + +### .NET + +* [JsonSchema.Net](https://github.com/gregsdennis/json-everything) +* [Newtonsoft.Json.Schema](https://github.com/JamesNK/Newtonsoft.Json.Schema) + +### Perl + +* [Test::JSON::Schema::Acceptance](https://github.com/karenetheridge/Test-JSON-Schema-Acceptance) (a wrapper of this test suite) +* [JSON::Schema::Modern](https://github.com/karenetheridge/JSON-Schema-Modern) +* [JSON::Schema::Tiny](https://github.com/karenetheridge/JSON-Schema-Tiny) + +### PHP + +* [opis/json-schema](https://github.com/opis/json-schema) +* [json-schema](https://github.com/justinrainbow/json-schema) +* [json-guard](https://github.com/thephpleague/json-guard) + +### PostgreSQL + +* [postgres-json-schema](https://github.com/gavinwahl/postgres-json-schema) +* [is\_jsonb\_valid](https://github.com/furstenheim/is_jsonb_valid) + +### Python + +* [jsonschema](https://github.com/Julian/jsonschema) +* [fastjsonschema](https://github.com/seznam/python-fastjsonschema) +* [hypothesis-jsonschema](https://github.com/Zac-HD/hypothesis-jsonschema) +* [jschon](https://github.com/marksparkza/jschon) +* [OpenAPI JSON Schema Generator](https://github.com/openapi-json-schema-tools/openapi-json-schema-generator) + +### Ruby + +* [json-schema](https://github.com/hoxworth/json-schema) +* [json\_schemer](https://github.com/davishmcclurg/json_schemer) + +### Rust + +* [jsonschema](https://github.com/Stranger6667/jsonschema-rs) +* [valico](https://github.com/rustless/valico) + +### Scala + +* [json-schema-validation-comparison](https://www.creekservice.org/json-schema-validation-comparison/functional) (Comparison site for JVM-based validator implementations) +* [typed-json](https://github.com/frawa/typed-json) + +### Swift + +* [JSONSchema](https://github.com/kylef/JSONSchema.swift) + +If you use it as well, please fork and send a pull request adding yourself to +the list :). + +## Contributing + +If you see something missing or incorrect, a pull request is most welcome! + +There are some sanity checks in place for testing the test suite. You can run +them with `bin/jsonschema_suite check` or `tox`. They will be run automatically +by [GitHub Actions](https://github.com/json-schema-org/JSON-Schema-Test-Suite/actions?query=workflow%3A%22Test+Suite+Sanity+Checking%22) +as well. + +This repository is maintained by the JSON Schema organization, and will be governed by the JSON Schema steering committee (once it exists). diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/additionalProperties.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/additionalProperties.json new file mode 100644 index 00000000..9618575e --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/additionalProperties.json @@ -0,0 +1,219 @@ +[ + { + "description": + "additionalProperties being false does not allow other properties", + "specification": [ { "core":"10.3.2.3", "quote": "The value of \"additionalProperties\" MUST be a valid JSON Schema. Boolean \"false\" forbids everything." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo": {}, "bar": {}}, + "patternProperties": { "^v": {} }, + "additionalProperties": false + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : "boom"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobarbaz", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "patternProperties are not additional properties", + "data": {"foo":1, "vroom": 2}, + "valid": true + } + ] + }, + { + "description": "non-ASCII pattern with additionalProperties", + "specification": [ { "core":"10.3.2.3"} ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": {"^á": {}}, + "additionalProperties": false + }, + "tests": [ + { + "description": "matching the pattern is valid", + "data": {"ármányos": 2}, + "valid": true + }, + { + "description": "not matching the pattern is invalid", + "data": {"élmény": 2}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with schema", + "specification": [ { "core":"10.3.2.3", "quote": "The value of \"additionalProperties\" MUST be a valid JSON Schema." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo": {}, "bar": {}}, + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "no additional properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "an additional valid property is valid", + "data": {"foo" : 1, "bar" : 2, "quux" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1, "bar" : 2, "quux" : 12}, + "valid": false + } + ] + }, + { + "description": "additionalProperties can exist by itself", + "specification": [ { "core":"10.3.2.3", "quote": "With no other applicator applying to object instances. This validates all the instance values irrespective of their property names" } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "an additional valid property is valid", + "data": {"foo" : true}, + "valid": true + }, + { + "description": "an additional invalid property is invalid", + "data": {"foo" : 1}, + "valid": false + } + ] + }, + { + "description": "additionalProperties are allowed by default", + "specification": [ { "core":"10.3.2.3", "quote": "Omitting this keyword has the same assertion behavior as an empty schema." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo": {}, "bar": {}} + }, + "tests": [ + { + "description": "additional properties are allowed", + "data": {"foo": 1, "bar": 2, "quux": true}, + "valid": true + } + ] + }, + { + "description": "additionalProperties does not look in applicators", + "specification":[ { "core": "10.2", "quote": "Subschemas of applicator keywords evaluate the instance completely independently such that the results of one such subschema MUST NOT impact the results of sibling subschemas." } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {"properties": {"foo": {}}} + ], + "additionalProperties": {"type": "boolean"} + }, + "tests": [ + { + "description": "properties defined in allOf are not examined", + "data": {"foo": 1, "bar": true}, + "valid": false + } + ] + }, + { + "description": "additionalProperties with null valued instance properties", + "specification": [ { "core":"10.3.2.3" } ], + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "additionalProperties with propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": { + "maxLength": 5 + }, + "additionalProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "Valid against both keywords", + "data": { "apple": 4 }, + "valid": true + }, + { + "description": "Valid against propertyNames, but not additionalProperties", + "data": { "fig": 2, "pear": "available" }, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo" : {}, + "foo2": { + "properties": { + "bar": {} + } + } + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "additionalProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "additionalProperties can't see bar even when foo2 is present", + "data": {"foo2": "", "bar": ""}, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/allOf.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/allOf.json new file mode 100644 index 00000000..9e87903f --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/allOf.json @@ -0,0 +1,312 @@ +[ + { + "description": "allOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "allOf", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "mismatch second", + "data": {"foo": "baz"}, + "valid": false + }, + { + "description": "mismatch first", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "wrong type", + "data": {"foo": "baz", "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "allOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"bar": {"type": "integer"}}, + "required": ["bar"], + "allOf" : [ + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + }, + { + "properties": { + "baz": {"type": "null"} + }, + "required": ["baz"] + } + ] + }, + "tests": [ + { + "description": "valid", + "data": {"foo": "quux", "bar": 2, "baz": null}, + "valid": true + }, + { + "description": "mismatch base schema", + "data": {"foo": "quux", "baz": null}, + "valid": false + }, + { + "description": "mismatch first allOf", + "data": {"bar": 2, "baz": null}, + "valid": false + }, + { + "description": "mismatch second allOf", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "mismatch both", + "data": {"bar": 2}, + "valid": false + } + ] + }, + { + "description": "allOf simple types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {"maximum": 30}, + {"minimum": 20} + ] + }, + "tests": [ + { + "description": "valid", + "data": 25, + "valid": true + }, + { + "description": "mismatch one", + "data": 35, + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "allOf with boolean schemas, some false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with two empty schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {}, + {} + ] + }, + "tests": [ + { + "description": "any data is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "allOf with the first empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + {}, + { "type": "number" } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "allOf with the last empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "nested allOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "allOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "allOf combined with anyOf, oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ { "multipleOf": 2 } ], + "anyOf": [ { "multipleOf": 3 } ], + "oneOf": [ { "multipleOf": 5 } ] + }, + "tests": [ + { + "description": "allOf: false, anyOf: false, oneOf: false", + "data": 1, + "valid": false + }, + { + "description": "allOf: false, anyOf: false, oneOf: true", + "data": 5, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: false", + "data": 3, + "valid": false + }, + { + "description": "allOf: false, anyOf: true, oneOf: true", + "data": 15, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: false", + "data": 2, + "valid": false + }, + { + "description": "allOf: true, anyOf: false, oneOf: true", + "data": 10, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: false", + "data": 6, + "valid": false + }, + { + "description": "allOf: true, anyOf: true, oneOf: true", + "data": 30, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anchor.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anchor.json new file mode 100644 index 00000000..99143fa1 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anchor.json @@ -0,0 +1,120 @@ +[ + { + "description": "Location-independent identifier", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#foo", + "$defs": { + "A": { + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with absolute URI", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/bar#foo", + "$defs": { + "A": { + "$id": "http://localhost:1234/draft2020-12/bar", + "$anchor": "foo", + "type": "integer" + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "Location-independent identifier with base URI change in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/root", + "$ref": "http://localhost:1234/draft2020-12/nested.json#foo", + "$defs": { + "A": { + "$id": "nested.json", + "$defs": { + "B": { + "$anchor": "foo", + "type": "integer" + } + } + } + } + }, + "tests": [ + { + "data": 1, + "description": "match", + "valid": true + }, + { + "data": "a", + "description": "mismatch", + "valid": false + } + ] + }, + { + "description": "same $anchor with different base uri", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/foobar", + "$defs": { + "A": { + "$id": "child1", + "allOf": [ + { + "$id": "child2", + "$anchor": "my_anchor", + "type": "number" + }, + { + "$anchor": "my_anchor", + "type": "string" + } + ] + } + }, + "$ref": "child1#my_anchor" + }, + "tests": [ + { + "description": "$ref resolves to /$defs/A/allOf/1", + "data": "a", + "valid": true + }, + { + "description": "$ref does not resolve to /$defs/A/allOf/0", + "data": 1, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anyOf.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anyOf.json new file mode 100644 index 00000000..89b192db --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/anyOf.json @@ -0,0 +1,203 @@ +[ + { + "description": "anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first anyOf valid", + "data": 1, + "valid": true + }, + { + "description": "second anyOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both anyOf valid", + "data": 3, + "valid": true + }, + { + "description": "neither anyOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "anyOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "anyOf" : [ + { + "maxLength": 2 + }, + { + "minLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one anyOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both anyOf invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [true, true] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, some true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [true, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "anyOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "anyOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first anyOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second anyOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both anyOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": true + }, + { + "description": "neither anyOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "anyOf with one empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is valid", + "data": 123, + "valid": true + } + ] + }, + { + "description": "nested anyOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "anyOf": [ + { + "anyOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/boolean_schema.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/boolean_schema.json new file mode 100644 index 00000000..6d40f23f --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/boolean_schema.json @@ -0,0 +1,104 @@ +[ + { + "description": "boolean schema 'true'", + "schema": true, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "boolean schema 'false'", + "schema": false, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/const.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/const.json new file mode 100644 index 00000000..50be86a0 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/const.json @@ -0,0 +1,387 @@ +[ + { + "description": "const validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 2 + }, + "tests": [ + { + "description": "same value is valid", + "data": 2, + "valid": true + }, + { + "description": "another value is invalid", + "data": 5, + "valid": false + }, + { + "description": "another type is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "const with object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": {"foo": "bar", "baz": "bax"} + }, + "tests": [ + { + "description": "same object is valid", + "data": {"foo": "bar", "baz": "bax"}, + "valid": true + }, + { + "description": "same object with different property order is valid", + "data": {"baz": "bax", "foo": "bar"}, + "valid": true + }, + { + "description": "another object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "another type is invalid", + "data": [1, 2], + "valid": false + } + ] + }, + { + "description": "const with array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [{ "foo": "bar" }] + }, + "tests": [ + { + "description": "same array is valid", + "data": [{"foo": "bar"}], + "valid": true + }, + { + "description": "another array item is invalid", + "data": [2], + "valid": false + }, + { + "description": "array with additional items is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + }, + { + "description": "const with null", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": null + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "not null is invalid", + "data": 0, + "valid": false + } + ] + }, + { + "description": "const with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": false + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "const with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": true + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "const with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [false] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "const with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": [true] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "const with {\"a\": false} does not match {\"a\": 0}", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": {"a": false} + }, + "tests": [ + { + "description": "{\"a\": false} is valid", + "data": {"a": false}, + "valid": true + }, + { + "description": "{\"a\": 0} is invalid", + "data": {"a": 0}, + "valid": false + }, + { + "description": "{\"a\": 0.0} is invalid", + "data": {"a": 0.0}, + "valid": false + } + ] + }, + { + "description": "const with {\"a\": true} does not match {\"a\": 1}", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": {"a": true} + }, + "tests": [ + { + "description": "{\"a\": true} is valid", + "data": {"a": true}, + "valid": true + }, + { + "description": "{\"a\": 1} is invalid", + "data": {"a": 1}, + "valid": false + }, + { + "description": "{\"a\": 1.0} is invalid", + "data": {"a": 1.0}, + "valid": false + } + ] + }, + { + "description": "const with 0 does not match other zero-like types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 0 + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "empty string is invalid", + "data": "", + "valid": false + } + ] + }, + { + "description": "const with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 1 + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "const with -2.0 matches integer and float types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": -2.0 + }, + "tests": [ + { + "description": "integer -2 is valid", + "data": -2, + "valid": true + }, + { + "description": "integer 2 is invalid", + "data": 2, + "valid": false + }, + { + "description": "float -2.0 is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float 2.0 is invalid", + "data": 2.0, + "valid": false + }, + { + "description": "float -2.00001 is invalid", + "data": -2.00001, + "valid": false + } + ] + }, + { + "description": "float and integers are equal up to 64-bit representation limits", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": 9007199254740992 + }, + "tests": [ + { + "description": "integer is valid", + "data": 9007199254740992, + "valid": true + }, + { + "description": "integer minus one is invalid", + "data": 9007199254740991, + "valid": false + }, + { + "description": "float is valid", + "data": 9007199254740992.0, + "valid": true + }, + { + "description": "float minus one is invalid", + "data": 9007199254740991.0, + "valid": false + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "const": "hello\u0000there" + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/contains.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/contains.json new file mode 100644 index 00000000..08a00a75 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/contains.json @@ -0,0 +1,176 @@ +[ + { + "description": "contains keyword validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"minimum": 5} + }, + "tests": [ + { + "description": "array with item matching schema (5) is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with item matching schema (6) is valid", + "data": [3, 4, 6], + "valid": true + }, + { + "description": "array with two items matching schema (5, 6) is valid", + "data": [3, 4, 5, 6], + "valid": true + }, + { + "description": "array without items matching schema is invalid", + "data": [2, 3, 4], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "not array is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "contains keyword with const keyword", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { "const": 5 } + }, + "tests": [ + { + "description": "array with item 5 is valid", + "data": [3, 4, 5], + "valid": true + }, + { + "description": "array with two items 5 is valid", + "data": [3, 4, 5, 5], + "valid": true + }, + { + "description": "array without item 5 is invalid", + "data": [1, 2, 3, 4], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": true + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains keyword with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + }, + { + "description": "non-arrays are valid", + "data": "contains does not apply to strings", + "valid": true + } + ] + }, + { + "description": "items + contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { "multipleOf": 2 }, + "contains": { "multipleOf": 3 } + }, + "tests": [ + { + "description": "matches items, does not match contains", + "data": [ 2, 4, 8 ], + "valid": false + }, + { + "description": "does not match items, matches contains", + "data": [ 3, 6, 9 ], + "valid": false + }, + { + "description": "matches both items and contains", + "data": [ 6, 12 ], + "valid": true + }, + { + "description": "matches neither items nor contains", + "data": [ 1, 5 ], + "valid": false + } + ] + }, + { + "description": "contains with false if subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "if": false, + "else": true + } + }, + "tests": [ + { + "description": "any non-empty array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "contains with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null items", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/content.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/content.json new file mode 100644 index 00000000..698f7805 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/content.json @@ -0,0 +1,131 @@ +[ + { + "description": "validation of string-encoded content based on media type", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json" + }, + "tests": [ + { + "description": "a valid JSON document", + "data": "{\"foo\": \"bar\"}", + "valid": true + }, + { + "description": "an invalid JSON document; validates true", + "data": "{:}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary string-encoding", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64 string", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "an invalid base64 string (% is not a valid character); validates true", + "data": "eyJmb28iOi%iYmFyIn0K", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64" + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + }, + { + "description": "validation of binary-encoded media type documents with schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contentMediaType": "application/json", + "contentEncoding": "base64", + "contentSchema": { "type": "object", "required": ["foo"], "properties": { "foo": { "type": "string" } } } + }, + "tests": [ + { + "description": "a valid base64-encoded JSON document", + "data": "eyJmb28iOiAiYmFyIn0K", + "valid": true + }, + { + "description": "another valid base64-encoded JSON document", + "data": "eyJib28iOiAyMCwgImZvbyI6ICJiYXoifQ==", + "valid": true + }, + { + "description": "an invalid base64-encoded JSON document; validates true", + "data": "eyJib28iOiAyMH0=", + "valid": true + }, + { + "description": "an empty object as a base64-encoded JSON document; validates true", + "data": "e30=", + "valid": true + }, + { + "description": "an empty array as a base64-encoded JSON document", + "data": "W10=", + "valid": true + }, + { + "description": "a validly-encoded invalid JSON document; validates true", + "data": "ezp9Cg==", + "valid": true + }, + { + "description": "an invalid base64 string that is valid JSON; validates true", + "data": "{}", + "valid": true + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/default.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/default.json new file mode 100644 index 00000000..ceb3ae27 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/default.json @@ -0,0 +1,82 @@ +[ + { + "description": "invalid type for default", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "type": "integer", + "default": [] + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"foo": 13}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "invalid string value for default", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "bar": { + "type": "string", + "minLength": 4, + "default": "bad" + } + } + }, + "tests": [ + { + "description": "valid when property is specified", + "data": {"bar": "good"}, + "valid": true + }, + { + "description": "still valid when the invalid default is used", + "data": {}, + "valid": true + } + ] + }, + { + "description": "the default keyword does not do anything if the property is missing", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "alpha": { + "type": "number", + "maximum": 3, + "default": 5 + } + } + }, + "tests": [ + { + "description": "an explicit property value is checked against maximum (passing)", + "data": { "alpha": 1 }, + "valid": true + }, + { + "description": "an explicit property value is checked against maximum (failing)", + "data": { "alpha": 5 }, + "valid": false + }, + { + "description": "missing properties are not filled in with the default", + "data": {}, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/defs.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/defs.json new file mode 100644 index 00000000..da2a503b --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/defs.json @@ -0,0 +1,21 @@ +[ + { + "description": "validate definition against metaschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://json-schema.org/draft/2020-12/schema" + }, + "tests": [ + { + "description": "valid definition schema", + "data": {"$defs": {"foo": {"type": "integer"}}}, + "valid": true + }, + { + "description": "invalid definition schema", + "data": {"$defs": {"foo": {"type": 1}}}, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentRequired.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentRequired.json new file mode 100644 index 00000000..2baa38e9 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentRequired.json @@ -0,0 +1,152 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": {"bar": ["foo"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependant", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "with dependency", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "empty dependents", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": {"bar": []} + }, + "tests": [ + { + "description": "empty object", + "data": {}, + "valid": true + }, + { + "description": "object with one property", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "non-object is valid", + "data": 1, + "valid": true + } + ] + }, + { + "description": "multiple dependents required", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": {"quux": ["foo", "bar"]} + }, + "tests": [ + { + "description": "neither", + "data": {}, + "valid": true + }, + { + "description": "nondependants", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "with dependencies", + "data": {"foo": 1, "bar": 2, "quux": 3}, + "valid": true + }, + { + "description": "missing dependency", + "data": {"foo": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing other dependency", + "data": {"bar": 1, "quux": 2}, + "valid": false + }, + { + "description": "missing both dependencies", + "data": {"quux": 1}, + "valid": false + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentRequired": { + "foo\nbar": ["foo\rbar"], + "foo\"bar": ["foo'bar"] + } + }, + "tests": [ + { + "description": "CRLF", + "data": { + "foo\nbar": 1, + "foo\rbar": 2 + }, + "valid": true + }, + { + "description": "quoted quotes", + "data": { + "foo'bar": 1, + "foo\"bar": 2 + }, + "valid": true + }, + { + "description": "CRLF missing dependent", + "data": { + "foo\nbar": 1, + "foo": 2 + }, + "valid": false + }, + { + "description": "quoted quotes missing dependent", + "data": { + "foo\"bar": 2 + }, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentSchemas.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentSchemas.json new file mode 100644 index 00000000..1c5f0574 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dependentSchemas.json @@ -0,0 +1,171 @@ +[ + { + "description": "single dependency", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentSchemas": { + "bar": { + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "integer"} + } + } + } + }, + "tests": [ + { + "description": "valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "no dependency", + "data": {"foo": "quux"}, + "valid": true + }, + { + "description": "wrong type", + "data": {"foo": "quux", "bar": 2}, + "valid": false + }, + { + "description": "wrong type other", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + }, + { + "description": "wrong type both", + "data": {"foo": "quux", "bar": "quux"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["bar"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "boolean subschemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentSchemas": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "object with property having schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property having schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "dependencies with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "dependentSchemas": { + "foo\tbar": {"minProperties": 4}, + "foo'bar": {"required": ["foo\"bar"]} + } + }, + "tests": [ + { + "description": "quoted tab", + "data": { + "foo\tbar": 1, + "a": 2, + "b": 3, + "c": 4 + }, + "valid": true + }, + { + "description": "quoted quote", + "data": { + "foo'bar": {"foo\"bar": 1} + }, + "valid": false + }, + { + "description": "quoted tab invalid under dependent schema", + "data": { + "foo\tbar": 1, + "a": 2 + }, + "valid": false + }, + { + "description": "quoted quote invalid under dependent schema", + "data": {"foo'bar": 1}, + "valid": false + } + ] + }, + { + "description": "dependent subschema incompatible with root", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": {} + }, + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "matches root", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "matches dependency", + "data": {"bar": 1}, + "valid": true + }, + { + "description": "matches both", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "no dependency", + "data": {"baz": 1}, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dynamicRef.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dynamicRef.json new file mode 100644 index 00000000..ffa211ba --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/dynamicRef.json @@ -0,0 +1,815 @@ +[ + { + "description": "A $dynamicRef to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamicRef-dynamicAnchor-same-schema/root", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef to an $anchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamicRef-anchor-same-schema/root", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "foo": { + "$anchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $ref to a $dynamicAnchor in the same schema resource behaves like a normal $ref to an $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/ref-dynamicAnchor-same-schema/root", + "type": "array", + "items": { "$ref": "#items" }, + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef resolves to the first $dynamicAnchor still in scope that is encountered when the schema is evaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/typical-dynamic-resolution/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "A $dynamicRef without anchor in fragment behaves identical to $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamicRef-without-anchor/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#/$defs/items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items", + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "An array of strings is invalid", + "data": ["foo", "bar"], + "valid": false + }, + { + "description": "An array of numbers is valid", + "data": [24, 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef with intermediate scopes that don't include a matching $dynamicAnchor does not affect dynamic scope resolution", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-with-intermediate-scopes/root", + "$ref": "intermediate-scope", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "intermediate-scope": { + "$id": "intermediate-scope", + "$ref": "list" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "An array of strings is valid", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "An array containing non-strings is invalid", + "data": ["foo", 42], + "valid": false + } + ] + }, + { + "description": "An $anchor with the same name as a $dynamicAnchor is not used for dynamic scope resolution", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-ignores-anchors/root", + "$ref": "list", + "$defs": { + "foo": { + "$anchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to satisfy the bookending requirement", + "$dynamicAnchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "Any array is valid", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef without a matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-resolution-without-bookend/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref", + "$anchor": "items" + } + } + } + } + }, + "tests": [ + { + "description": "Any array is valid", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef with a non-matching $dynamicAnchor in the same schema resource behaves like a normal $ref to $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/unmatched-dynamic-anchor/root", + "$ref": "list", + "$defs": { + "foo": { + "$dynamicAnchor": "items", + "type": "string" + }, + "list": { + "$id": "list", + "type": "array", + "items": { "$dynamicRef": "#items" }, + "$defs": { + "items": { + "$comment": "This is only needed to give the reference somewhere to resolve to when it behaves like $ref", + "$anchor": "items", + "$dynamicAnchor": "foo" + } + } + } + } + }, + "tests": [ + { + "description": "Any array is valid", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "A $dynamicRef that initially resolves to a schema with a matching $dynamicAnchor resolves to the first $dynamicAnchor in the dynamic scope", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/relative-dynamic-reference/root", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "foo": { "const": "pass" } + }, + "$ref": "extended", + "$defs": { + "extended": { + "$id": "extended", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "bar": { "$ref": "bar" } + } + }, + "bar": { + "$id": "bar", + "type": "object", + "properties": { + "baz": { "$dynamicRef": "extended#meta" } + } + } + } + }, + "tests": [ + { + "description": "The recursive part is valid against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "pass" } + } + }, + "valid": true + }, + { + "description": "The recursive part is not valid against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "fail" } + } + }, + "valid": false + } + ] + }, + { + "description": "A $dynamicRef that initially resolves to a schema without a matching $dynamicAnchor behaves like a normal $ref to $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/relative-dynamic-reference-without-bookend/root", + "$dynamicAnchor": "meta", + "type": "object", + "properties": { + "foo": { "const": "pass" } + }, + "$ref": "extended", + "$defs": { + "extended": { + "$id": "extended", + "$anchor": "meta", + "type": "object", + "properties": { + "bar": { "$ref": "bar" } + } + }, + "bar": { + "$id": "bar", + "type": "object", + "properties": { + "baz": { "$dynamicRef": "extended#meta" } + } + } + } + }, + "tests": [ + { + "description": "The recursive part doesn't need to validate against the root", + "data": { + "foo": "pass", + "bar": { + "baz": { "foo": "fail" } + } + }, + "valid": true + } + ] + }, + { + "description": "multiple dynamic paths to the $dynamicRef keyword", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-ref-with-multiple-paths/main", + "if": { + "properties": { + "kindOfList": { "const": "numbers" } + }, + "required": ["kindOfList"] + }, + "then": { "$ref": "numberList" }, + "else": { "$ref": "stringList" }, + + "$defs": { + "genericList": { + "$id": "genericList", + "properties": { + "list": { + "items": { "$dynamicRef": "#itemType" } + } + }, + "$defs": { + "defaultItemType": { + "$comment": "Only needed to satisfy bookending requirement", + "$dynamicAnchor": "itemType" + } + } + }, + "numberList": { + "$id": "numberList", + "$defs": { + "itemType": { + "$dynamicAnchor": "itemType", + "type": "number" + } + }, + "$ref": "genericList" + }, + "stringList": { + "$id": "stringList", + "$defs": { + "itemType": { + "$dynamicAnchor": "itemType", + "type": "string" + } + }, + "$ref": "genericList" + } + } + }, + "tests": [ + { + "description": "number list with number values", + "data": { + "kindOfList": "numbers", + "list": [1.1] + }, + "valid": true + }, + { + "description": "number list with string values", + "data": { + "kindOfList": "numbers", + "list": ["foo"] + }, + "valid": false + }, + { + "description": "string list with number values", + "data": { + "kindOfList": "strings", + "list": [1.1] + }, + "valid": false + }, + { + "description": "string list with string values", + "data": { + "kindOfList": "strings", + "list": ["foo"] + }, + "valid": true + } + ] + }, + { + "description": "after leaving a dynamic scope, it is not used by a $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-ref-leaving-dynamic-scope/main", + "if": { + "$id": "first_scope", + "$defs": { + "thingy": { + "$comment": "this is first_scope#thingy", + "$dynamicAnchor": "thingy", + "type": "number" + } + } + }, + "then": { + "$id": "second_scope", + "$ref": "start", + "$defs": { + "thingy": { + "$comment": "this is second_scope#thingy, the final destination of the $dynamicRef", + "$dynamicAnchor": "thingy", + "type": "null" + } + } + }, + "$defs": { + "start": { + "$comment": "this is the landing spot from $ref", + "$id": "start", + "$dynamicRef": "inner_scope#thingy" + }, + "thingy": { + "$comment": "this is the first stop for the $dynamicRef", + "$id": "inner_scope", + "$dynamicAnchor": "thingy", + "type": "string" + } + } + }, + "tests": [ + { + "description": "string matches /$defs/thingy, but the $dynamicRef does not stop here", + "data": "a string", + "valid": false + }, + { + "description": "first_scope is not in dynamic scope for the $dynamicRef", + "data": 42, + "valid": false + }, + { + "description": "/then/$defs/thingy is the final stop for the $dynamicRef", + "data": null, + "valid": true + } + ] + }, + { + "description": "strict-tree schema, guards against misspelled properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-tree.json", + "$dynamicAnchor": "node", + + "$ref": "tree.json", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "instance with misspelled field", + "data": { + "children": [{ + "daat": 1 + }] + }, + "valid": false + }, + { + "description": "instance with correct field", + "data": { + "children": [{ + "data": 1 + }] + }, + "valid": true + } + ] + }, + { + "description": "tests for implementation dynamic anchor and reference link", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-extendible.json", + "$ref": "extendible-dynamic-ref.json", + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref and $dynamicAnchor are independent of order - $defs first", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-defs-first.json", + "allOf": [ + { + "$ref": "extendible-dynamic-ref.json" + }, + { + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + } + ] + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref and $dynamicAnchor are independent of order - $ref first", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/strict-extendible-allof-ref-first.json", + "allOf": [ + { + "$defs": { + "elements": { + "$dynamicAnchor": "elements", + "properties": { + "a": true + }, + "required": ["a"], + "additionalProperties": false + } + } + }, + { + "$ref": "extendible-dynamic-ref.json" + } + ] + }, + "tests": [ + { + "description": "incorrect parent schema", + "data": { + "a": true + }, + "valid": false + }, + { + "description": "incorrect extended schema", + "data": { + "elements": [ + { "b": 1 } + ] + }, + "valid": false + }, + { + "description": "correct extended schema", + "data": { + "elements": [ + { "a": 1 } + ] + }, + "valid": true + } + ] + }, + { + "description": "$ref to $dynamicRef finds detached $dynamicAnchor", + "schema": { + "$ref": "http://localhost:1234/draft2020-12/detached-dynamicref.json#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$dynamicRef points to a boolean schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "true": true, + "false": false + }, + "properties": { + "true": { + "$dynamicRef": "#/$defs/true" + }, + "false": { + "$dynamicRef": "#/$defs/false" + } + } + }, + "tests": [ + { + "description": "follow $dynamicRef to a true schema", + "data": { "true": 1 }, + "valid": true + }, + { + "description": "follow $dynamicRef to a false schema", + "data": { "false": 1 }, + "valid": false + } + ] + }, + { + "description": "$dynamicRef skips over intermediate resources - direct reference", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://test.json-schema.org/dynamic-ref-skips-intermediate-resource/main", + "type": "object", + "properties": { + "bar-item": { + "$ref": "item" + } + }, + "$defs": { + "bar": { + "$id": "bar", + "type": "array", + "items": { + "$ref": "item" + }, + "$defs": { + "item": { + "$id": "item", + "type": "object", + "properties": { + "content": { + "$dynamicRef": "#content" + } + }, + "$defs": { + "defaultContent": { + "$dynamicAnchor": "content", + "type": "integer" + } + } + }, + "content": { + "$dynamicAnchor": "content", + "type": "string" + } + } + } + } + }, + "tests": [ + { + "description": "integer property passes", + "data": { "bar-item": { "content": 42 } }, + "valid": true + }, + { + "description": "string property fails", + "data": { "bar-item": { "content": "value" } }, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/enum.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/enum.json new file mode 100644 index 00000000..c8f35eac --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/enum.json @@ -0,0 +1,358 @@ +[ + { + "description": "simple enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [1, 2, 3] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": 1, + "valid": true + }, + { + "description": "something else is invalid", + "data": 4, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [6, "foo", [], true, {"foo": 12}] + }, + "tests": [ + { + "description": "one of the enum is valid", + "data": [], + "valid": true + }, + { + "description": "something else is invalid", + "data": null, + "valid": false + }, + { + "description": "objects are deep compared", + "data": {"foo": false}, + "valid": false + }, + { + "description": "valid object matches", + "data": {"foo": 12}, + "valid": true + }, + { + "description": "extra properties in object is invalid", + "data": {"foo": 12, "boo": 42}, + "valid": false + } + ] + }, + { + "description": "heterogeneous enum-with-null validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [6, null] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is valid", + "data": 6, + "valid": true + }, + { + "description": "something else is invalid", + "data": "test", + "valid": false + } + ] + }, + { + "description": "enums in properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type":"object", + "properties": { + "foo": {"enum":["foo"]}, + "bar": {"enum":["bar"]} + }, + "required": ["bar"] + }, + "tests": [ + { + "description": "both properties are valid", + "data": {"foo":"foo", "bar":"bar"}, + "valid": true + }, + { + "description": "wrong foo value", + "data": {"foo":"foot", "bar":"bar"}, + "valid": false + }, + { + "description": "wrong bar value", + "data": {"foo":"foo", "bar":"bart"}, + "valid": false + }, + { + "description": "missing optional property is valid", + "data": {"bar":"bar"}, + "valid": true + }, + { + "description": "missing required property is invalid", + "data": {"foo":"foo"}, + "valid": false + }, + { + "description": "missing all properties is invalid", + "data": {}, + "valid": false + } + ] + }, + { + "description": "enum with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": ["foo\nbar", "foo\rbar"] + }, + "tests": [ + { + "description": "member 1 is valid", + "data": "foo\nbar", + "valid": true + }, + { + "description": "member 2 is valid", + "data": "foo\rbar", + "valid": true + }, + { + "description": "another string is invalid", + "data": "abc", + "valid": false + } + ] + }, + { + "description": "enum with false does not match 0", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [false] + }, + "tests": [ + { + "description": "false is valid", + "data": false, + "valid": true + }, + { + "description": "integer zero is invalid", + "data": 0, + "valid": false + }, + { + "description": "float zero is invalid", + "data": 0.0, + "valid": false + } + ] + }, + { + "description": "enum with [false] does not match [0]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[false]] + }, + "tests": [ + { + "description": "[false] is valid", + "data": [false], + "valid": true + }, + { + "description": "[0] is invalid", + "data": [0], + "valid": false + }, + { + "description": "[0.0] is invalid", + "data": [0.0], + "valid": false + } + ] + }, + { + "description": "enum with true does not match 1", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [true] + }, + "tests": [ + { + "description": "true is valid", + "data": true, + "valid": true + }, + { + "description": "integer one is invalid", + "data": 1, + "valid": false + }, + { + "description": "float one is invalid", + "data": 1.0, + "valid": false + } + ] + }, + { + "description": "enum with [true] does not match [1]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[true]] + }, + "tests": [ + { + "description": "[true] is valid", + "data": [true], + "valid": true + }, + { + "description": "[1] is invalid", + "data": [1], + "valid": false + }, + { + "description": "[1.0] is invalid", + "data": [1.0], + "valid": false + } + ] + }, + { + "description": "enum with 0 does not match false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [0] + }, + "tests": [ + { + "description": "false is invalid", + "data": false, + "valid": false + }, + { + "description": "integer zero is valid", + "data": 0, + "valid": true + }, + { + "description": "float zero is valid", + "data": 0.0, + "valid": true + } + ] + }, + { + "description": "enum with [0] does not match [false]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[0]] + }, + "tests": [ + { + "description": "[false] is invalid", + "data": [false], + "valid": false + }, + { + "description": "[0] is valid", + "data": [0], + "valid": true + }, + { + "description": "[0.0] is valid", + "data": [0.0], + "valid": true + } + ] + }, + { + "description": "enum with 1 does not match true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [1] + }, + "tests": [ + { + "description": "true is invalid", + "data": true, + "valid": false + }, + { + "description": "integer one is valid", + "data": 1, + "valid": true + }, + { + "description": "float one is valid", + "data": 1.0, + "valid": true + } + ] + }, + { + "description": "enum with [1] does not match [true]", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [[1]] + }, + "tests": [ + { + "description": "[true] is invalid", + "data": [true], + "valid": false + }, + { + "description": "[1] is valid", + "data": [1], + "valid": true + }, + { + "description": "[1.0] is valid", + "data": [1.0], + "valid": true + } + ] + }, + { + "description": "nul characters in strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "enum": [ "hello\u0000there" ] + }, + "tests": [ + { + "description": "match string with nul", + "data": "hello\u0000there", + "valid": true + }, + { + "description": "do not match string lacking nul", + "data": "hellothere", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMaximum.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMaximum.json new file mode 100644 index 00000000..05db2335 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMaximum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMaximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMaximum": 3.0 + }, + "tests": [ + { + "description": "below the exclusiveMaximum is valid", + "data": 2.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 3.0, + "valid": false + }, + { + "description": "above the exclusiveMaximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMinimum.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMinimum.json new file mode 100644 index 00000000..00af9d7f --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/exclusiveMinimum.json @@ -0,0 +1,31 @@ +[ + { + "description": "exclusiveMinimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "exclusiveMinimum": 1.1 + }, + "tests": [ + { + "description": "above the exclusiveMinimum is valid", + "data": 1.2, + "valid": true + }, + { + "description": "boundary point is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "below the exclusiveMinimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/format.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/format.json new file mode 100644 index 00000000..01adcbda --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/format.json @@ -0,0 +1,838 @@ +[ + { + "description": "email format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid email string is only an annotation by default", + "data": "2962", + "valid": true + } + ] + }, + { + "description": "idn-email format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-email" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid idn-email string is only an annotation by default", + "data": "2962", + "valid": true + } + ] + }, + { + "description": "regex format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "regex" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid regex string is only an annotation by default", + "data": "^(abc]", + "valid": true + } + ] + }, + { + "description": "ipv4 format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv4" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid ipv4 string is only an annotation by default", + "data": "127.0.0.0.1", + "valid": true + } + ] + }, + { + "description": "ipv6 format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "ipv6" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid ipv6 string is only an annotation by default", + "data": "12345::", + "valid": true + } + ] + }, + { + "description": "idn-hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "idn-hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid idn-hostname string is only an annotation by default", + "data": "〮실례.테스트", + "valid": true + } + ] + }, + { + "description": "hostname format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "hostname" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid hostname string is only an annotation by default", + "data": "-a-host-name-that-starts-with--", + "valid": true + } + ] + }, + { + "description": "date format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid date string is only an annotation by default", + "data": "06/19/1963", + "valid": true + } + ] + }, + { + "description": "date-time format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "date-time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid date-time string is only an annotation by default", + "data": "1990-02-31T15:59:60.123-08:00", + "valid": true + } + ] + }, + { + "description": "time format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "time" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid time string is only an annotation by default", + "data": "08:30:06 PST", + "valid": true + } + ] + }, + { + "description": "json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid json-pointer string is only an annotation by default", + "data": "/foo/bar~", + "valid": true + } + ] + }, + { + "description": "relative-json-pointer format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "relative-json-pointer" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid relative-json-pointer string is only an annotation by default", + "data": "/foo/bar", + "valid": true + } + ] + }, + { + "description": "iri format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid iri string is only an annotation by default", + "data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "valid": true + } + ] + }, + { + "description": "iri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "iri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid iri-reference string is only an annotation by default", + "data": "\\\\WINDOWS\\filëßåré", + "valid": true + } + ] + }, + { + "description": "uri format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri string is only an annotation by default", + "data": "//foo.bar/?baz=qux#quux", + "valid": true + } + ] + }, + { + "description": "uri-reference format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-reference" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri-reference string is only an annotation by default", + "data": "\\\\WINDOWS\\fileshare", + "valid": true + } + ] + }, + { + "description": "uri-template format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uri-template" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uri-template string is only an annotation by default", + "data": "http://example.com/dictionary/{term:1}/{term", + "valid": true + } + ] + }, + { + "description": "uuid format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "uuid" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid uuid string is only an annotation by default", + "data": "2eb8aa08-aa98-11ea-b4aa-73b441d1638", + "valid": true + } + ] + }, + { + "description": "duration format", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "format": "duration" + }, + "tests": [ + { + "description": "all string formats ignore integers", + "data": 12, + "valid": true + }, + { + "description": "all string formats ignore floats", + "data": 13.7, + "valid": true + }, + { + "description": "all string formats ignore objects", + "data": {}, + "valid": true + }, + { + "description": "all string formats ignore arrays", + "data": [], + "valid": true + }, + { + "description": "all string formats ignore booleans", + "data": false, + "valid": true + }, + { + "description": "all string formats ignore nulls", + "data": null, + "valid": true + }, + { + "description": "invalid duration string is only an annotation by default", + "data": "PT1D", + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/if-then-else.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/if-then-else.json new file mode 100644 index 00000000..1c35d7e6 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/if-then-else.json @@ -0,0 +1,268 @@ +[ + { + "description": "ignore if without then or else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone if", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone if", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore then without if", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "then": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone then", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone then", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "ignore else without if", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "else": { + "const": 0 + } + }, + "tests": [ + { + "description": "valid when valid against lone else", + "data": 0, + "valid": true + }, + { + "description": "valid when invalid against lone else", + "data": "hello", + "valid": true + } + ] + }, + { + "description": "if and then without else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid when if test fails", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if and else without then", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid when if test passes", + "data": -1, + "valid": true + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "validate against correct branch, then vs else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "exclusiveMaximum": 0 + }, + "then": { + "minimum": -10 + }, + "else": { + "multipleOf": 2 + } + }, + "tests": [ + { + "description": "valid through then", + "data": -1, + "valid": true + }, + { + "description": "invalid through then", + "data": -100, + "valid": false + }, + { + "description": "valid through else", + "data": 4, + "valid": true + }, + { + "description": "invalid through else", + "data": 3, + "valid": false + } + ] + }, + { + "description": "non-interference across combined schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "if": { + "exclusiveMaximum": 0 + } + }, + { + "then": { + "minimum": -10 + } + }, + { + "else": { + "multipleOf": 2 + } + } + ] + }, + "tests": [ + { + "description": "valid, but would have been invalid through then", + "data": -100, + "valid": true + }, + { + "description": "valid, but would have been invalid through else", + "data": 3, + "valid": true + } + ] + }, + { + "description": "if with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": true, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema true in if always chooses the then path (valid)", + "data": "then", + "valid": true + }, + { + "description": "boolean schema true in if always chooses the then path (invalid)", + "data": "else", + "valid": false + } + ] + }, + { + "description": "if with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": false, + "then": { "const": "then" }, + "else": { "const": "else" } + }, + "tests": [ + { + "description": "boolean schema false in if always chooses the else path (invalid)", + "data": "then", + "valid": false + }, + { + "description": "boolean schema false in if always chooses the else path (valid)", + "data": "else", + "valid": true + } + ] + }, + { + "description": "if appears at the end when serialized (keyword processing sequence)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "then": { "const": "yes" }, + "else": { "const": "other" }, + "if": { "maxLength": 4 } + }, + "tests": [ + { + "description": "yes redirects to then and passes", + "data": "yes", + "valid": true + }, + { + "description": "other redirects to else and passes", + "data": "other", + "valid": true + }, + { + "description": "no redirects to then and fails", + "data": "no", + "valid": false + }, + { + "description": "invalid redirects to else and fails", + "data": "invalid", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/infinite-loop-detection.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/infinite-loop-detection.json new file mode 100644 index 00000000..46f157a3 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/infinite-loop-detection.json @@ -0,0 +1,37 @@ +[ + { + "description": "evaluating the same schema location against the same data location twice is not a sign of an infinite loop", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "int": { "type": "integer" } + }, + "allOf": [ + { + "properties": { + "foo": { + "$ref": "#/$defs/int" + } + } + }, + { + "additionalProperties": { + "$ref": "#/$defs/int" + } + } + ] + }, + "tests": [ + { + "description": "passing case", + "data": { "foo": 1 }, + "valid": true + }, + { + "description": "failing case", + "data": { "foo": "a string" }, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/items.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/items.json new file mode 100644 index 00000000..6a3e1cf2 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/items.json @@ -0,0 +1,304 @@ +[ + { + "description": "a schema given for items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": {"type": "integer"} + }, + "tests": [ + { + "description": "valid items", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "wrong type of items", + "data": [1, "x"], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": {"foo" : "bar"}, + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "length": 1 + }, + "valid": true + } + ] + }, + { + "description": "items with boolean schema (true)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": true + }, + "tests": [ + { + "description": "any array is valid", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items with boolean schema (false)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": false + }, + "tests": [ + { + "description": "any non-empty array is invalid", + "data": [ 1, "foo", true ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "items and subitems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "item": { + "type": "array", + "items": false, + "prefixItems": [ + { "$ref": "#/$defs/sub-item" }, + { "$ref": "#/$defs/sub-item" } + ] + }, + "sub-item": { + "type": "object", + "required": ["foo"] + } + }, + "type": "array", + "items": false, + "prefixItems": [ + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" }, + { "$ref": "#/$defs/item" } + ] + }, + "tests": [ + { + "description": "valid items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": true + }, + { + "description": "too many items", + "data": [ + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "too many sub-items", + "data": [ + [ {"foo": null}, {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong item", + "data": [ + {"foo": null}, + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "wrong sub-item", + "data": [ + [ {}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ], + [ {"foo": null}, {"foo": null} ] + ], + "valid": false + }, + { + "description": "fewer items is valid", + "data": [ + [ {"foo": null} ], + [ {"foo": null} ] + ], + "valid": true + } + ] + }, + { + "description": "nested items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "number" + } + } + } + } + }, + "tests": [ + { + "description": "valid nested array", + "data": [[[[1]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": true + }, + { + "description": "nested array with invalid type", + "data": [[[["1"]], [[2],[3]]], [[[4], [5], [6]]]], + "valid": false + }, + { + "description": "not deep enough", + "data": [[[1], [2],[3]], [[4], [5], [6]]], + "valid": false + } + ] + }, + { + "description": "prefixItems with no additional items allowed", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{}, {}, {}], + "items": false + }, + "tests": [ + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "fewer number of items present (1)", + "data": [ 1 ], + "valid": true + }, + { + "description": "fewer number of items present (2)", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "equal number of items present", + "data": [ 1, 2, 3 ], + "valid": true + }, + { + "description": "additional items are not permitted", + "data": [ 1, 2, 3, 4 ], + "valid": false + } + ] + }, + { + "description": "items does not look in applicators, valid case", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { "prefixItems": [ { "minimum": 3 } ] } + ], + "items": { "minimum": 5 } + }, + "tests": [ + { + "description": "prefixItems in allOf does not constrain items, invalid case", + "data": [ 3, 5 ], + "valid": false + }, + { + "description": "prefixItems in allOf does not constrain items, valid case", + "data": [ 5, 5 ], + "valid": true + } + ] + }, + { + "description": "prefixItems validation adjusts the starting index for items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ { "type": "string" } ], + "items": { "type": "integer" } + }, + "tests": [ + { + "description": "valid items", + "data": [ "x", 2, 3 ], + "valid": true + }, + { + "description": "wrong type of second item", + "data": [ "x", "y" ], + "valid": false + } + ] + }, + { + "description": "items with heterogeneous array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{}], + "items": false + }, + "tests": [ + { + "description": "heterogeneous invalid instance", + "data": [ "foo", "bar", 37 ], + "valid": false + }, + { + "description": "valid instance", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "items with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxContains.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxContains.json new file mode 100644 index 00000000..8cd3ca74 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxContains.json @@ -0,0 +1,102 @@ +[ + { + "description": "maxContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "two items still valid against lone maxContains", + "data": [ 1, 2 ], + "valid": true + } + ] + }, + { + "description": "maxContains with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, valid maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "all elements match, invalid maxContains", + "data": [ 1, 1 ], + "valid": false + }, + { + "description": "some elements match, valid maxContains", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "some elements match, invalid maxContains", + "data": [ 1, 2, 1 ], + "valid": false + } + ] + }, + { + "description": "maxContains with contains, value with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 1.0 + }, + "tests": [ + { + "description": "one element matches, valid maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many elements match, invalid maxContains", + "data": [ 1, 1 ], + "valid": false + } + ] + }, + { + "description": "minContains < maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 1, + "maxContains": 3 + }, + "tests": [ + { + "description": "actual < minContains < maxContains", + "data": [ ], + "valid": false + }, + { + "description": "minContains < actual < maxContains", + "data": [ 1, 1 ], + "valid": true + }, + { + "description": "minContains < maxContains < actual", + "data": [ 1, 1, 1, 1 ], + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxItems.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxItems.json new file mode 100644 index 00000000..f6a6b7c9 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "maxItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxItems": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "foobar", + "valid": true + } + ] + }, + { + "description": "maxItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxItems": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": [1], + "valid": true + }, + { + "description": "too long is invalid", + "data": [1, 2, 3], + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxLength.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxLength.json new file mode 100644 index 00000000..7462726d --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "maxLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxLength": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 100, + "valid": true + }, + { + "description": "two graphemes is long enough", + "data": "\uD83D\uDCA9\uD83D\uDCA9", + "valid": true + } + ] + }, + { + "description": "maxLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxLength": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": "f", + "valid": true + }, + { + "description": "too long is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxProperties.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxProperties.json new file mode 100644 index 00000000..73ae7316 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maxProperties.json @@ -0,0 +1,79 @@ +[ + { + "description": "maxProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 2 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [1, 2, 3], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "maxProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 2.0 + }, + "tests": [ + { + "description": "shorter is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too long is invalid", + "data": {"foo": 1, "bar": 2, "baz": 3}, + "valid": false + } + ] + }, + { + "description": "maxProperties = 0 means the object is empty", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maxProperties": 0 + }, + "tests": [ + { + "description": "no properties is valid", + "data": {}, + "valid": true + }, + { + "description": "one property is invalid", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maximum.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maximum.json new file mode 100644 index 00000000..b99a541e --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/maximum.json @@ -0,0 +1,60 @@ +[ + { + "description": "maximum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maximum": 3.0 + }, + "tests": [ + { + "description": "below the maximum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 3.0, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 3.5, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "maximum validation with unsigned integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "maximum": 300 + }, + "tests": [ + { + "description": "below the maximum is invalid", + "data": 299.97, + "valid": true + }, + { + "description": "boundary point integer is valid", + "data": 300, + "valid": true + }, + { + "description": "boundary point float is valid", + "data": 300.00, + "valid": true + }, + { + "description": "above the maximum is invalid", + "data": 300.5, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minContains.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minContains.json new file mode 100644 index 00000000..ee72d7d6 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minContains.json @@ -0,0 +1,224 @@ +[ + { + "description": "minContains without contains is ignored", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minContains": 1 + }, + "tests": [ + { + "description": "one item valid against lone minContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "zero items still valid against lone minContains", + "data": [], + "valid": true + } + ] + }, + { + "description": "minContains=1 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "no elements match", + "data": [ 2 ], + "valid": false + }, + { + "description": "single element matches, valid minContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [ 1, 2 ], + "valid": true + }, + { + "description": "all elements match, valid minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "some elements match, invalid minContains", + "data": [ 1, 2 ], + "valid": false + }, + { + "description": "all elements match, valid minContains (exactly as needed)", + "data": [ 1, 1 ], + "valid": true + }, + { + "description": "all elements match, valid minContains (more than needed)", + "data": [ 1, 1, 1 ], + "valid": true + }, + { + "description": "some elements match, valid minContains", + "data": [ 1, 2, 1 ], + "valid": true + } + ] + }, + { + "description": "minContains=2 with contains with a decimal value", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 2.0 + }, + "tests": [ + { + "description": "one element matches, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "both elements match, valid minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "maxContains = minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 2, + "minContains": 2 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "all elements match, invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "all elements match, invalid maxContains", + "data": [ 1, 1, 1 ], + "valid": false + }, + { + "description": "all elements match, valid maxContains and minContains", + "data": [ 1, 1 ], + "valid": true + } + ] + }, + { + "description": "maxContains < minContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "maxContains": 1, + "minContains": 3 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": false + }, + { + "description": "invalid minContains", + "data": [ 1 ], + "valid": false + }, + { + "description": "invalid maxContains", + "data": [ 1, 1, 1 ], + "valid": false + }, + { + "description": "invalid maxContains and minContains", + "data": [ 1, 1 ], + "valid": false + } + ] + }, + { + "description": "minContains = 0", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 0 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": true + }, + { + "description": "minContains = 0 makes contains always pass", + "data": [ 2 ], + "valid": true + } + ] + }, + { + "description": "minContains = 0 with maxContains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "contains": {"const": 1}, + "minContains": 0, + "maxContains": 1 + }, + "tests": [ + { + "description": "empty data", + "data": [ ], + "valid": true + }, + { + "description": "not more than maxContains", + "data": [ 1 ], + "valid": true + }, + { + "description": "too many", + "data": [ 1, 1 ], + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minItems.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minItems.json new file mode 100644 index 00000000..9d6a8b6d --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minItems.json @@ -0,0 +1,50 @@ +[ + { + "description": "minItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minItems": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "exact length is valid", + "data": [1], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + }, + { + "description": "ignores non-arrays", + "data": "", + "valid": true + } + ] + }, + { + "description": "minItems validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minItems": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "too short is invalid", + "data": [], + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minLength.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minLength.json new file mode 100644 index 00000000..5076c5a9 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minLength.json @@ -0,0 +1,55 @@ +[ + { + "description": "minLength validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minLength": 2 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "exact length is valid", + "data": "fo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + }, + { + "description": "ignores non-strings", + "data": 1, + "valid": true + }, + { + "description": "one grapheme is not long enough", + "data": "\uD83D\uDCA9", + "valid": false + } + ] + }, + { + "description": "minLength validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minLength": 2.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": "foo", + "valid": true + }, + { + "description": "too short is invalid", + "data": "f", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minProperties.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minProperties.json new file mode 100644 index 00000000..a753ad35 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minProperties.json @@ -0,0 +1,60 @@ +[ + { + "description": "minProperties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minProperties": 1 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "exact length is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "minProperties validation with a decimal", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minProperties": 1.0 + }, + "tests": [ + { + "description": "longer is valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "too short is invalid", + "data": {}, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minimum.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minimum.json new file mode 100644 index 00000000..dc440527 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/minimum.json @@ -0,0 +1,75 @@ +[ + { + "description": "minimum validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minimum": 1.1 + }, + "tests": [ + { + "description": "above the minimum is valid", + "data": 2.6, + "valid": true + }, + { + "description": "boundary point is valid", + "data": 1.1, + "valid": true + }, + { + "description": "below the minimum is invalid", + "data": 0.6, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + }, + { + "description": "minimum validation with signed integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "minimum": -2 + }, + "tests": [ + { + "description": "negative above the minimum is valid", + "data": -1, + "valid": true + }, + { + "description": "positive above the minimum is valid", + "data": 0, + "valid": true + }, + { + "description": "boundary point is valid", + "data": -2, + "valid": true + }, + { + "description": "boundary point with float is valid", + "data": -2.0, + "valid": true + }, + { + "description": "float below the minimum is invalid", + "data": -2.0001, + "valid": false + }, + { + "description": "int below the minimum is invalid", + "data": -3, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "x", + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/multipleOf.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/multipleOf.json new file mode 100644 index 00000000..92d6979b --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/multipleOf.json @@ -0,0 +1,97 @@ +[ + { + "description": "by int", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 2 + }, + "tests": [ + { + "description": "int by int", + "data": 10, + "valid": true + }, + { + "description": "int by int fail", + "data": 7, + "valid": false + }, + { + "description": "ignores non-numbers", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "by number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 1.5 + }, + "tests": [ + { + "description": "zero is multiple of anything", + "data": 0, + "valid": true + }, + { + "description": "4.5 is multiple of 1.5", + "data": 4.5, + "valid": true + }, + { + "description": "35 is not multiple of 1.5", + "data": 35, + "valid": false + } + ] + }, + { + "description": "by small number", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "multipleOf": 0.0001 + }, + "tests": [ + { + "description": "0.0075 is multiple of 0.0001", + "data": 0.0075, + "valid": true + }, + { + "description": "0.00751 is not multiple of 0.0001", + "data": 0.00751, + "valid": false + } + ] + }, + { + "description": "float division = inf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", "multipleOf": 0.123456789 + }, + "tests": [ + { + "description": "always invalid, but naive implementations may raise an overflow error", + "data": 1e308, + "valid": false + } + ] + }, + { + "description": "small multiple of large integer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer", "multipleOf": 1e-8 + }, + "tests": [ + { + "description": "any integer is a multiple of 1e-8", + "data": 12391239123, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/not.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/not.json new file mode 100644 index 00000000..d0f2b6e8 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/not.json @@ -0,0 +1,301 @@ +[ + { + "description": "not", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": {"type": "integer"} + }, + "tests": [ + { + "description": "allowed", + "data": "foo", + "valid": true + }, + { + "description": "disallowed", + "data": 1, + "valid": false + } + ] + }, + { + "description": "not multiple types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": {"type": ["integer", "boolean"]} + }, + "tests": [ + { + "description": "valid", + "data": "foo", + "valid": true + }, + { + "description": "mismatch", + "data": 1, + "valid": false + }, + { + "description": "other mismatch", + "data": true, + "valid": false + } + ] + }, + { + "description": "not more complex schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "type": "object", + "properties": { + "foo": { + "type": "string" + } + } + } + }, + "tests": [ + { + "description": "match", + "data": 1, + "valid": true + }, + { + "description": "other match", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "mismatch", + "data": {"foo": "bar"}, + "valid": false + } + ] + }, + { + "description": "forbidden property", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "not": {} + } + } + }, + "tests": [ + { + "description": "property present", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "property absent", + "data": {"bar": 1, "baz": 2}, + "valid": true + } + ] + }, + { + "description": "forbid everything with empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": {} + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "forbid everything with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": true + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "boolean true is invalid", + "data": true, + "valid": false + }, + { + "description": "boolean false is invalid", + "data": false, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + }, + { + "description": "object is invalid", + "data": {"foo": "bar"}, + "valid": false + }, + { + "description": "empty object is invalid", + "data": {}, + "valid": false + }, + { + "description": "array is invalid", + "data": ["foo"], + "valid": false + }, + { + "description": "empty array is invalid", + "data": [], + "valid": false + } + ] + }, + { + "description": "allow everything with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": false + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "boolean true is valid", + "data": true, + "valid": true + }, + { + "description": "boolean false is valid", + "data": false, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + }, + { + "description": "array is valid", + "data": ["foo"], + "valid": true + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "double negation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { "not": {} } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "collect annotations inside a 'not', even if collection is disabled", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "not": { + "$comment": "this subschema must still produce annotations internally, even though the 'not' will ultimately discard them", + "anyOf": [ + true, + { "properties": { "foo": true } } + ], + "unevaluatedProperties": false + } + }, + "tests": [ + { + "description": "unevaluated property", + "data": { "bar": 1 }, + "valid": true + }, + { + "description": "annotations are still collected inside a 'not'", + "data": { "foo": 1 }, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/oneOf.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/oneOf.json new file mode 100644 index 00000000..7a7c7ffe --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/oneOf.json @@ -0,0 +1,293 @@ +[ + { + "description": "oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "type": "integer" + }, + { + "minimum": 2 + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": 1, + "valid": true + }, + { + "description": "second oneOf valid", + "data": 2.5, + "valid": true + }, + { + "description": "both oneOf valid", + "data": 3, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": 1.5, + "valid": false + } + ] + }, + { + "description": "oneOf with base schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string", + "oneOf" : [ + { + "minLength": 2 + }, + { + "maxLength": 4 + } + ] + }, + "tests": [ + { + "description": "mismatch base schema", + "data": 3, + "valid": false + }, + { + "description": "one oneOf valid", + "data": "foobar", + "valid": true + }, + { + "description": "both oneOf valid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [true, true, true] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, one true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [true, false, false] + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "oneOf with boolean schemas, more than one true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [true, true, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf with boolean schemas, all false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [false, false, false] + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "oneOf complex types", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "properties": { + "bar": {"type": "integer"} + }, + "required": ["bar"] + }, + { + "properties": { + "foo": {"type": "string"} + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid (complex)", + "data": {"bar": 2}, + "valid": true + }, + { + "description": "second oneOf valid (complex)", + "data": {"foo": "baz"}, + "valid": true + }, + { + "description": "both oneOf valid (complex)", + "data": {"foo": "baz", "bar": 2}, + "valid": false + }, + { + "description": "neither oneOf valid (complex)", + "data": {"foo": 2, "bar": "quux"}, + "valid": false + } + ] + }, + { + "description": "oneOf with empty schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { "type": "number" }, + {} + ] + }, + "tests": [ + { + "description": "one valid - valid", + "data": "foo", + "valid": true + }, + { + "description": "both valid - invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "oneOf with required", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "oneOf": [ + { "required": ["foo", "bar"] }, + { "required": ["foo", "baz"] } + ] + }, + "tests": [ + { + "description": "both invalid - invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "first valid - valid", + "data": {"foo": 1, "bar": 2}, + "valid": true + }, + { + "description": "second valid - valid", + "data": {"foo": 1, "baz": 3}, + "valid": true + }, + { + "description": "both valid - invalid", + "data": {"foo": 1, "bar": 2, "baz" : 3}, + "valid": false + } + ] + }, + { + "description": "oneOf with missing optional property", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "properties": { + "bar": true, + "baz": true + }, + "required": ["bar"] + }, + { + "properties": { + "foo": true + }, + "required": ["foo"] + } + ] + }, + "tests": [ + { + "description": "first oneOf valid", + "data": {"bar": 8}, + "valid": true + }, + { + "description": "second oneOf valid", + "data": {"foo": "foo"}, + "valid": true + }, + { + "description": "both oneOf valid", + "data": {"foo": "foo", "bar": 8}, + "valid": false + }, + { + "description": "neither oneOf valid", + "data": {"baz": "quux"}, + "valid": false + } + ] + }, + { + "description": "nested oneOf, to check validation semantics", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "oneOf": [ + { + "type": "null" + } + ] + } + ] + }, + "tests": [ + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "anything non-null is invalid", + "data": 123, + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/pattern.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/pattern.json new file mode 100644 index 00000000..af0b8d89 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/pattern.json @@ -0,0 +1,65 @@ +[ + { + "description": "pattern validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "^a*$" + }, + "tests": [ + { + "description": "a matching pattern is valid", + "data": "aaa", + "valid": true + }, + { + "description": "a non-matching pattern is invalid", + "data": "abc", + "valid": false + }, + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "pattern is not anchored", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "pattern": "a+" + }, + "tests": [ + { + "description": "matches a substring", + "data": "xxaayy", + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/patternProperties.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/patternProperties.json new file mode 100644 index 00000000..81829c71 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/patternProperties.json @@ -0,0 +1,176 @@ +[ + { + "description": + "patternProperties validates properties matching a regex", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "f.*o": {"type": "integer"} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "multiple valid matches is valid", + "data": {"foo": 1, "foooooo" : 2}, + "valid": true + }, + { + "description": "a single invalid match is invalid", + "data": {"foo": "bar", "fooooo": 2}, + "valid": false + }, + { + "description": "multiple invalid matches is invalid", + "data": {"foo": "bar", "foooooo" : "baz"}, + "valid": false + }, + { + "description": "ignores arrays", + "data": ["foo"], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "multiple simultaneous patternProperties are validated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "a*": {"type": "integer"}, + "aaa*": {"maximum": 20} + } + }, + "tests": [ + { + "description": "a single valid match is valid", + "data": {"a": 21}, + "valid": true + }, + { + "description": "a simultaneous match is valid", + "data": {"aaaa": 18}, + "valid": true + }, + { + "description": "multiple matches is valid", + "data": {"a": 21, "aaaa": 18}, + "valid": true + }, + { + "description": "an invalid due to one is invalid", + "data": {"a": "bar"}, + "valid": false + }, + { + "description": "an invalid due to the other is invalid", + "data": {"aaaa": 31}, + "valid": false + }, + { + "description": "an invalid due to both is invalid", + "data": {"aaa": "foo", "aaaa": 31}, + "valid": false + } + ] + }, + { + "description": "regexes are not anchored by default and are case sensitive", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "[0-9]{2,}": { "type": "boolean" }, + "X_": { "type": "string" } + } + }, + "tests": [ + { + "description": "non recognized members are ignored", + "data": { "answer 1": "42" }, + "valid": true + }, + { + "description": "recognized members are accounted for", + "data": { "a31b": null }, + "valid": false + }, + { + "description": "regexes are case sensitive", + "data": { "a_x_3": 3 }, + "valid": true + }, + { + "description": "regexes are case sensitive, 2", + "data": { "a_X_3": 3 }, + "valid": false + } + ] + }, + { + "description": "patternProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "f.*": true, + "b.*": false + } + }, + "tests": [ + { + "description": "object with property matching schema true is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "object with property matching schema false is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "object with both properties is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + }, + { + "description": "object with a property matching both true and false is invalid", + "data": {"foobar":1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "patternProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "patternProperties": { + "^.*bar$": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foobar": null}, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/prefixItems.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/prefixItems.json new file mode 100644 index 00000000..0adfc069 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/prefixItems.json @@ -0,0 +1,104 @@ +[ + { + "description": "a schema given for prefixItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + {"type": "integer"}, + {"type": "string"} + ] + }, + "tests": [ + { + "description": "correct types", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "wrong types", + "data": [ "foo", 1 ], + "valid": false + }, + { + "description": "incomplete array of items", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with additional items", + "data": [ 1, "foo", true ], + "valid": true + }, + { + "description": "empty array", + "data": [ ], + "valid": true + }, + { + "description": "JavaScript pseudo-array is valid", + "data": { + "0": "invalid", + "1": "valid", + "length": 2 + }, + "valid": true + } + ] + }, + { + "description": "prefixItems with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [true, false] + }, + "tests": [ + { + "description": "array with one item is valid", + "data": [ 1 ], + "valid": true + }, + { + "description": "array with two items is invalid", + "data": [ 1, "foo" ], + "valid": false + }, + { + "description": "empty array is valid", + "data": [], + "valid": true + } + ] + }, + { + "description": "additional items are allowed by default", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "integer"}] + }, + "tests": [ + { + "description": "only the first item is validated", + "data": [1, "foo", false], + "valid": true + } + ] + }, + { + "description": "prefixItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { + "type": "null" + } + ] + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/properties.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/properties.json new file mode 100644 index 00000000..eb66fa8b --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/properties.json @@ -0,0 +1,242 @@ +[ + { + "description": "object properties validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "both properties present and valid is valid", + "data": {"foo": 1, "bar": "baz"}, + "valid": true + }, + { + "description": "one property invalid is invalid", + "data": {"foo": 1, "bar": {}}, + "valid": false + }, + { + "description": "both properties invalid is invalid", + "data": {"foo": [], "bar": {}}, + "valid": false + }, + { + "description": "doesn't invalidate other properties", + "data": {"quux": []}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": + "properties, patternProperties, additionalProperties interaction", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "array", "maxItems": 3}, + "bar": {"type": "array"} + }, + "patternProperties": {"f.o": {"minItems": 2}}, + "additionalProperties": {"type": "integer"} + }, + "tests": [ + { + "description": "property validates property", + "data": {"foo": [1, 2]}, + "valid": true + }, + { + "description": "property invalidates property", + "data": {"foo": [1, 2, 3, 4]}, + "valid": false + }, + { + "description": "patternProperty invalidates property", + "data": {"foo": []}, + "valid": false + }, + { + "description": "patternProperty validates nonproperty", + "data": {"fxo": [1, 2]}, + "valid": true + }, + { + "description": "patternProperty invalidates nonproperty", + "data": {"fxo": []}, + "valid": false + }, + { + "description": "additionalProperty ignores property", + "data": {"bar": []}, + "valid": true + }, + { + "description": "additionalProperty validates others", + "data": {"quux": 3}, + "valid": true + }, + { + "description": "additionalProperty invalidates others", + "data": {"quux": "foo"}, + "valid": false + } + ] + }, + { + "description": "properties with boolean schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": true, + "bar": false + } + }, + "tests": [ + { + "description": "no property present is valid", + "data": {}, + "valid": true + }, + { + "description": "only 'true' property present is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "only 'false' property present is invalid", + "data": {"bar": 2}, + "valid": false + }, + { + "description": "both properties present is invalid", + "data": {"foo": 1, "bar": 2}, + "valid": false + } + ] + }, + { + "description": "properties with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo\nbar": {"type": "number"}, + "foo\"bar": {"type": "number"}, + "foo\\bar": {"type": "number"}, + "foo\rbar": {"type": "number"}, + "foo\tbar": {"type": "number"}, + "foo\fbar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with all numbers is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1", + "foo\\bar": "1", + "foo\rbar": "1", + "foo\tbar": "1", + "foo\fbar": "1" + }, + "valid": false + } + ] + }, + { + "description": "properties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "null"} + } + }, + "tests": [ + { + "description": "allows null values", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "__proto__": {"type": "number"}, + "toString": { + "properties": { "length": { "type": "string" } } + }, + "constructor": {"type": "number"} + } + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": true + }, + { + "description": "__proto__ not valid", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString not valid", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor not valid", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present and valid", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/propertyNames.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/propertyNames.json new file mode 100644 index 00000000..7ecfb7ec --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/propertyNames.json @@ -0,0 +1,85 @@ +[ + { + "description": "propertyNames validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": {"maxLength": 3} + }, + "tests": [ + { + "description": "all property names valid", + "data": { + "f": {}, + "foo": {} + }, + "valid": true + }, + { + "description": "some property names invalid", + "data": { + "foo": {}, + "foobar": {} + }, + "valid": false + }, + { + "description": "object without properties is valid", + "data": {}, + "valid": true + }, + { + "description": "ignores arrays", + "data": [1, 2, 3, 4], + "valid": true + }, + { + "description": "ignores strings", + "data": "foobar", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": true + }, + "tests": [ + { + "description": "object with any properties is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + }, + { + "description": "propertyNames with boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": false + }, + "tests": [ + { + "description": "object with any properties is invalid", + "data": {"foo": 1}, + "valid": false + }, + { + "description": "empty object is valid", + "data": {}, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/ref.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/ref.json new file mode 100644 index 00000000..a1d3efaf --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/ref.json @@ -0,0 +1,1052 @@ +[ + { + "description": "root pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"$ref": "#"} + }, + "additionalProperties": false + }, + "tests": [ + { + "description": "match", + "data": {"foo": false}, + "valid": true + }, + { + "description": "recursive match", + "data": {"foo": {"foo": false}}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": false}, + "valid": false + }, + { + "description": "recursive mismatch", + "data": {"foo": {"bar": false}}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {"type": "integer"}, + "bar": {"$ref": "#/properties/foo"} + } + }, + "tests": [ + { + "description": "match", + "data": {"bar": 3}, + "valid": true + }, + { + "description": "mismatch", + "data": {"bar": true}, + "valid": false + } + ] + }, + { + "description": "relative pointer ref to array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + {"type": "integer"}, + {"$ref": "#/prefixItems/0"} + ] + }, + "tests": [ + { + "description": "match array", + "data": [1, 2], + "valid": true + }, + { + "description": "mismatch array", + "data": [1, "foo"], + "valid": false + } + ] + }, + { + "description": "escaped pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "tilde~field": {"type": "integer"}, + "slash/field": {"type": "integer"}, + "percent%field": {"type": "integer"} + }, + "properties": { + "tilde": {"$ref": "#/$defs/tilde~0field"}, + "slash": {"$ref": "#/$defs/slash~1field"}, + "percent": {"$ref": "#/$defs/percent%25field"} + } + }, + "tests": [ + { + "description": "slash invalid", + "data": {"slash": "aoeu"}, + "valid": false + }, + { + "description": "tilde invalid", + "data": {"tilde": "aoeu"}, + "valid": false + }, + { + "description": "percent invalid", + "data": {"percent": "aoeu"}, + "valid": false + }, + { + "description": "slash valid", + "data": {"slash": 123}, + "valid": true + }, + { + "description": "tilde valid", + "data": {"tilde": 123}, + "valid": true + }, + { + "description": "percent valid", + "data": {"percent": 123}, + "valid": true + } + ] + }, + { + "description": "nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "a": {"type": "integer"}, + "b": {"$ref": "#/$defs/a"}, + "c": {"$ref": "#/$defs/b"} + }, + "$ref": "#/$defs/c" + }, + "tests": [ + { + "description": "nested ref valid", + "data": 5, + "valid": true + }, + { + "description": "nested ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref applies alongside sibling keywords", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "reffed": { + "type": "array" + } + }, + "properties": { + "foo": { + "$ref": "#/$defs/reffed", + "maxItems": 2 + } + } + }, + "tests": [ + { + "description": "ref valid, maxItems valid", + "data": { "foo": [] }, + "valid": true + }, + { + "description": "ref valid, maxItems invalid", + "data": { "foo": [1, 2, 3] }, + "valid": false + }, + { + "description": "ref invalid", + "data": { "foo": "string" }, + "valid": false + } + ] + }, + { + "description": "remote ref, containing refs itself", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "https://json-schema.org/draft/2020-12/schema" + }, + "tests": [ + { + "description": "remote ref valid", + "data": {"minLength": 1}, + "valid": true + }, + { + "description": "remote ref invalid", + "data": {"minLength": -1}, + "valid": false + } + ] + }, + { + "description": "property named $ref that is not a reference", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "$ref": {"type": "string"} + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "property named $ref, containing an actual $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "$ref": {"$ref": "#/$defs/is-string"} + }, + "$defs": { + "is-string": { + "type": "string" + } + } + }, + "tests": [ + { + "description": "property named $ref valid", + "data": {"$ref": "a"}, + "valid": true + }, + { + "description": "property named $ref invalid", + "data": {"$ref": 2}, + "valid": false + } + ] + }, + { + "description": "$ref to boolean schema true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": true + } + }, + "tests": [ + { + "description": "any value is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to boolean schema false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bool", + "$defs": { + "bool": false + } + }, + "tests": [ + { + "description": "any value is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "Recursive references between schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/tree", + "description": "tree of nodes", + "type": "object", + "properties": { + "meta": {"type": "string"}, + "nodes": { + "type": "array", + "items": {"$ref": "node"} + } + }, + "required": ["meta", "nodes"], + "$defs": { + "node": { + "$id": "http://localhost:1234/draft2020-12/node", + "description": "node", + "type": "object", + "properties": { + "value": {"type": "number"}, + "subtree": {"$ref": "tree"} + }, + "required": ["value"] + } + } + }, + "tests": [ + { + "description": "valid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 1.1}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": true + }, + { + "description": "invalid tree", + "data": { + "meta": "root", + "nodes": [ + { + "value": 1, + "subtree": { + "meta": "child", + "nodes": [ + {"value": "string is invalid"}, + {"value": 1.2} + ] + } + }, + { + "value": 2, + "subtree": { + "meta": "child", + "nodes": [ + {"value": 2.1}, + {"value": 2.2} + ] + } + } + ] + }, + "valid": false + } + ] + }, + { + "description": "refs with quote", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo\"bar": {"$ref": "#/$defs/foo%22bar"} + }, + "$defs": { + "foo\"bar": {"type": "number"} + } + }, + "tests": [ + { + "description": "object with numbers is valid", + "data": { + "foo\"bar": 1 + }, + "valid": true + }, + { + "description": "object with strings is invalid", + "data": { + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "ref creates new scope when adjacent to keywords", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "A": { + "unevaluatedProperties": false + } + }, + "properties": { + "prop1": { + "type": "string" + } + }, + "$ref": "#/$defs/A" + }, + "tests": [ + { + "description": "referenced subschema doesn't see annotations from properties", + "data": { + "prop1": "match" + }, + "valid": false + } + ] + }, + { + "description": "naive replacement of $ref with its destination is not correct", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "a_string": { "type": "string" } + }, + "enum": [ + { "$ref": "#/$defs/a_string" } + ] + }, + "tests": [ + { + "description": "do not evaluate the $ref inside the enum, matching any string", + "data": "this is a string", + "valid": false + }, + { + "description": "do not evaluate the $ref inside the enum, definition exact match", + "data": { "type": "string" }, + "valid": false + }, + { + "description": "match the enum exactly", + "data": { "$ref": "#/$defs/a_string" }, + "valid": true + } + ] + }, + { + "description": "refs with relative uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/schema-relative-uri-defs1.json", + "properties": { + "foo": { + "$id": "schema-relative-uri-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-relative-uri-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "relative refs with absolute uris and defs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/schema-refs-absolute-uris-defs1.json", + "properties": { + "foo": { + "$id": "http://example.com/schema-refs-absolute-uris-defs2.json", + "$defs": { + "inner": { + "properties": { + "bar": { "type": "string" } + } + } + }, + "$ref": "#/$defs/inner" + } + }, + "$ref": "schema-refs-absolute-uris-defs2.json" + }, + "tests": [ + { + "description": "invalid on inner field", + "data": { + "foo": { + "bar": 1 + }, + "bar": "a" + }, + "valid": false + }, + { + "description": "invalid on outer field", + "data": { + "foo": { + "bar": "a" + }, + "bar": 1 + }, + "valid": false + }, + { + "description": "valid on both fields", + "data": { + "foo": { + "bar": "a" + }, + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "$id must be resolved against nearest parent, not just immediate parent", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/a.json", + "$defs": { + "x": { + "$id": "http://example.com/b/c.json", + "not": { + "$defs": { + "y": { + "$id": "d.json", + "type": "number" + } + } + } + } + }, + "allOf": [ + { + "$ref": "http://example.com/b/d.json" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $ref", + "schema": { + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/draft2020-12/ref-and-id1/base.json", + "$ref": "int.json", + "$defs": { + "bigint": { + "$comment": "canonical uri: https://example.com/ref-and-id1/int.json", + "$id": "int.json", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id1-int.json", + "$id": "/draft2020-12/ref-and-id1-int.json", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "order of evaluation: $id and $anchor and $ref", + "schema": { + "$comment": "$id must be evaluated before $ref to get the proper $ref destination", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/draft2020-12/ref-and-id2/base.json", + "$ref": "#bigint", + "$defs": { + "bigint": { + "$comment": "canonical uri: /ref-and-id2/base.json#/$defs/bigint; another valid uri for this location: /ref-and-id2/base.json#bigint", + "$anchor": "bigint", + "maximum": 10 + }, + "smallint": { + "$comment": "canonical uri: https://example.com/ref-and-id2#/$defs/smallint; another valid uri for this location: https://example.com/ref-and-id2/#bigint", + "$id": "https://example.com/draft2020-12/ref-and-id2/", + "$anchor": "bigint", + "maximum": 2 + } + } + }, + "tests": [ + { + "description": "data is valid against first definition", + "data": 5, + "valid": true + }, + { + "description": "data is invalid against first definition", + "data": 50, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with $ref via the URN", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed", + "minimum": 30, + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ffff-ffff-4321feebdaed"} + } + }, + "tests": [ + { + "description": "valid under the URN IDed schema", + "data": {"foo": 37}, + "valid": true + }, + { + "description": "invalid under the URN IDed schema", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "simple URN base URI with JSON pointer", + "schema": { + "$comment": "URIs do not have to have HTTP(s) schemes", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-00ff-ff00-4321feebdaed", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with NSS", + "schema": { + "$comment": "RFC 8141 §2.2", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:1/406/47452/2", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with r-component", + "schema": { + "$comment": "RFC 8141 §2.3.1", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:foo-bar-baz-qux?+CCResolve:cc=uk", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with q-component", + "schema": { + "$comment": "RFC 8141 §2.3.2", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:example:weather?=op=map&lat=39.56&lon=-104.85&datetime=1969-07-21T02:56:15Z", + "properties": { + "foo": {"$ref": "#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and JSON pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-0000-0000-4321feebdaed#/$defs/bar"} + }, + "$defs": { + "bar": {"type": "string"} + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN base URI with URN and anchor ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed", + "properties": { + "foo": {"$ref": "urn:uuid:deadbeef-1234-ff00-00ff-4321feebdaed#something"} + }, + "$defs": { + "bar": { + "$anchor": "something", + "type": "string" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": {"foo": "bar"}, + "valid": true + }, + { + "description": "a non-string is invalid", + "data": {"foo": 12}, + "valid": false + } + ] + }, + { + "description": "URN ref with nested pointer ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": { + "foo": { + "$id": "urn:uuid:deadbeef-4321-ffff-ffff-1234feebdaed", + "$defs": {"bar": {"type": "string"}}, + "$ref": "#/$defs/bar" + } + } + }, + "tests": [ + { + "description": "a string is valid", + "data": "bar", + "valid": true + }, + { + "description": "a non-string is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "ref to if", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/if", + "if": { + "$id": "http://example.com/ref/if", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to then", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/then", + "then": { + "$id": "http://example.com/ref/then", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref to else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://example.com/ref/else", + "else": { + "$id": "http://example.com/ref/else", + "type": "integer" + } + }, + "tests": [ + { + "description": "a non-integer is invalid due to the $ref", + "data": "foo", + "valid": false + }, + { + "description": "an integer is valid", + "data": 12, + "valid": true + } + ] + }, + { + "description": "ref with absolute-path-reference", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://example.com/ref/absref.json", + "$defs": { + "a": { + "$id": "http://example.com/ref/absref/foobar.json", + "type": "number" + }, + "b": { + "$id": "http://example.com/absref/foobar.json", + "type": "string" + } + }, + "$ref": "/absref/foobar.json" + }, + "tests": [ + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "an integer is invalid", + "data": 12, + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - *nix", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:///folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "$id with file URI still resolves pointers - windows", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "file:///c:/folder/file.json", + "$defs": { + "foo": { + "type": "number" + } + }, + "$ref": "#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "empty tokens in $ref json-pointer", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "": { + "$defs": { + "": { "type": "number" } + } + } + }, + "allOf": [ + { + "$ref": "#/$defs//$defs/" + } + ] + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/refRemote.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/refRemote.json new file mode 100644 index 00000000..047ac74c --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/refRemote.json @@ -0,0 +1,342 @@ +[ + { + "description": "remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/integer.json" + }, + "tests": [ + { + "description": "remote ref valid", + "data": 1, + "valid": true + }, + { + "description": "remote ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "fragment within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/subSchemas.json#/$defs/integer" + }, + "tests": [ + { + "description": "remote fragment valid", + "data": 1, + "valid": true + }, + { + "description": "remote fragment invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "anchor within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/locationIndependentIdentifier.json#foo" + }, + "tests": [ + { + "description": "remote anchor valid", + "data": 1, + "valid": true + }, + { + "description": "remote anchor invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "ref within remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/subSchemas.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "ref within ref valid", + "data": 1, + "valid": true + }, + { + "description": "ref within ref invalid", + "data": "a", + "valid": false + } + ] + }, + { + "description": "base URI change", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/", + "items": { + "$id": "baseUriChange/", + "items": {"$ref": "folderInteger.json"} + } + }, + "tests": [ + { + "description": "base URI change ref valid", + "data": [[1]], + "valid": true + }, + { + "description": "base URI change ref invalid", + "data": [["a"]], + "valid": false + } + ] + }, + { + "description": "base URI change - change folder", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/scope_change_defs1.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolder/"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolder/", + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "base URI change - change folder in subschema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/scope_change_defs2.json", + "type" : "object", + "properties": {"list": {"$ref": "baseUriChangeFolderInSubschema/#/$defs/bar"}}, + "$defs": { + "baz": { + "$id": "baseUriChangeFolderInSubschema/", + "$defs": { + "bar": { + "type": "array", + "items": {"$ref": "folderInteger.json"} + } + } + } + } + }, + "tests": [ + { + "description": "number is valid", + "data": {"list": [1]}, + "valid": true + }, + { + "description": "string is invalid", + "data": {"list": ["a"]}, + "valid": false + } + ] + }, + { + "description": "root ref in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/object", + "type": "object", + "properties": { + "name": {"$ref": "name-defs.json#/$defs/orNull"} + } + }, + "tests": [ + { + "description": "string is valid", + "data": { + "name": "foo" + }, + "valid": true + }, + { + "description": "null is valid", + "data": { + "name": null + }, + "valid": true + }, + { + "description": "object is invalid", + "data": { + "name": { + "name": null + } + }, + "valid": false + } + ] + }, + { + "description": "remote ref with ref to defs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/schema-remote-ref-ref-defs1.json", + "$ref": "ref-and-defs.json" + }, + "tests": [ + { + "description": "invalid", + "data": { + "bar": 1 + }, + "valid": false + }, + { + "description": "valid", + "data": { + "bar": "a" + }, + "valid": true + } + ] + }, + { + "description": "Location-independent identifier in remote ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/locationIndependentIdentifier.json#/$defs/refToInteger" + }, + "tests": [ + { + "description": "integer is valid", + "data": 1, + "valid": true + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + }, + { + "description": "retrieved nested refs resolve relative to their URI not $id", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "http://localhost:1234/draft2020-12/some-id", + "properties": { + "name": {"$ref": "nested/foo-ref-string.json"} + } + }, + "tests": [ + { + "description": "number is invalid", + "data": { + "name": {"foo": 1} + }, + "valid": false + }, + { + "description": "string is valid", + "data": { + "name": {"foo": "a"} + }, + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different $id", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/different-id-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with different URN $id", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/urn-ref-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "remote HTTP ref with nested absolute ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/nested-absolute-ref-to-string.json" + }, + "tests": [ + { + "description": "number is invalid", + "data": 1, + "valid": false + }, + { + "description": "string is valid", + "data": "foo", + "valid": true + } + ] + }, + { + "description": "$ref to $ref finds detached $anchor", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "http://localhost:1234/draft2020-12/detached-ref.json#/$defs/foo" + }, + "tests": [ + { + "description": "number is valid", + "data": 1, + "valid": true + }, + { + "description": "non-number is invalid", + "data": "a", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/required.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/required.json new file mode 100644 index 00000000..b7cb99a6 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/required.json @@ -0,0 +1,158 @@ +[ + { + "description": "required validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {}, + "bar": {} + }, + "required": ["foo"] + }, + "tests": [ + { + "description": "present required property is valid", + "data": {"foo": 1}, + "valid": true + }, + { + "description": "non-present required property is invalid", + "data": {"bar": 1}, + "valid": false + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "", + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + } + ] + }, + { + "description": "required default validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + } + }, + "tests": [ + { + "description": "not required by default", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with empty array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": {} + }, + "required": [] + }, + "tests": [ + { + "description": "property not required", + "data": {}, + "valid": true + } + ] + }, + { + "description": "required with escaped characters", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": [ + "foo\nbar", + "foo\"bar", + "foo\\bar", + "foo\rbar", + "foo\tbar", + "foo\fbar" + ] + }, + "tests": [ + { + "description": "object with all properties present is valid", + "data": { + "foo\nbar": 1, + "foo\"bar": 1, + "foo\\bar": 1, + "foo\rbar": 1, + "foo\tbar": 1, + "foo\fbar": 1 + }, + "valid": true + }, + { + "description": "object with some properties missing is invalid", + "data": { + "foo\nbar": "1", + "foo\"bar": "1" + }, + "valid": false + } + ] + }, + { + "description": "required properties whose names are Javascript object property names", + "comment": "Ensure JS implementations don't universally consider e.g. __proto__ to always be present in an object.", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "required": ["__proto__", "toString", "constructor"] + }, + "tests": [ + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores other non-objects", + "data": 12, + "valid": true + }, + { + "description": "none of the properties mentioned", + "data": {}, + "valid": false + }, + { + "description": "__proto__ present", + "data": { "__proto__": "foo" }, + "valid": false + }, + { + "description": "toString present", + "data": { "toString": { "length": 37 } }, + "valid": false + }, + { + "description": "constructor present", + "data": { "constructor": { "length": 37 } }, + "valid": false + }, + { + "description": "all present", + "data": { + "__proto__": 12, + "toString": { "length": "foo" }, + "constructor": 37 + }, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/type.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/type.json new file mode 100644 index 00000000..2123c408 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/type.json @@ -0,0 +1,501 @@ +[ + { + "description": "integer type matches integers", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "integer" + }, + "tests": [ + { + "description": "an integer is an integer", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is an integer", + "data": 1.0, + "valid": true + }, + { + "description": "a float is not an integer", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an integer", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not an integer, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not an integer", + "data": {}, + "valid": false + }, + { + "description": "an array is not an integer", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an integer", + "data": true, + "valid": false + }, + { + "description": "null is not an integer", + "data": null, + "valid": false + } + ] + }, + { + "description": "number type matches numbers", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "number" + }, + "tests": [ + { + "description": "an integer is a number", + "data": 1, + "valid": true + }, + { + "description": "a float with zero fractional part is a number (and an integer)", + "data": 1.0, + "valid": true + }, + { + "description": "a float is a number", + "data": 1.1, + "valid": true + }, + { + "description": "a string is not a number", + "data": "foo", + "valid": false + }, + { + "description": "a string is still not a number, even if it looks like one", + "data": "1", + "valid": false + }, + { + "description": "an object is not a number", + "data": {}, + "valid": false + }, + { + "description": "an array is not a number", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a number", + "data": true, + "valid": false + }, + { + "description": "null is not a number", + "data": null, + "valid": false + } + ] + }, + { + "description": "string type matches strings", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "string" + }, + "tests": [ + { + "description": "1 is not a string", + "data": 1, + "valid": false + }, + { + "description": "a float is not a string", + "data": 1.1, + "valid": false + }, + { + "description": "a string is a string", + "data": "foo", + "valid": true + }, + { + "description": "a string is still a string, even if it looks like a number", + "data": "1", + "valid": true + }, + { + "description": "an empty string is still a string", + "data": "", + "valid": true + }, + { + "description": "an object is not a string", + "data": {}, + "valid": false + }, + { + "description": "an array is not a string", + "data": [], + "valid": false + }, + { + "description": "a boolean is not a string", + "data": true, + "valid": false + }, + { + "description": "null is not a string", + "data": null, + "valid": false + } + ] + }, + { + "description": "object type matches objects", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object" + }, + "tests": [ + { + "description": "an integer is not an object", + "data": 1, + "valid": false + }, + { + "description": "a float is not an object", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an object", + "data": "foo", + "valid": false + }, + { + "description": "an object is an object", + "data": {}, + "valid": true + }, + { + "description": "an array is not an object", + "data": [], + "valid": false + }, + { + "description": "a boolean is not an object", + "data": true, + "valid": false + }, + { + "description": "null is not an object", + "data": null, + "valid": false + } + ] + }, + { + "description": "array type matches arrays", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "array" + }, + "tests": [ + { + "description": "an integer is not an array", + "data": 1, + "valid": false + }, + { + "description": "a float is not an array", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not an array", + "data": "foo", + "valid": false + }, + { + "description": "an object is not an array", + "data": {}, + "valid": false + }, + { + "description": "an array is an array", + "data": [], + "valid": true + }, + { + "description": "a boolean is not an array", + "data": true, + "valid": false + }, + { + "description": "null is not an array", + "data": null, + "valid": false + } + ] + }, + { + "description": "boolean type matches booleans", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "boolean" + }, + "tests": [ + { + "description": "an integer is not a boolean", + "data": 1, + "valid": false + }, + { + "description": "zero is not a boolean", + "data": 0, + "valid": false + }, + { + "description": "a float is not a boolean", + "data": 1.1, + "valid": false + }, + { + "description": "a string is not a boolean", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not a boolean", + "data": "", + "valid": false + }, + { + "description": "an object is not a boolean", + "data": {}, + "valid": false + }, + { + "description": "an array is not a boolean", + "data": [], + "valid": false + }, + { + "description": "true is a boolean", + "data": true, + "valid": true + }, + { + "description": "false is a boolean", + "data": false, + "valid": true + }, + { + "description": "null is not a boolean", + "data": null, + "valid": false + } + ] + }, + { + "description": "null type matches only the null object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "null" + }, + "tests": [ + { + "description": "an integer is not null", + "data": 1, + "valid": false + }, + { + "description": "a float is not null", + "data": 1.1, + "valid": false + }, + { + "description": "zero is not null", + "data": 0, + "valid": false + }, + { + "description": "a string is not null", + "data": "foo", + "valid": false + }, + { + "description": "an empty string is not null", + "data": "", + "valid": false + }, + { + "description": "an object is not null", + "data": {}, + "valid": false + }, + { + "description": "an array is not null", + "data": [], + "valid": false + }, + { + "description": "true is not null", + "data": true, + "valid": false + }, + { + "description": "false is not null", + "data": false, + "valid": false + }, + { + "description": "null is null", + "data": null, + "valid": true + } + ] + }, + { + "description": "multiple types can be specified in an array", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["integer", "string"] + }, + "tests": [ + { + "description": "an integer is valid", + "data": 1, + "valid": true + }, + { + "description": "a string is valid", + "data": "foo", + "valid": true + }, + { + "description": "a float is invalid", + "data": 1.1, + "valid": false + }, + { + "description": "an object is invalid", + "data": {}, + "valid": false + }, + { + "description": "an array is invalid", + "data": [], + "valid": false + }, + { + "description": "a boolean is invalid", + "data": true, + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type as array with one item", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["string"] + }, + "tests": [ + { + "description": "string is valid", + "data": "foo", + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + } + ] + }, + { + "description": "type: array or object", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["array", "object"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + }, + { + "description": "null is invalid", + "data": null, + "valid": false + } + ] + }, + { + "description": "type: array, object or null", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": ["array", "object", "null"] + }, + "tests": [ + { + "description": "array is valid", + "data": [1,2,3], + "valid": true + }, + { + "description": "object is valid", + "data": {"foo": 123}, + "valid": true + }, + { + "description": "null is valid", + "data": null, + "valid": true + }, + { + "description": "number is invalid", + "data": 123, + "valid": false + }, + { + "description": "string is invalid", + "data": "foo", + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedItems.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedItems.json new file mode 100644 index 00000000..f861cefa --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedItems.json @@ -0,0 +1,798 @@ +[ + { + "description": "unevaluatedItems true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": true + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems as schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": { "type": "string" } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with valid unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with invalid unevaluated items", + "data": [42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with uniform items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": { "type": "string" }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", "bar"], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with tuple", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with items and prefixItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "type": "string" } + ], + "items": true, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "unevaluatedItems doesn't apply", + "data": ["foo", 42], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "items": {"type": "number"}, + "unevaluatedItems": {"type": "string"} + }, + "tests": [ + { + "description": "valid under items", + "comment": "no elements are considered by unevaluatedItems", + "data": [5, 6, 7, 8], + "valid": true + }, + { + "description": "invalid under items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested tuple", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "type": "string" } + ], + "allOf": [ + { + "prefixItems": [ + true, + { "type": "number" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", 42], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", 42, true], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": {"type": "boolean"}, + "anyOf": [ + { "items": {"type": "string"} }, + true + ] + }, + "tests": [ + { + "description": "with only (valid) additional items", + "data": [true, false], + "valid": true + }, + { + "description": "with no additional items", + "data": ["yes", "no"], + "valid": true + }, + { + "description": "with invalid additional item", + "data": ["yes", false], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with nested prefixItems and items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "prefixItems": [ + { "type": "string" } + ], + "items": true + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with nested unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "prefixItems": [ + { "type": "string" } + ] + }, + { "unevaluatedItems": true } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no additional items", + "data": ["foo"], + "valid": true + }, + { + "description": "with additional items", + "data": ["foo", 42, true], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "anyOf": [ + { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + { + "prefixItems": [ + true, + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when one schema matches and has no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "when one schema matches and has unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + }, + { + "description": "when two schemas match and has no unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "when two schemas match and has unevaluated items", + "data": ["foo", "bar", "baz", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "oneOf": [ + { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + { + "prefixItems": [ + true, + { "const": "baz" } + ] + } + ], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with not", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "not": { + "not": { + "prefixItems": [ + true, + { "const": "bar" } + ] + } + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with unevaluated items", + "data": ["foo", "bar"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [ + { "const": "foo" } + ], + "if": { + "prefixItems": [ + true, + { "const": "bar" } + ] + }, + "then": { + "prefixItems": [ + true, + true, + { "const": "then" } + ] + }, + "else": { + "prefixItems": [ + true, + true, + true, + { "const": "else" } + ] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "when if matches and it has no unevaluated items", + "data": ["foo", "bar", "then"], + "valid": true + }, + { + "description": "when if matches and it has unevaluated items", + "data": ["foo", "bar", "then", "else"], + "valid": false + }, + { + "description": "when if doesn't match and it has no unevaluated items", + "data": ["foo", 42, 42, "else"], + "valid": true + }, + { + "description": "when if doesn't match and it has unevaluated items", + "data": ["foo", 42, 42, "else", 42], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [true], + "unevaluatedItems": false + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": [], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$ref": "#/$defs/bar", + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false, + "$defs": { + "bar": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": false, + "prefixItems": [ + { "type": "string" } + ], + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems with $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/unevaluated-items-with-dynamic-ref/derived", + + "$ref": "./baseSchema", + + "$defs": { + "derived": { + "$dynamicAnchor": "addons", + "prefixItems": [ + true, + { "type": "string" } + ] + }, + "baseSchema": { + "$id": "./baseSchema", + + "$comment": "unevaluatedItems comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering", + "unevaluatedItems": false, + "type": "array", + "prefixItems": [ + { "type": "string" } + ], + "$dynamicRef": "#addons", + + "$defs": { + "defaultAddons": { + "$comment": "Needed to satisfy the bookending requirement", + "$dynamicAnchor": "addons" + } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated items", + "data": ["foo", "bar"], + "valid": true + }, + { + "description": "with unevaluated items", + "data": ["foo", "bar", "baz"], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "prefixItems": [ true ] + }, + { "unevaluatedItems": false } + ] + }, + "tests": [ + { + "description": "always fails", + "data": [ 1 ], + "valid": false + } + ] + }, + { + "description": "item is evaluated in an uncle schema to unevaluatedItems", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": { + "foo": { + "prefixItems": [ + { "type": "string" } + ], + "unevaluatedItems": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "prefixItems": [ + true, + { "type": "string" } + ] + } + } + } + ] + }, + "tests": [ + { + "description": "no extra items", + "data": { + "foo": [ + "test" + ] + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": [ + "test", + "test" + ] + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedItems depends on adjacent contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [true], + "contains": {"type": "string"}, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "second item is evaluated by contains", + "data": [ 1, "foo" ], + "valid": true + }, + { + "description": "contains fails, second item is not evaluated", + "data": [ 1, 2 ], + "valid": false + }, + { + "description": "contains passes, second item is not evaluated", + "data": [ 1, 2, "foo" ], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems depends on multiple nested contains", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { "contains": { "multipleOf": 2 } }, + { "contains": { "multipleOf": 3 } } + ], + "unevaluatedItems": { "multipleOf": 5 } + }, + "tests": [ + { + "description": "5 not evaluated, passes unevaluatedItems", + "data": [ 2, 3, 4, 5, 6 ], + "valid": true + }, + { + "description": "7 not evaluated, fails unevaluatedItems", + "data": [ 2, 3, 4, 7, 8 ], + "valid": false + } + ] + }, + { + "description": "unevaluatedItems and contains interact to control item dependency relationship", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "contains": {"const": "a"} + }, + "then": { + "if": { + "contains": {"const": "b"} + }, + "then": { + "if": { + "contains": {"const": "c"} + } + } + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "empty array is valid", + "data": [], + "valid": true + }, + { + "description": "only a's are valid", + "data": [ "a", "a" ], + "valid": true + }, + { + "description": "a's and b's are valid", + "data": [ "a", "b", "a", "b", "a" ], + "valid": true + }, + { + "description": "a's, b's and c's are valid", + "data": [ "c", "a", "c", "c", "b", "a" ], + "valid": true + }, + { + "description": "only b's are invalid", + "data": [ "b", "b" ], + "valid": false + }, + { + "description": "only c's are invalid", + "data": [ "c", "c" ], + "valid": false + }, + { + "description": "only b's and c's are invalid", + "data": [ "c", "b", "c", "b", "c" ], + "valid": false + }, + { + "description": "only a's and c's are invalid", + "data": [ "c", "a", "c", "a", "c" ], + "valid": false + } + ] + }, + { + "description": "non-array instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores objects", + "data": {}, + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedItems with null instance elements", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedItems": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null elements", + "data": [ null ], + "valid": true + } + ] + }, + { + "description": "unevaluatedItems can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "prefixItems": [{"const": "a"}] + }, + "unevaluatedItems": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": [ "a" ], + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": [ "b" ], + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedProperties.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedProperties.json new file mode 100644 index 00000000..ae29c9eb --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/unevaluatedProperties.json @@ -0,0 +1,1601 @@ +[ + { + "description": "unevaluatedProperties true", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties schema", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "unevaluatedProperties": { + "type": "string", + "minLength": 3 + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with valid unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with invalid unevaluated properties", + "data": { + "foo": "fo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": {}, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "patternProperties": { + "^foo": { "type": "string" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with adjacent additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "additionalProperties": true, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "properties": { + "bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested patternProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "patternProperties": { + "^bar": { "type": "string" } + } + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with nested additionalProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "additionalProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no additional properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with additional properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with nested unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": { + "type": "string", + "maxLength": 2 + } + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with anyOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "anyOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + }, + { + "properties": { + "quux": { "const": "quux" } + }, + "required": ["quux"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when one matches and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when one matches and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "not-baz" + }, + "valid": false + }, + { + "description": "when two match and has no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": true + }, + { + "description": "when two match and has unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz", + "quux": "not-quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "oneOf": [ + { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + }, + { + "properties": { + "baz": { "const": "baz" } + }, + "required": ["baz"] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "quux": "quux" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with not", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "not": { + "not": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, then not defined", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "else": { + "properties": { + "baz": { "type": "string" } + }, + "required": ["baz"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": false + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": true + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with if/then/else, else not defined", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "if": { + "properties": { + "foo": { "const": "then" } + }, + "required": ["foo"] + }, + "then": { + "properties": { + "bar": { "type": "string" } + }, + "required": ["bar"] + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "when if is true and has no unevaluated properties", + "data": { + "foo": "then", + "bar": "bar" + }, + "valid": true + }, + { + "description": "when if is true and has unevaluated properties", + "data": { + "foo": "then", + "bar": "bar", + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has no unevaluated properties", + "data": { + "baz": "baz" + }, + "valid": false + }, + { + "description": "when if is false and has unevaluated properties", + "data": { + "foo": "else", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with dependentSchemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "dependentSchemas": { + "foo": { + "properties": { + "bar": { "const": "bar" } + }, + "required": ["bar"] + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with boolean schemas", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [true], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "$ref": "#/$defs/bar", + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false, + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties before $ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "unevaluatedProperties": false, + "properties": { + "foo": { "type": "string" } + }, + "$ref": "#/$defs/bar", + "$defs": { + "bar": { + "properties": { + "bar": { "type": "string" } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties with $dynamicRef", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.com/unevaluated-properties-with-dynamic-ref/derived", + + "$ref": "./baseSchema", + + "$defs": { + "derived": { + "$dynamicAnchor": "addons", + "properties": { + "bar": { "type": "string" } + } + }, + "baseSchema": { + "$id": "./baseSchema", + + "$comment": "unevaluatedProperties comes first so it's more likely to catch bugs with implementations that are sensitive to keyword ordering", + "unevaluatedProperties": false, + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "$dynamicRef": "#addons", + + "$defs": { + "defaultAddons": { + "$comment": "Needed to satisfy the bookending requirement", + "$dynamicAnchor": "addons" + } + } + } + } + }, + "tests": [ + { + "description": "with no unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + }, + { + "description": "with unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar", + "baz": "baz" + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "properties": { + "foo": true + } + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can't see inside cousins (reverse order)", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "allOf": [ + { + "unevaluatedProperties": false + }, + { + "properties": { + "foo": true + } + } + ] + }, + "tests": [ + { + "description": "always fails", + "data": { + "foo": 1 + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer false, inner true, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": true + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties outside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { "type": "string" } + }, + "allOf": [ + { + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "nested unevaluatedProperties, outer true, inner false, properties inside", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ], + "unevaluatedProperties": true + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, true with properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": true + }, + { + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": false + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "cousin unevaluatedProperties, true and false, false with properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "allOf": [ + { + "unevaluatedProperties": true + }, + { + "properties": { + "foo": { "type": "string" } + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "with no nested unevaluated properties", + "data": { + "foo": "foo" + }, + "valid": true + }, + { + "description": "with nested unevaluated properties", + "data": { + "foo": "foo", + "bar": "bar" + }, + "valid": false + } + ] + }, + { + "description": "property is evaluated in an uncle schema to unevaluatedProperties", + "comment": "see https://stackoverflow.com/questions/66936884/deeply-nested-unevaluatedproperties-and-their-expectations", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "foo": { + "type": "object", + "properties": { + "bar": { + "type": "string" + } + }, + "unevaluatedProperties": false + } + }, + "anyOf": [ + { + "properties": { + "foo": { + "properties": { + "faz": { + "type": "string" + } + } + } + } + } + ] + }, + "tests": [ + { + "description": "no extra properties", + "data": { + "foo": { + "bar": "test" + } + }, + "valid": true + }, + { + "description": "uncle keyword evaluation is not significant", + "data": { + "foo": { + "bar": "test", + "faz": "test" + } + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, allOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + }, + "unevaluatedProperties": false + } + ], + "anyOf": [ + { + "properties": { + "bar": true + } + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": true + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": false + } + ] + }, + { + "description": "in-place applicator siblings, anyOf has unevaluated", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "allOf": [ + { + "properties": { + "foo": true + } + } + ], + "anyOf": [ + { + "properties": { + "bar": true + }, + "unevaluatedProperties": false + } + ] + }, + "tests": [ + { + "description": "base case: both properties present", + "data": { + "foo": 1, + "bar": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, bar is missing", + "data": { + "foo": 1 + }, + "valid": false + }, + { + "description": "in place applicator siblings, foo is missing", + "data": { + "bar": 1 + }, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties + single cyclic ref", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "x": { "$ref": "#" } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is valid", + "data": {}, + "valid": true + }, + { + "description": "Single is valid", + "data": { "x": {} }, + "valid": true + }, + { + "description": "Unevaluated on 1st level is invalid", + "data": { "x": {}, "y": {} }, + "valid": false + }, + { + "description": "Nested is valid", + "data": { "x": { "x": {} } }, + "valid": true + }, + { + "description": "Unevaluated on 2nd level is invalid", + "data": { "x": { "x": {}, "y": {} } }, + "valid": false + }, + { + "description": "Deep nested is valid", + "data": { "x": { "x": { "x": {} } } }, + "valid": true + }, + { + "description": "Unevaluated on 3rd level is invalid", + "data": { "x": { "x": { "x": {}, "y": {} } } }, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties + ref inside allOf / oneOf", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "one": { + "properties": { "a": true } + }, + "two": { + "required": ["x"], + "properties": { "x": true } + } + }, + "allOf": [ + { "$ref": "#/$defs/one" }, + { "properties": { "b": true } }, + { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { + "required": ["y"], + "properties": { "y": true } + } + ] + } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid (no x or y)", + "data": {}, + "valid": false + }, + { + "description": "a and b are invalid (no x or y)", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "x and y are invalid", + "data": { "x": 1, "y": 1 }, + "valid": false + }, + { + "description": "a and x are valid", + "data": { "a": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and y are valid", + "data": { "a": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x are valid", + "data": { "a": 1, "b": 1, "x": 1 }, + "valid": true + }, + { + "description": "a and b and y are valid", + "data": { "a": 1, "b": 1, "y": 1 }, + "valid": true + }, + { + "description": "a and b and x and y are invalid", + "data": { "a": 1, "b": 1, "x": 1, "y": 1 }, + "valid": false + } + ] + }, + { + "description": "dynamic evalation inside nested refs", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "one": { + "oneOf": [ + { "$ref": "#/$defs/two" }, + { "required": ["b"], "properties": { "b": true } }, + { "required": ["xx"], "patternProperties": { "x": true } }, + { "required": ["all"], "unevaluatedProperties": true } + ] + }, + "two": { + "oneOf": [ + { "required": ["c"], "properties": { "c": true } }, + { "required": ["d"], "properties": { "d": true } } + ] + } + }, + "oneOf": [ + { "$ref": "#/$defs/one" }, + { "required": ["a"], "properties": { "a": true } } + ], + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "Empty is invalid", + "data": {}, + "valid": false + }, + { + "description": "a is valid", + "data": { "a": 1 }, + "valid": true + }, + { + "description": "b is valid", + "data": { "b": 1 }, + "valid": true + }, + { + "description": "c is valid", + "data": { "c": 1 }, + "valid": true + }, + { + "description": "d is valid", + "data": { "d": 1 }, + "valid": true + }, + { + "description": "a + b is invalid", + "data": { "a": 1, "b": 1 }, + "valid": false + }, + { + "description": "a + c is invalid", + "data": { "a": 1, "c": 1 }, + "valid": false + }, + { + "description": "a + d is invalid", + "data": { "a": 1, "d": 1 }, + "valid": false + }, + { + "description": "b + c is invalid", + "data": { "b": 1, "c": 1 }, + "valid": false + }, + { + "description": "b + d is invalid", + "data": { "b": 1, "d": 1 }, + "valid": false + }, + { + "description": "c + d is invalid", + "data": { "c": 1, "d": 1 }, + "valid": false + }, + { + "description": "xx is valid", + "data": { "xx": 1 }, + "valid": true + }, + { + "description": "xx + foox is valid", + "data": { "xx": 1, "foox": 1 }, + "valid": true + }, + { + "description": "xx + foo is invalid", + "data": { "xx": 1, "foo": 1 }, + "valid": false + }, + { + "description": "xx + a is invalid", + "data": { "xx": 1, "a": 1 }, + "valid": false + }, + { + "description": "xx + b is invalid", + "data": { "xx": 1, "b": 1 }, + "valid": false + }, + { + "description": "xx + c is invalid", + "data": { "xx": 1, "c": 1 }, + "valid": false + }, + { + "description": "xx + d is invalid", + "data": { "xx": 1, "d": 1 }, + "valid": false + }, + { + "description": "all is valid", + "data": { "all": 1 }, + "valid": true + }, + { + "description": "all + foo is valid", + "data": { "all": 1, "foo": 1 }, + "valid": true + }, + { + "description": "all + a is invalid", + "data": { "all": 1, "a": 1 }, + "valid": false + } + ] + }, + { + "description": "non-object instances are valid", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "ignores booleans", + "data": true, + "valid": true + }, + { + "description": "ignores integers", + "data": 123, + "valid": true + }, + { + "description": "ignores floats", + "data": 1.0, + "valid": true + }, + { + "description": "ignores arrays", + "data": [], + "valid": true + }, + { + "description": "ignores strings", + "data": "foo", + "valid": true + }, + { + "description": "ignores null", + "data": null, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties with null valued instance properties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "unevaluatedProperties": { + "type": "null" + } + }, + "tests": [ + { + "description": "allows null valued properties", + "data": {"foo": null}, + "valid": true + } + ] + }, + { + "description": "unevaluatedProperties not affected by propertyNames", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "propertyNames": {"maxLength": 1}, + "unevaluatedProperties": { + "type": "number" + } + }, + "tests": [ + { + "description": "allows only number properties", + "data": {"a": 1}, + "valid": true + }, + { + "description": "string property is invalid", + "data": {"a": "b"}, + "valid": false + } + ] + }, + { + "description": "unevaluatedProperties can see annotations from if without then and else", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "if": { + "patternProperties": { + "foo": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "valid in case if is evaluated", + "data": { + "foo": "a" + }, + "valid": true + }, + { + "description": "invalid in case if is evaluated", + "data": { + "bar": "a" + }, + "valid": false + } + ] + }, + { + "description": "dependentSchemas with unevaluatedProperties", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "properties": {"foo2": {}}, + "dependentSchemas": { + "foo" : {}, + "foo2": { + "properties": { + "bar":{} + } + } + }, + "unevaluatedProperties": false + }, + "tests": [ + { + "description": "unevaluatedProperties doesn't consider dependentSchemas", + "data": {"foo": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties doesn't see bar when foo2 is absent", + "data": {"bar": ""}, + "valid": false + }, + { + "description": "unevaluatedProperties sees bar when foo2 is present", + "data": { "foo2": "", "bar": ""}, + "valid": true + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/uniqueItems.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/uniqueItems.json new file mode 100644 index 00000000..4ea3bf98 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/uniqueItems.json @@ -0,0 +1,419 @@ +[ + { + "description": "uniqueItems validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "uniqueItems": true + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is invalid", + "data": [1, 1], + "valid": false + }, + { + "description": "non-unique array of more than two integers is invalid", + "data": [1, 2, 1], + "valid": false + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": false + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of strings is valid", + "data": ["foo", "bar", "baz"], + "valid": true + }, + { + "description": "non-unique array of strings is invalid", + "data": ["foo", "bar", "foo"], + "valid": false + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is invalid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": false + }, + { + "description": "property order of array of objects is ignored", + "data": [{"foo": "bar", "bar": "foo"}, {"bar": "foo", "foo": "bar"}], + "valid": false + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is invalid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": false + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is invalid", + "data": [["foo"], ["foo"]], + "valid": false + }, + { + "description": "non-unique array of more than two arrays is invalid", + "data": [["foo"], ["bar"], ["foo"]], + "valid": false + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "[1] and [true] are unique", + "data": [[1], [true]], + "valid": true + }, + { + "description": "[0] and [false] are unique", + "data": [[0], [false]], + "valid": true + }, + { + "description": "nested [1] and [true] are unique", + "data": [[[1], "foo"], [[true], "foo"]], + "valid": true + }, + { + "description": "nested [0] and [false] are unique", + "data": [[[0], "foo"], [[false], "foo"]], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1, "{}"], + "valid": true + }, + { + "description": "non-unique heterogeneous types are invalid", + "data": [{}, [1], true, null, {}, 1], + "valid": false + }, + { + "description": "different objects are unique", + "data": [{"a": 1, "b": 2}, {"a": 2, "b": 1}], + "valid": true + }, + { + "description": "objects are non-unique despite key order", + "data": [{"a": 1, "b": 2}, {"b": 2, "a": 1}], + "valid": false + }, + { + "description": "{\"a\": false} and {\"a\": 0} are unique", + "data": [{"a": false}, {"a": 0}], + "valid": true + }, + { + "description": "{\"a\": true} and {\"a\": 1} are unique", + "data": [{"a": true}, {"a": 1}], + "valid": true + } + ] + }, + { + "description": "uniqueItems with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is not valid", + "data": [false, true, "foo", "foo"], + "valid": false + }, + { + "description": "non-unique array extended from [true, false] is not valid", + "data": [true, false, "foo", "foo"], + "valid": false + } + ] + }, + { + "description": "uniqueItems with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": true, + "items": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is not valid", + "data": [false, false], + "valid": false + }, + { + "description": "[true, true] from items array is not valid", + "data": [true, true], + "valid": false + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + }, + { + "description": "uniqueItems=false validation", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "uniqueItems": false + }, + "tests": [ + { + "description": "unique array of integers is valid", + "data": [1, 2], + "valid": true + }, + { + "description": "non-unique array of integers is valid", + "data": [1, 1], + "valid": true + }, + { + "description": "numbers are unique if mathematically unequal", + "data": [1.0, 1.00, 1], + "valid": true + }, + { + "description": "false is not equal to zero", + "data": [0, false], + "valid": true + }, + { + "description": "true is not equal to one", + "data": [1, true], + "valid": true + }, + { + "description": "unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "baz"}], + "valid": true + }, + { + "description": "non-unique array of objects is valid", + "data": [{"foo": "bar"}, {"foo": "bar"}], + "valid": true + }, + { + "description": "unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : false}}} + ], + "valid": true + }, + { + "description": "non-unique array of nested objects is valid", + "data": [ + {"foo": {"bar" : {"baz" : true}}}, + {"foo": {"bar" : {"baz" : true}}} + ], + "valid": true + }, + { + "description": "unique array of arrays is valid", + "data": [["foo"], ["bar"]], + "valid": true + }, + { + "description": "non-unique array of arrays is valid", + "data": [["foo"], ["foo"]], + "valid": true + }, + { + "description": "1 and true are unique", + "data": [1, true], + "valid": true + }, + { + "description": "0 and false are unique", + "data": [0, false], + "valid": true + }, + { + "description": "unique heterogeneous types are valid", + "data": [{}, [1], true, null, 1], + "valid": true + }, + { + "description": "non-unique heterogeneous types are valid", + "data": [{}, [1], true, null, {}, 1], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "unique array extended from [false, true] is valid", + "data": [false, true, "foo", "bar"], + "valid": true + }, + { + "description": "unique array extended from [true, false] is valid", + "data": [true, false, "foo", "bar"], + "valid": true + }, + { + "description": "non-unique array extended from [false, true] is valid", + "data": [false, true, "foo", "foo"], + "valid": true + }, + { + "description": "non-unique array extended from [true, false] is valid", + "data": [true, false, "foo", "foo"], + "valid": true + } + ] + }, + { + "description": "uniqueItems=false with an array of items and additionalItems=false", + "schema": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "prefixItems": [{"type": "boolean"}, {"type": "boolean"}], + "uniqueItems": false, + "items": false + }, + "tests": [ + { + "description": "[false, true] from items array is valid", + "data": [false, true], + "valid": true + }, + { + "description": "[true, false] from items array is valid", + "data": [true, false], + "valid": true + }, + { + "description": "[false, false] from items array is valid", + "data": [false, false], + "valid": true + }, + { + "description": "[true, true] from items array is valid", + "data": [true, true], + "valid": true + }, + { + "description": "extra items are invalid even if unique", + "data": [false, true, null], + "valid": false + } + ] + } +] diff --git a/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/vocabulary.json b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/vocabulary.json new file mode 100644 index 00000000..1acb96a9 --- /dev/null +++ b/vendor/JSON-Schema-Test-Suite/tests/draft2020-12/vocabulary.json @@ -0,0 +1,57 @@ +[ + { + "description": "schema that uses custom metaschema with with no validation vocabulary", + "schema": { + "$id": "https://schema/using/no/validation", + "$schema": "http://localhost:1234/draft2020-12/metaschema-no-validation.json", + "properties": { + "badProperty": false, + "numberProperty": { + "minimum": 10 + } + } + }, + "tests": [ + { + "description": "applicator vocabulary still works", + "data": { + "badProperty": "this property should not exist" + }, + "valid": false + }, + { + "description": "no validation: valid number", + "data": { + "numberProperty": 20 + }, + "valid": true + }, + { + "description": "no validation: invalid number, but it still validates", + "data": { + "numberProperty": 1 + }, + "valid": true + } + ] + }, + { + "description": "ignore unrecognized optional vocabulary", + "schema": { + "$schema": "http://localhost:1234/draft2020-12/metaschema-optional-vocabulary.json", + "type": "number" + }, + "tests": [ + { + "description": "string value", + "data": "foobar", + "valid": false + }, + { + "description": "number value", + "data": 20, + "valid": true + } + ] + } +]