Complete rewrite of the validator - aiming a 2.0-release

Schema a now "parsed" into C++-validator-objects in a first
step and then validation takes place with these objects.

Errors are now handled via a user-provided error-handler
allowing the user to collect all errors at once or bail out
when a certain threshold is reached. Fixes #36 and #8.

One (sub-)schema can now be referenced with different URIs. Fixes #9

JSON schema draft 7 is now supported. Fixes #35
This commit is contained in:
Patrick Boettcher 2018-05-22 18:02:52 +02:00
parent 2785ce0c64
commit 7beb40bc61
88 changed files with 3880 additions and 1507 deletions

View File

@ -7,9 +7,11 @@ AllowShortFunctionsOnASingleLine: Inline
BreakBeforeBraces: Linux
ColumnLimit: 0
ConstructorInitializerAllOnOneLineOrOnePerLine: true
IndentWidth: 2
ObjCBlockIndentWidth: 2
IndentWidth: 4
IndentPPDirectives: AfterHash
ObjCBlockIndentWidth: 0
SpaceAfterCStyleCast: true
TabWidth: 2
TabWidth: 4
AccessModifierOffset: -4
UseTab: ForIndentation
...

View File

@ -60,7 +60,7 @@ script:
- $CXX --version
# put json.hpp to nlohmann
- mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.1.2/json.hpp -O nlohmann/json.hpp
- mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.5.0/json.hpp -O nlohmann/json.hpp
# compile and execute unit tests
- mkdir -p build && cd build

View File

@ -32,7 +32,7 @@ endif()
# and one for the validator
add_library(json-schema-validator
src/json-schema-draft4.json.cpp
src/json-schema-draft7.json.cpp
src/json-uri.cpp
src/json-validator.cpp)
@ -95,8 +95,13 @@ if (BUILD_EXAMPLES)
# simple json-schema-validator-executable
add_executable(json-schema-validate app/json-schema-validate.cpp)
target_link_libraries(json-schema-validate json-schema-validator)
add_executable(readme app/readme.cpp)
target_link_libraries(readme json-schema-validator)
endif()
#add_subdirectory(ng)
if (BUILD_TESTS)
# test-zone
enable_testing()

134
README.md
View File

@ -1,53 +1,68 @@
[![Build Status](https://travis-ci.org/pboettch/json-schema-validator.svg?branch=master)](https://travis-ci.org/pboettch/json-schema-validator)
# Modern C++ JSON schema validator
# JSON schema validator for JSON for Modern C++
# What is it?
This is a C++ library for validating JSON documents based on a
[JSON Schema](http://json-schema.org/) which itself should validate with
[draft-4 of JSON Schema Validation](http://json-schema.org/schema).
[draft-7 of JSON Schema Validation](http://json-schema.org/schema).
First a disclaimer: *It is work in progress and
contributions or hints or discussions are welcome.*
contributions or hints or discussions are welcome.* Even though a 2.0.0 release is immenent.
Niels Lohmann et al develop a great JSON parser for C++ called [JSON for Modern
C++](https://github.com/nlohmann/json). This validator is based on this
library, hence the name.
The name is for the moment purely marketing, because there is, IMHO, not so much
modern C++ inside. There is plenty of space to make it more modern.
External documentation is missing as well. However the API of the validator
will be rather simple.
is rather simple.
# New in version 2
Although significant changes have been coorporate to the 2 version
(a complete rewrite) the API is compatible with the 1.0.0 release. Except for
the namespace which is now `nlohmann::json_schema.
Version **2** supports JSON schema draft 7, whereas 1 was supporting draft 4
only. Please update your schemas.
The primary change in 2 is the way a schema is used. While in version 1 the schema was
kept as a JSON-document and used again and again during validation, in versin 2 the schema
is parsed into compiled C++ objects which are then used during validation. There are surely
still optimizations to be done, but validation speed has improved by factor 100
or more.
In JSON-schema one sub-schema can be
# Design goals
The main goal of this validator is to produce *human-comprehensible* error
messages if a JSON-document/instance does not comply with its schema. This is
done with exceptions thrown at the users with a helpful message telling what's
wrong with the document while validating.
messages if a JSON-document/instance does not comply to its schema.
By default this is done with exceptions thrown at the users with a helpful
message telling what's wrong with the document while validating.
With **2.0.0** the user can passed a `json_scheam::basic_error_handler` derived object
along with the instance to validate to receive a each time a validation error occurs
and decice what to do (throwing, counting, collecting).
Another goal was to use Niels Lohmann's JSON-library. This is why the validator
lives in his namespace.
# Weaknesses
Schema-reference resolution is not recursivity-proven: If there is a nested
cross-schema reference, it will not stop. (Though I haven't tested it)
Numerical validation uses `int64_t`, `uint64_t` or `double`, depending on if
Numerical validation uses nlohmann integer, unsigned and floating point types, depending on if
the schema type is "integer" or "number". Bignum (i.e. arbitrary precision and
range) is not supported at this time.
Unsigned integer validation will only take place if the following two conditions are true:
- The nlohmann `type()` of the json object under validation is `nlohmann::json::value_t::number_unsigned`
- The schema specifies a numerical minimum greater than or equal to 0
Currently JSON-URI with "plain name fragments" are not supported. So referring to an URI
with `$ref: "file.json#plain"` will not work.
# How to use
The current state of the build-system needs at least version **3.1.1** of NLohmann's
The current state of the build-system needs at least version **3.5.0** of NLohmann's
JSON library. It is looking for the `json.hpp` within a `nlohmann/`-path.
When build the library you need to provide the path to the directory where the include-file
@ -66,7 +81,7 @@ cmake .. \
-DNLOHMANN_JSON_DIR=<path/to/>nlohmann/json.hpp \
-DJSON_SCHEMA_TEST_SUITE_PATH=<path/to/JSON-Schema-test-suite> # optional
make # install
ctest # if test-suite has been given
ctest # run unit, non-regression and test-suite tests
```
### As a subdirectory from within
@ -93,7 +108,6 @@ In your initial call to cmake simply add:
```bash
cmake -DBUILD_SHARED_LIBS=ON
```
## Code
See also `app/json-schema-validate.cpp`.
@ -104,13 +118,12 @@ See also `app/json-schema-validate.cpp`.
#include "json-schema.hpp"
using nlohmann::json;
using nlohmann::json_uri;
using nlohmann::json_schema_draft4::json_validator;
using nlohmann::json_schema::json_validator;
// The schema is defined based upon a string literal
static json person_schema = R"(
{
"$schema": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "A person",
"properties": {
"name": {
@ -137,55 +150,74 @@ static json person_schema = R"(
static json bad_person = {{"age", 42}};
static json good_person = {{"name", "Albert"}, {"age", 42}};
int main(){
int main()
{
/* json-parse the schema */
/* json-parse the schema */
json_validator validator; // create validator
json_validator validator; // create validator
try {
validator.set_root_schema(person_schema); // insert root-schema
} catch (const std::exception &e) {
std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n";
return EXIT_FAILURE;
}
try {
validator.set_root_schema(person_schema); // insert root-schema
} catch (const std::exception &e) {
std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n";
return EXIT_FAILURE;
}
/* json-parse the people - API of 1.0.0, default throwing error handler */
/* json-parse the people */
for (auto &person : {bad_person, good_person}) {
std::cout << "About to validate this person:\n"
<< std::setw(2) << person << std::endl;
try {
validator.validate(person); // validate the document
std::cout << "Validation succeeded\n";
} catch (const std::exception &e) {
std::cerr << "Validation failed, here is why: " << e.what() << "\n";
}
}
for (auto &person : {bad_person, good_person})
{
std::cout << "About to validate this person:\n" << std::setw(2) << person << std::endl;
try {
validator.validate(person); // validate the document
std::cout << "Validation succeeded\n";
} catch (const std::exception &e) {
std::cerr << "Validation failed, here is why: " << e.what() << "\n";
}
}
return EXIT_SUCCESS;
/* json-parse the people - with custom error handler */
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
{
void error(const std::string &path, const json &instance, const std::string &message) override
{
nlohmann::json_schema::basic_error_handler::error(path, instance, message);
std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n";
}
};
for (auto &person : {bad_person, good_person}) {
std::cout << "About to validate this person:\n"
<< std::setw(2) << person << std::endl;
custom_error_handler err;
validator.validate(person, err); // validate the document - uses the default throwing error-handler
if (err)
std::cerr << "Validation failed\n";
else
std::cout << "Validation succeeded\n";
}
return EXIT_SUCCESS;
}
```
# Compliance
There is an application which can be used for testing the validator with the
[JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite).
In order to simplify the testing, the test-suite is included in the repository.
If you have cloned this repository providing a path the repository-root via the
cmake-variable `JSON_SCHEMA_TEST_SUITE_PATH` will enable the test-target(s).
All required tests are **OK**.
**12** optional tests of **305** total (required + optional) tests are failing:
- 10 of them are `format`-strings which are not supported.
- big numbers are not working (2)
# Additional features
## Default values
The goal is to create an empty document, based on schema-defined
default-values, recursively populated.

View File

@ -1,27 +1,10 @@
/*
* Modern C++ JSON schema validator
* JSON schema validator for JSON for modern C++
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
* SPDX-License-Identifier: MIT
*
* 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.
*/
#include <json-schema.hpp>
@ -30,7 +13,7 @@
using nlohmann::json;
using nlohmann::json_uri;
using nlohmann::json_schema_draft4::json_validator;
using nlohmann::json_schema::json_validator;
static void usage(const char *name)
{
@ -47,9 +30,10 @@ static void usage(const char *name)
static void loader(const json_uri &uri, json &schema)
{
std::fstream lf("." + uri.path());
std::string filename = "./" + uri.path();
std::fstream lf(filename);
if (!lf.good())
throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path());
throw std::invalid_argument("could not open " + uri.url() + " tried with " + filename);
try {
lf >> schema;
@ -58,6 +42,15 @@ static void loader(const json_uri &uri, json &schema)
}
}
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
{
void error(const std::string &path, const json &instance, const std::string &message) override
{
nlohmann::json_schema::basic_error_handler::error(path, instance, message);
std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n";
}
};
int main(int argc, char *argv[])
{
if (argc != 2)
@ -95,10 +88,16 @@ int main(int argc, char *argv[])
try {
std::cin >> document;
validator.validate(document);
} catch (std::exception &e) {
std::cerr << "json parsing failed: " << e.what() << " at offset: " << std::cin.tellg() << "\n";
return EXIT_FAILURE;
}
custom_error_handler err;
validator.validate(document, err);
if (err) {
std::cerr << "schema validation failed\n";
std::cerr << e.what() << " at offset: " << std::cin.tellg() << "\n";
return EXIT_FAILURE;
}

90
app/readme.cpp Normal file
View File

@ -0,0 +1,90 @@
#include <iostream>
#include <iomanip>
#include "json-schema.hpp"
using nlohmann::json;
using nlohmann::json_schema::json_validator;
// The schema is defined based upon a string literal
static json person_schema = R"(
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "A person",
"properties": {
"name": {
"description": "Name",
"type": "string"
},
"age": {
"description": "Age of the person",
"type": "number",
"minimum": 2,
"maximum": 200
}
},
"required": [
"name",
"age"
],
"type": "object"
}
)"_json;
// The people are defined with brace initialization
static json bad_person = {{"age", 42}};
static json good_person = {{"name", "Albert"}, {"age", 42}};
int main()
{
/* json-parse the schema */
json_validator validator; // create validator
try {
validator.set_root_schema(person_schema); // insert root-schema
} catch (const std::exception &e) {
std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n";
return EXIT_FAILURE;
}
/* json-parse the people - API of 1.0.0, default throwing error handler */
for (auto &person : {bad_person, good_person}) {
std::cout << "About to validate this person:\n"
<< std::setw(2) << person << std::endl;
try {
validator.validate(person); // validate the document
std::cout << "Validation succeeded\n";
} catch (const std::exception &e) {
std::cerr << "Validation failed, here is why: " << e.what() << "\n";
}
}
/* json-parse the people - with custom error handler */
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
{
void error(const std::string &path, const json &instance, const std::string &message) override
{
nlohmann::json_schema::basic_error_handler::error(path, instance, message);
std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n";
}
};
for (auto &person : {bad_person, good_person}) {
std::cout << "About to validate this person:\n"
<< std::setw(2) << person << std::endl;
custom_error_handler err;
validator.validate(person, err); // validate the document - uses the default throwing error-handler
if (err)
std::cerr << "Validation failed\n";
else
std::cout << "Validation succeeded\n";
}
return EXIT_SUCCESS;
}

View File

@ -1,109 +1,114 @@
#include <json-schema.hpp>
namespace nlohmann
{
namespace json_schema_draft4
{
json draft4_schema_builtin = R"( {
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"positiveInteger": {
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"minItems": 1,
"uniqueItems": true
"uniqueItems": true,
"default": []
}
},
"type": "object",
"type": ["object", "boolean"],
"properties": {
"id": {
"$id": {
"type": "string",
"format": "uri"
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$comment": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"default": true,
"readOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
"type": "number"
},
"maxLength": { "$ref": "#/definitions/positiveInteger" },
"minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": {}
"default": true
},
"maxItems": { "$ref": "#/definitions/positiveInteger" },
"minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": { "$ref": "#/definitions/positiveInteger" },
"minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": {
"anyOf": [
{ "type": "boolean" },
{ "$ref": "#" }
],
"default": {}
},
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
@ -117,6 +122,7 @@ json draft4_schema_builtin = R"( {
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
@ -128,8 +134,11 @@ json draft4_schema_builtin = R"( {
]
}
},
"propertyNames": { "$ref": "#" },
"const": true,
"enum": {
"type": "array",
"items": true,
"minItems": 1,
"uniqueItems": true
},
@ -144,17 +153,16 @@ json draft4_schema_builtin = R"( {
}
]
},
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": {"$ref": "#"},
"then": {"$ref": "#"},
"else": {"$ref": "#"},
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"dependencies": {
"exclusiveMaximum": [ "maximum" ],
"exclusiveMinimum": [ "minimum" ]
},
"default": {}
} )"_json;
}
"default": true
}

View File

@ -0,0 +1,185 @@
/*
* JSON schema validator for JSON for modern C++
*
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* SPDX-License-Identifier: MIT
*
*/
#include <nlohmann/json.hpp>
namespace nlohmann
{
namespace json_schema
{
json draft7_schema_builtin = R"( {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
},
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$comment": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": true,
"readOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": true
},
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"const": true,
"enum": {
"type": "array",
"items": true,
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": { "$ref": "#" },
"then": { "$ref": "#" },
"else": { "$ref": "#" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"default": true
} )"_json;
}
} // namespace nlohmann

View File

@ -1,91 +1,40 @@
/*
* Modern C++ JSON schema validator
* JSON schema validator for JSON for modern C++
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
* SPDX-License-Identifier: MIT
*
* 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.
*/
#ifndef NLOHMANN_JSON_SCHEMA_HPP__
#define NLOHMANN_JSON_SCHEMA_HPP__
#ifdef _WIN32
# if defined(JSON_SCHEMA_VALIDATOR_EXPORTS)
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllexport)
# elif defined(JSON_SCHEMA_VALIDATOR_IMPORTS)
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllimport)
# else
# define JSON_SCHEMA_VALIDATOR_API
# endif
# if defined(JSON_SCHEMA_VALIDATOR_EXPORTS)
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllexport)
# elif defined(JSON_SCHEMA_VALIDATOR_IMPORTS)
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllimport)
# else
# define JSON_SCHEMA_VALIDATOR_API
# endif
#else
# define JSON_SCHEMA_VALIDATOR_API
# define JSON_SCHEMA_VALIDATOR_API
#endif
#include <nlohmann/json.hpp>
#ifdef NLOHMANN_JSON_VERSION_MAJOR
# if NLOHMANN_JSON_VERSION_MAJOR < 3 || NLOHMANN_JSON_VERSION_MINOR < 5 || NLOHMANN_JSON_VERSION_PATCH < 0
# error "Please use this library with NLohmann's JSON version 3.5.0 or higher"
# endif
#else
# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.5.0"
#endif
// make yourself a home - welcome to nlohmann's namespace
namespace nlohmann
{
// a class representing a JSON-pointer RFC6901
//
// examples of JSON pointers
//
// # - root of the current document
// #item - refers to the object which is identified ("id") by `item`
// in the current document
// #/path/to/element
// - refers to the element in /path/to from the root-document
//
//
// The json_pointer-class stores everything in a string, which might seem bizarre
// as parsing is done from a string to a string, but from_string() is also
// doing some formatting.
//
// TODO
// ~ and % - codec
// needs testing and clarification regarding the '#' at the beginning
class local_json_pointer
{
std::string str_;
void from_string(const std::string &r);
public:
local_json_pointer(const std::string &s = "")
{
from_string(s);
}
void append(const std::string &elem)
{
str_.append(elem);
}
const std::string &to_string() const
{
return str_;
}
};
// A class representing a JSON-URI for schemas derived from
// section 8 of JSON Schema: A Media Type for Describing JSON Documents
// draft-wright-json-schema-00
@ -102,32 +51,32 @@ class JSON_SCHEMA_VALIDATOR_API json_uri
std::string proto_;
std::string hostname_;
std::string path_;
local_json_pointer pointer_;
nlohmann::json::json_pointer pointer_;
protected:
// decodes a JSON uri and replaces all or part of the currently stored values
void from_string(const std::string &uri);
void update(const std::string &uri);
std::tuple<std::string, std::string, std::string, std::string, std::string> tie() const
{
return std::tie(urn_, proto_, hostname_, path_, pointer_.to_string());
return std::tie(urn_, proto_, hostname_, path_, pointer_);
}
public:
json_uri(const std::string &uri)
{
from_string(uri);
update(uri);
}
const std::string protocol() const { return proto_; }
const std::string hostname() const { return hostname_; }
const std::string path() const { return path_; }
const local_json_pointer pointer() const { return pointer_; }
const std::string url() const;
const nlohmann::json::json_pointer pointer() const { return pointer_; }
const std::string url() const { return location(); }
const std::string location() const;
// decode and encode strings for ~ and % escape sequences
static std::string unescape(const std::string &);
static std::string escape(const std::string &);
// create a new json_uri based in this one and the given uri
@ -135,7 +84,7 @@ public:
json_uri derive(const std::string &uri) const
{
json_uri u = *this;
u.from_string(uri);
u.update(uri);
return u;
}
@ -143,7 +92,7 @@ public:
json_uri append(const std::string &field) const
{
json_uri u = *this;
u.pointer_.append("/" + field);
u.pointer_ = nlohmann::json::json_pointer(u.pointer_.to_string() + '/' + escape(field));
return u;
}
@ -162,42 +111,47 @@ public:
friend std::ostream &operator<<(std::ostream &os, const json_uri &u);
};
namespace json_schema_draft4
namespace json_schema
{
extern json draft4_schema_builtin;
extern json draft7_schema_builtin;
class basic_error_handler
{
bool error_{false};
public:
virtual void error(const std::string & /*path*/, const json & /* instance */, const std::string & /*message*/)
{
error_ = true;
}
void reset() { error_ = false; }
operator bool() const { return error_; }
};
class root_schema;
class JSON_SCHEMA_VALIDATOR_API json_validator
{
std::vector<std::shared_ptr<json>> schema_store_;
std::shared_ptr<json> root_schema_;
std::function<void(const json_uri &, json &)> schema_loader_ = nullptr;
std::function<void(const std::string &, const std::string &)> format_check_ = nullptr;
std::map<json_uri, const json *> schema_refs_;
void validate(const json &instance, const json &schema_, const std::string &name);
void validate_array(const json &instance, const json &schema_, const std::string &name);
void validate_object(const json &instance, const json &schema_, const std::string &name);
void validate_string(const json &instance, const json &schema, const std::string &name);
void insert_schema(const json &input, const json_uri &id);
std::unique_ptr<root_schema> root_;
public:
json_validator(std::function<void(const json_uri &, json &)> loader = nullptr,
std::function<void(const std::string &, const std::string &)> format = nullptr)
: schema_loader_(loader), format_check_(format)
{
}
std::function<void(const std::string &, const std::string &)> format = nullptr);
~json_validator();
// insert and set a root-schema
// insert and set thea root-schema
void set_root_schema(const json &);
// validate a json-document based on the root-schema
void validate(const json &instance);
void validate(const json &);
// validate a json-document based on the root-schema with a custom error-handler
void validate(const json &, basic_error_handler &);
};
} // json_schema_draft4
} // nlohmann
} // namespace json_schema
} // namespace nlohmann
#endif /* NLOHMANN_JSON_SCHEMA_HPP__ */

View File

@ -1,27 +1,10 @@
/*
* Modern C++ JSON schema validator
* JSON schema validator for JSON for modern C++
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
* SPDX-License-Identifier: MIT
*
* 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.
*/
#include "json-schema.hpp"
@ -30,76 +13,92 @@
namespace nlohmann
{
void local_json_pointer::from_string(const std::string &r)
void json_uri::update(const std::string &uri)
{
str_ = "#";
std::string pointer = ""; // default pointer is document-root
if (r.size() == 0)
return;
if (r[0] != '#')
throw std::invalid_argument("not a valid JSON pointer - missing # at the beginning");
if (r.size() == 1)
return;
std::size_t pos = 1;
do {
std::size_t next = r.find('/', pos + 1);
str_.append(r.substr(pos, next - pos));
pos = next;
} while (pos != std::string::npos);
}
void json_uri::from_string(const std::string &uri)
{
// if it is an urn take it as it is - maybe there is more to be done
if (uri.find("urn:") == 0) {
urn_ = uri;
return;
}
std::string pointer = "#"; // default pointer is the root
// first split the URI into URL and JSON-pointer
// first split the URI into location and pointer
auto pointer_separator = uri.find('#');
if (pointer_separator != std::string::npos) // and extract the JSON-pointer-string if found
pointer = uri.substr(pointer_separator);
if (pointer_separator != std::string::npos) { // and extract the pointer-string if found
pointer = uri.substr(pointer_separator + 1); // remove #
// the rest is an URL
std::string url = uri.substr(0, pointer_separator);
if (url.size()) { // if an URL is part of the URI
// unescape %-values IOW, decode JSON-URI-formatted JSON-pointer
std::size_t pos = pointer.size() - 1;
do {
pos = pointer.rfind('%', pos);
if (pos == std::string::npos)
break;
std::size_t pos = 0;
auto proto = url.find("://", pos);
if (proto != std::string::npos) { // extract the protocol
proto_ = url.substr(pos, proto - pos);
pos = 3 + proto; // 3 == "://"
auto hostname = url.find("/", pos);
if (hostname != std::string::npos) { // and the hostname (no proto without hostname)
hostname_ = url.substr(pos, hostname - pos);
pos = hostname;
if (pos >= pointer.size() - 2) {
pos--;
continue;
}
}
// the rest is the path
auto path = url.substr(pos);
if (path[0] == '/') // if it starts with a / it is root-path
path_ = path;
else // otherwise it is a subfolder
path_.append(path);
std::string hex = pointer.substr(pos + 1, 2);
char ascii = (char) std::strtoul(hex.c_str(), nullptr, 16);
pointer.replace(pos, 3, 1, ascii);
pointer_ = local_json_pointer("");
pos--;
} while (1);
}
if (pointer.size() > 0)
pointer_ = pointer;
auto location = uri.substr(0, pointer_separator);
if (location.size()) { // a location part has been found
pointer_ = ""_json_pointer; // if a location is given, the pointer is emptied
// if it is an URN take it as it is
if (location.find("urn:") == 0) {
urn_ = location;
// and clear URL members
proto_ = "";
hostname_ = "";
path_ = "";
} else { // it is an URL
// split URL in protocol, hostname and path
std::size_t pos = 0;
auto proto = location.find("://", pos);
if (proto != std::string::npos) { // extract the protocol
urn_ = ""; // clear URN-member if URL is parsed
proto_ = location.substr(pos, proto - pos);
pos = 3 + proto; // 3 == "://"
auto hostname = location.find("/", pos);
if (hostname != std::string::npos) { // and the hostname (no proto without hostname)
hostname_ = location.substr(pos, hostname - pos);
pos = hostname;
}
}
auto path = location.substr(pos);
// URNs cannot of have paths
if (urn_.size() && path.size())
throw std::invalid_argument("Cannot add a path (" + path + ") to an URN URI (" + urn_ + ")");
if (path[0] == '/') // if it starts with a / it is root-path
path_ = path;
else if (pos == 0) { // the URL contained only a path and the current path has no / at the end, strip last element until / and append
auto last_slash = path_.rfind('/');
path_ = path_.substr(0, last_slash) + '/' + path;
} else // otherwise it is a subfolder
path_.append(path);
}
}
pointer_ = nlohmann::json::json_pointer(pointer);
}
const std::string json_uri::url() const
const std::string json_uri::location() const
{
if (urn_.size())
return urn_;
std::stringstream s;
if (proto_.size() > 0)
@ -115,9 +114,7 @@ std::string json_uri::to_string() const
{
std::stringstream s;
s << urn_
<< url()
<< pointer_.to_string();
s << location() << " # " << pointer_.to_string();
return s.str();
}
@ -127,48 +124,11 @@ std::ostream &operator<<(std::ostream &os, const json_uri &u)
return os << u.to_string();
}
std::string json_uri::unescape(const std::string &src)
{
std::string l = src;
std::size_t pos = src.size() - 1;
do {
pos = l.rfind('~', pos);
if (pos == std::string::npos)
break;
if (pos < l.size() - 1) {
switch (l[pos + 1]) {
case '0':
l.replace(pos, 2, "~");
break;
case '1':
l.replace(pos, 2, "/");
break;
default:
break;
}
}
if (pos == 0)
break;
pos--;
} while (pos != std::string::npos);
// TODO - percent handling
return l;
}
std::string json_uri::escape(const std::string &src)
{
std::vector<std::pair<std::string, std::string>> chars = {
{"~", "~0"},
{"/", "~1"},
{"%", "%25"}};
{"/", "~1"}};
std::string l = src;
@ -186,4 +146,4 @@ std::string json_uri::escape(const std::string &src)
return l;
}
} // nlohmann
} // namespace nlohmann

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,8 @@ function(add_test_simple_schema name schema instance)
COMMAND ${PIPE_IN_TEST_SCRIPT}
$<TARGET_FILE:json-schema-validate>
${schema}
${instance})
${instance}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
endfunction()
file(GLOB TEST_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/*)
@ -17,3 +18,8 @@ foreach(DIR ${TEST_DIRS})
add_subdirectory(${DIR})
endif()
endforeach()
add_executable(uri uri.cpp)
target_link_libraries(uri json-schema-validator)
add_test(NAME uri COMMAND uri)

View File

@ -1,9 +1,11 @@
set(JSON_SCHEMA_TEST_PREFIX "JSON-Suite" CACHE STRING "prefix for JSON-tests added to ctest")
set(DRAFT "draft7")
# find schema-test-suite
find_path(JSON_SCHEMA_TEST_SUITE_PATH
NAMES
tests/draft4)
tests/${DRAFT})
if (NOT JSON_SCHEMA_TEST_SUITE_PATH)
message(STATUS "Set JSON_SCHEMA_TEST_SUITE_PATH to a path in which JSON-Schema-Test-Suite is located (github.com/json-schema-org/JSON-Schema-Test-Suite). Using internal test-suite which might be out of date.")
@ -21,7 +23,7 @@ if(JSON_SCHEMA_TEST_SUITE_PATH)
option(JSON_SCHEMA_ENABLE_OPTIONAL_TESTS "Enable optional tests of the JSONSchema Test Suite" ON)
# create tests foreach test-file
file(GLOB TEST_FILES ${JSON_SCHEMA_TEST_SUITE_PATH}/tests/draft4/*.json)
file(GLOB TEST_FILES ${JSON_SCHEMA_TEST_SUITE_PATH}/tests/${DRAFT}/*.json)
foreach(TEST_FILE ${TEST_FILES})
get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE)
@ -30,7 +32,7 @@ if(JSON_SCHEMA_TEST_SUITE_PATH)
endforeach()
if (JSON_SCHEMA_ENABLE_OPTIONAL_TESTS)
file(GLOB OPT_TEST_FILES ${JSON_SCHEMA_TEST_SUITE_PATH}/tests/draft4/optional/*.json)
file(GLOB OPT_TEST_FILES ${JSON_SCHEMA_TEST_SUITE_PATH}/tests/${DRAFT}/optional/*.json)
foreach(TEST_FILE ${OPT_TEST_FILES})
get_filename_component(TEST_NAME ${TEST_FILE} NAME_WE)
@ -38,18 +40,13 @@ if(JSON_SCHEMA_TEST_SUITE_PATH)
COMMAND ${PIPE_IN_TEST_SCRIPT} $<TARGET_FILE:json-schema-test> ${TEST_FILE})
endforeach()
# XXX Unfortunately URLs are not very well handled yet, accept those tests which fail
set_tests_properties(JSON-Suite::ref
JSON-Suite::refRemote
PROPERTIES
WILL_FAIL ON)
# some optional tests will fail as well.
set_tests_properties(JSON-Suite::Optional::bignum
JSON-Suite::Optional::ecmascript-regex
JSON-Suite::Optional::format
PROPERTIES
WILL_FAIL ON)
set_tests_properties(
JSON-Suite::Optional::bignum
JSON-Suite::Optional::content
JSON-Suite::Optional::zeroTerminatedFloats
PROPERTIES
WILL_FAIL ON)
endif()
else()
endif()

View File

@ -1,37 +1,20 @@
/*
* Modern C++ JSON schema validator
* JSON schema validator for JSON for modern C++
*
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
* SPDX-License-Identifier: MIT
*
* 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.
*/
#include "json-schema.hpp"
#include <fstream>
#include <regex>
#include <iostream>
#include <regex>
using nlohmann::json;
using nlohmann::json_uri;
using nlohmann::json_schema_draft4::json_validator;
using nlohmann::json_schema::json_validator;
static void format_check(const std::string &format, const std::string &value)
{
@ -61,8 +44,8 @@ static void format_check(const std::string &format, const std::string &value)
static void loader(const json_uri &uri, json &schema)
{
if (uri.to_string() == "http://json-schema.org/draft-04/schema#") {
schema = nlohmann::json_schema_draft4::draft4_schema_builtin;
if (uri.location() == "http://json-schema.org/draft-07/schema") {
schema = nlohmann::json_schema::draft7_schema_builtin;
return;
}

View File

@ -0,0 +1,3 @@
{
"type": "integer"
}

View File

@ -0,0 +1,3 @@
{
"type": "integer"
}

View File

@ -0,0 +1,11 @@
{
"definitions": {
"orNull": {
"anyOf": [
{"type": "null"},
{"$ref": "#"}
]
}
},
"type": "string"
}

View File

@ -0,0 +1,8 @@
{
"integer": {
"type": "integer"
},
"refToInteger": {
"$ref": "#/integer"
}
}

View File

@ -1,73 +0,0 @@
[
{
"description": "maximum validation",
"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 (explicit false exclusivity)",
"schema": {"maximum": 3.0, "exclusiveMaximum": false},
"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": "exclusiveMaximum validation",
"schema": {
"maximum": 3.0,
"exclusiveMaximum": true
},
"tests": [
{
"description": "below the maximum is still valid",
"data": 2.2,
"valid": true
},
{
"description": "boundary point is invalid",
"data": 3.0,
"valid": false
}
]
}
]

View File

@ -1,73 +0,0 @@
[
{
"description": "minimum validation",
"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 (explicit false exclusivity)",
"schema": {"minimum": 1.1, "exclusiveMinimum": false},
"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": "exclusiveMinimum validation",
"schema": {
"minimum": 1.1,
"exclusiveMinimum": true
},
"tests": [
{
"description": "above the minimum is still valid",
"data": 1.2,
"valid": true
},
{
"description": "boundary point is invalid",
"data": 1.1,
"valid": false
}
]
}
]

View File

@ -1,223 +0,0 @@
[
{
"description": "validation of date-time strings",
"schema": {"format": "date-time"},
"tests": [
{
"description": "a valid date-time string",
"data": "1963-06-19T08:30:06.283185Z",
"valid": true
},
{
"description": "an invalid date-time string",
"data": "06/19/1963 08:30:06 PST",
"valid": false
},
{
"description": "only RFC3339 not all of ISO 8601 are valid",
"data": "2013-350T01:01:01",
"valid": false
}
]
},
{
"description": "validation of URIs",
"schema": {"format": "uri"},
"tests": [
{
"description": "a valid URL with anchor tag",
"data": "http://foo.bar/?baz=qux#quux",
"valid": true
},
{
"description": "a valid URL with anchor tag and parantheses",
"data": "http://foo.com/blah_(wikipedia)_blah#cite-1",
"valid": true
},
{
"description": "a valid URL with URL-encoded stuff",
"data": "http://foo.bar/?q=Test%20URL-encoded%20stuff",
"valid": true
},
{
"description": "a valid puny-coded URL ",
"data": "http://xn--nw2a.xn--j6w193g/",
"valid": true
},
{
"description": "a valid URL with many special characters",
"data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com",
"valid": true
},
{
"description": "a valid URL based on IPv4",
"data": "http://223.255.255.254",
"valid": true
},
{
"description": "a valid URL with ftp scheme",
"data": "ftp://ftp.is.co.za/rfc/rfc1808.txt",
"valid": true
},
{
"description": "a valid URL for a simple text file",
"data": "http://www.ietf.org/rfc/rfc2396.txt",
"valid": true
},
{
"description": "a valid URL ",
"data": "ldap://[2001:db8::7]/c=GB?objectClass?one",
"valid": true
},
{
"description": "a valid mailto URI",
"data": "mailto:John.Doe@example.com",
"valid": true
},
{
"description": "a valid newsgroup URI",
"data": "news:comp.infosystems.www.servers.unix",
"valid": true
},
{
"description": "a valid tel URI",
"data": "tel:+1-816-555-1212",
"valid": true
},
{
"description": "a valid URN",
"data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
"valid": true
},
{
"description": "an invalid protocol-relative URI Reference",
"data": "//foo.bar/?baz=qux#quux",
"valid": false
},
{
"description": "an invalid relative URI Reference",
"data": "/abc",
"valid": false
},
{
"description": "an invalid URI",
"data": "\\\\WINDOWS\\fileshare",
"valid": false
},
{
"description": "an invalid URI though valid URI reference",
"data": "abc",
"valid": false
},
{
"description": "an invalid URI with spaces",
"data": "http:// shouldfail.com",
"valid": false
},
{
"description": "an invalid URI with spaces and missing scheme",
"data": ":// should fail",
"valid": false
}
]
},
{
"description": "validation of e-mail addresses",
"schema": {"format": "email"},
"tests": [
{
"description": "a valid e-mail address",
"data": "joe.bloggs@example.com",
"valid": true
},
{
"description": "an invalid e-mail address",
"data": "2962",
"valid": false
}
]
},
{
"description": "validation of IP addresses",
"schema": {"format": "ipv4"},
"tests": [
{
"description": "a valid IP address",
"data": "192.168.0.1",
"valid": true
},
{
"description": "an IP address with too many components",
"data": "127.0.0.0.1",
"valid": false
},
{
"description": "an IP address with out-of-range values",
"data": "256.256.256.256",
"valid": false
},
{
"description": "an IP address without 4 components",
"data": "127.0",
"valid": false
},
{
"description": "an IP address as an integer",
"data": "0x7f000001",
"valid": false
}
]
},
{
"description": "validation of IPv6 addresses",
"schema": {"format": "ipv6"},
"tests": [
{
"description": "a valid IPv6 address",
"data": "::1",
"valid": true
},
{
"description": "an IPv6 address with out-of-range values",
"data": "12345::",
"valid": false
},
{
"description": "an IPv6 address with too many components",
"data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1",
"valid": false
},
{
"description": "an IPv6 address containing illegal characters",
"data": "::laptop",
"valid": false
}
]
},
{
"description": "validation of host names",
"schema": {"format": "hostname"},
"tests": [
{
"description": "a valid host name",
"data": "www.example.com",
"valid": true
},
{
"description": "a host name starting with an illegal character",
"data": "-a-host-name-that-starts-with--",
"valid": false
},
{
"description": "a host name containing illegal characters",
"data": "not_a_valid_host_name",
"valid": false
},
{
"description": "a host name with a component too long",
"data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component",
"valid": false
}
]
}
]

View File

@ -40,6 +40,25 @@
}
]
},
{
"description": "non-ASCII pattern with additionalProperties",
"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 allows a schema which should validate",
@ -94,5 +113,21 @@
"valid": true
}
]
},
{
"description": "additionalProperties should not look in applicators",
"schema": {
"allOf": [
{"properties": {"foo": {}}}
],
"additionalProperties": {"type": "boolean"}
},
"tests": [
{
"description": "properties defined in allOf are not allowed",
"data": {"foo": 1, "bar": true},
"valid": false
}
]
}
]

View File

@ -108,5 +108,38 @@
"valid": false
}
]
},
{
"description": "allOf with boolean schemas, all true",
"schema": {"allOf": [true, true]},
"tests": [
{
"description": "any value is valid",
"data": "foo",
"valid": true
}
]
},
{
"description": "allOf with boolean schemas, some false",
"schema": {"allOf": [true, false]},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
},
{
"description": "allOf with boolean schemas, all false",
"schema": {"allOf": [false, false]},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
}
]

View File

@ -65,6 +65,39 @@
}
]
},
{
"description": "anyOf with boolean schemas, all true",
"schema": {"anyOf": [true, true]},
"tests": [
{
"description": "any value is valid",
"data": "foo",
"valid": true
}
]
},
{
"description": "anyOf with boolean schemas, some true",
"schema": {"anyOf": [true, false]},
"tests": [
{
"description": "any value is valid",
"data": "foo",
"valid": true
}
]
},
{
"description": "anyOf with boolean schemas, all false",
"schema": {"anyOf": [false, false]},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
},
{
"description": "anyOf complex types",
"schema": {

View File

@ -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
}
]
}
]

View File

@ -0,0 +1,86 @@
[
{
"description": "const validation",
"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": {"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": {"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": {"const": null},
"tests": [
{
"description": "null is valid",
"data": null,
"valid": true
},
{
"description": "not null is invalid",
"data": 0,
"valid": false
}
]
}
]

View File

@ -0,0 +1,95 @@
[
{
"description": "contains keyword validation",
"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": {
"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": {"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": {"contains": false},
"tests": [
{
"description": "any non-empty array is invalid",
"data": ["foo"],
"valid": false
},
{
"description": "empty array is invalid",
"data": [],
"valid": false
}
]
}
]

View File

@ -1,7 +1,7 @@
[
{
"description": "valid definition",
"schema": {"$ref": "http://json-schema.org/draft-04/schema#"},
"schema": {"$ref": "http://json-schema.org/draft-07/schema#"},
"tests": [
{
"description": "valid definition schema",
@ -16,7 +16,7 @@
},
{
"description": "invalid definition",
"schema": {"$ref": "http://json-schema.org/draft-04/schema#"},
"schema": {"$ref": "http://json-schema.org/draft-07/schema#"},
"tests": [
{
"description": "invalid definition schema",

View File

@ -42,6 +42,24 @@
}
]
},
{
"description": "dependencies with empty array",
"schema": {
"dependencies": {"bar": []}
},
"tests": [
{
"description": "empty object",
"data": {},
"valid": true
},
{
"description": "object with one property",
"data": {"bar": 2},
"valid": true
}
]
},
{
"description": "multiple dependencies",
"schema": {
@ -119,5 +137,36 @@
"valid": false
}
]
},
{
"description": "dependencies with boolean subschemas",
"schema": {
"dependencies": {
"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
}
]
}
]

View File

@ -39,13 +39,13 @@
{
"description": "enums in properties",
"schema": {
"type":"object",
"properties": {
"foo": {"enum":["foo"]},
"bar": {"enum":["bar"]}
},
"required": ["bar"]
},
"type":"object",
"properties": {
"foo": {"enum":["foo"]},
"bar": {"enum":["bar"]}
},
"required": ["bar"]
},
"tests": [
{
"description": "both properties are valid",

View File

@ -0,0 +1,30 @@
[
{
"description": "exclusiveMaximum validation",
"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
}
]
}
]

View File

@ -0,0 +1,30 @@
[
{
"description": "exclusiveMinimum validation",
"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
}
]
}
]

View File

@ -0,0 +1,188 @@
[
{
"description": "ignore if without then or else",
"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": {
"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": {
"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": {
"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": {
"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": {
"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": {
"allOf": [
{
"if": {
"exclusiveMaximum": 0
}
},
{
"then": {
"minimum": -10
}
},
{
"else": {
"multipleOf": 2
}
}
]
},
"tests": [
{
"description": "valid, but woud have been invalid through then",
"data": -100,
"valid": true
},
{
"description": "valid, but would have been invalid through else",
"data": 3,
"valid": true
}
]
}
]

View File

@ -74,5 +74,60 @@
"valid": true
}
]
},
{
"description": "items with boolean schema (true)",
"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": {"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 with boolean schemas",
"schema": {
"items": [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
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "maximum validation",
"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
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "minimum validation",
"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
}
]
}
]

View File

@ -91,6 +91,27 @@
"valid": true
}
]
},
{
"description": "not with boolean schema true",
"schema": {"not": true},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
},
{
"description": "not with boolean schema false",
"schema": {"not": false},
"tests": [
{
"description": "any value is valid",
"data": "foo",
"valid": true
}
]
}
]

View File

@ -65,6 +65,50 @@
}
]
},
{
"description": "oneOf with boolean schemas, all true",
"schema": {"oneOf": [true, true, true]},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
},
{
"description": "oneOf with boolean schemas, one true",
"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": {"oneOf": [true, true, false]},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
},
{
"description": "oneOf with boolean schemas, all false",
"schema": {"oneOf": [false, false, false]},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
},
{
"description": "oneOf complex types",
"schema": {

View File

@ -68,8 +68,7 @@
{
"description": "float comparison with high precision",
"schema": {
"maximum": 972783798187987123879878123.18878137,
"exclusiveMaximum": true
"exclusiveMaximum": 972783798187987123879878123.18878137
},
"tests": [
{
@ -93,8 +92,7 @@
{
"description": "float comparison with high precision on negative numbers",
"schema": {
"minimum": -972783798187987123879878123.18878137,
"exclusiveMinimum": true
"exclusiveMinimum": -972783798187987123879878123.18878137
},
"tests": [
{

View File

@ -0,0 +1,77 @@
[
{
"description": "validation of string-encoded content based on media type",
"schema": {
"contentMediaType": "application/json"
},
"tests": [
{
"description": "a valid JSON document",
"data": "{\"foo\": \"bar\"}",
"valid": true
},
{
"description": "an invalid JSON document",
"data": "{:}",
"valid": false
},
{
"description": "ignores non-strings",
"data": 100,
"valid": true
}
]
},
{
"description": "validation of binary string-encoding",
"schema": {
"contentEncoding": "base64"
},
"tests": [
{
"description": "a valid base64 string",
"data": "eyJmb28iOiAiYmFyIn0K",
"valid": true
},
{
"description": "an invalid base64 string (% is not a valid character)",
"data": "eyJmb28iOi%iYmFyIn0K",
"valid": false
},
{
"description": "ignores non-strings",
"data": 100,
"valid": true
}
]
},
{
"description": "validation of binary-encoded media type documents",
"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",
"data": "ezp9Cg==",
"valid": false
},
{
"description": "an invalid base64 string that is valid JSON",
"data": "{}",
"valid": false
},
{
"description": "ignores non-strings",
"data": 100,
"valid": true
}
]
}
]

View File

@ -0,0 +1,53 @@
[
{
"description": "validation of date-time strings",
"schema": {"format": "date-time"},
"tests": [
{
"description": "a valid date-time string",
"data": "1963-06-19T08:30:06.283185Z",
"valid": true
},
{
"description": "a valid date-time string without second fraction",
"data": "1963-06-19T08:30:06Z",
"valid": true
},
{
"description": "a valid date-time string with plus offset",
"data": "1937-01-01T12:00:27.87+00:20",
"valid": true
},
{
"description": "a valid date-time string with minus offset",
"data": "1990-12-31T15:59:50.123-08:00",
"valid": true
},
{
"description": "a invalid day in date-time string",
"data": "1990-02-31T15:59:60.123-08:00",
"valid": false
},
{
"description": "an invalid offset in date-time string",
"data": "1990-12-31T15:59:60-24:00",
"valid": false
},
{
"description": "an invalid date-time string",
"data": "06/19/1963 08:30:06 PST",
"valid": false
},
{
"description": "case-insensitive T and Z",
"data": "1963-06-19t08:30:06.283185z",
"valid": true
},
{
"description": "only RFC3339 not all of ISO 8601 are valid",
"data": "2013-350T01:01:01",
"valid": false
}
]
}
]

View File

@ -0,0 +1,23 @@
[
{
"description": "validation of date strings",
"schema": {"format": "date"},
"tests": [
{
"description": "a valid date string",
"data": "1963-06-19",
"valid": true
},
{
"description": "an invalid date-time string",
"data": "06/19/1963",
"valid": false
},
{
"description": "only RFC3339 not all of ISO 8601 are valid",
"data": "2013-350",
"valid": false
}
]
}
]

View File

@ -0,0 +1,18 @@
[
{
"description": "validation of e-mail addresses",
"schema": {"format": "email"},
"tests": [
{
"description": "a valid e-mail address",
"data": "joe.bloggs@example.com",
"valid": true
},
{
"description": "an invalid e-mail address",
"data": "2962",
"valid": false
}
]
}
]

View File

@ -0,0 +1,33 @@
[
{
"description": "validation of host names",
"schema": {"format": "hostname"},
"tests": [
{
"description": "a valid host name",
"data": "www.example.com",
"valid": true
},
{
"description": "a valid punycoded IDN hostname",
"data": "xn--4gbwdl.xn--wgbh1c",
"valid": true
},
{
"description": "a host name starting with an illegal character",
"data": "-a-host-name-that-starts-with--",
"valid": false
},
{
"description": "a host name containing illegal characters",
"data": "not_a_valid_host_name",
"valid": false
},
{
"description": "a host name with a component too long",
"data": "a-vvvvvvvvvvvvvvvveeeeeeeeeeeeeeeerrrrrrrrrrrrrrrryyyyyyyyyyyyyyyy-long-host-name-component",
"valid": false
}
]
}
]

View File

@ -0,0 +1,18 @@
[
{
"description": "validation of an internationalized e-mail addresses",
"schema": {"format": "idn-email"},
"tests": [
{
"description": "a valid idn e-mail (example@example.test in Hangul)",
"data": "실례@실례.테스트",
"valid": true
},
{
"description": "an invalid idn e-mail address",
"data": "2962",
"valid": false
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "validation of internationalized host names",
"schema": {"format": "idn-hostname"},
"tests": [
{
"description": "a valid host name (example.test in Hangul)",
"data": "실례.테스트",
"valid": true
},
{
"description": "illegal first char U+302E Hangul single dot tone mark",
"data": "〮실례.테스트",
"valid": false
},
{
"description": "contains illegal char U+302E Hangul single dot tone mark",
"data": "실〮례.테스트",
"valid": false
},
{
"description": "a host name with a component too long",
"data": "실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실실례례테스트례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례례례례례례례례테스트례례례례례례례례례례례례테스트례례실례.테스트",
"valid": false
}
]
}
]

View File

@ -0,0 +1,33 @@
[
{
"description": "validation of IP addresses",
"schema": {"format": "ipv4"},
"tests": [
{
"description": "a valid IP address",
"data": "192.168.0.1",
"valid": true
},
{
"description": "an IP address with too many components",
"data": "127.0.0.0.1",
"valid": false
},
{
"description": "an IP address with out-of-range values",
"data": "256.256.256.256",
"valid": false
},
{
"description": "an IP address without 4 components",
"data": "127.0",
"valid": false
},
{
"description": "an IP address as an integer",
"data": "0x7f000001",
"valid": false
}
]
}
]

View File

@ -0,0 +1,28 @@
[
{
"description": "validation of IPv6 addresses",
"schema": {"format": "ipv6"},
"tests": [
{
"description": "a valid IPv6 address",
"data": "::1",
"valid": true
},
{
"description": "an IPv6 address with out-of-range values",
"data": "12345::",
"valid": false
},
{
"description": "an IPv6 address with too many components",
"data": "1:1:1:1:1:1:1:1:1:1:1:1:1:1:1:1",
"valid": false
},
{
"description": "an IPv6 address containing illegal characters",
"data": "::laptop",
"valid": false
}
]
}
]

View File

@ -0,0 +1,43 @@
[
{
"description": "validation of IRI References",
"schema": {"format": "iri-reference"},
"tests": [
{
"description": "a valid IRI",
"data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx",
"valid": true
},
{
"description": "a valid protocol-relative IRI Reference",
"data": "//ƒøø.ßår/?∂éœ=πîx#πîüx",
"valid": true
},
{
"description": "a valid relative IRI Reference",
"data": "/âππ",
"valid": true
},
{
"description": "an invalid IRI Reference",
"data": "\\\\WINDOWS\\filëßåré",
"valid": false
},
{
"description": "a valid IRI Reference",
"data": "âππ",
"valid": true
},
{
"description": "a valid IRI fragment",
"data": "#ƒrägmênt",
"valid": true
},
{
"description": "an invalid IRI fragment",
"data": "#ƒräg\\mênt",
"valid": false
}
]
}
]

View File

@ -0,0 +1,53 @@
[
{
"description": "validation of IRIs",
"schema": {"format": "iri"},
"tests": [
{
"description": "a valid IRI with anchor tag",
"data": "http://ƒøø.ßår/?∂éœ=πîx#πîüx",
"valid": true
},
{
"description": "a valid IRI with anchor tag and parantheses",
"data": "http://ƒøø.com/blah_(wîkïpédiå)_blah#ßité-1",
"valid": true
},
{
"description": "a valid IRI with URL-encoded stuff",
"data": "http://ƒøø.ßår/?q=Test%20URL-encoded%20stuff",
"valid": true
},
{
"description": "a valid IRI with many special characters",
"data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com",
"valid": true
},
{
"description": "a valid IRI based on IPv6",
"data": "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
"valid": true
},
{
"description": "an invalid IRI based on IPv6",
"data": "http://2001:0db8:85a3:0000:0000:8a2e:0370:7334",
"valid": false
},
{
"description": "an invalid relative IRI Reference",
"data": "/abc",
"valid": false
},
{
"description": "an invalid IRI",
"data": "\\\\WINDOWS\\filëßåré",
"valid": false
},
{
"description": "an invalid IRI though valid IRI reference",
"data": "âππ",
"valid": false
}
]
}
]

View File

@ -0,0 +1,168 @@
[
{
"description": "validation of JSON-pointers (JSON String Representation)",
"schema": {"format": "json-pointer"},
"tests": [
{
"description": "a valid JSON-pointer",
"data": "/foo/bar~0/baz~1/%a",
"valid": true
},
{
"description": "not a valid JSON-pointer (~ not escaped)",
"data": "/foo/bar~",
"valid": false
},
{
"description": "valid JSON-pointer with empty segment",
"data": "/foo//bar",
"valid": true
},
{
"description": "valid JSON-pointer with the last empty segment",
"data": "/foo/bar/",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #1",
"data": "",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #2",
"data": "/foo",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #3",
"data": "/foo/0",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #4",
"data": "/",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #5",
"data": "/a~1b",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #6",
"data": "/c%d",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #7",
"data": "/e^f",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #8",
"data": "/g|h",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #9",
"data": "/i\\j",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #10",
"data": "/k\"l",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #11",
"data": "/ ",
"valid": true
},
{
"description": "valid JSON-pointer as stated in RFC 6901 #12",
"data": "/m~0n",
"valid": true
},
{
"description": "valid JSON-pointer used adding to the last array position",
"data": "/foo/-",
"valid": true
},
{
"description": "valid JSON-pointer (- used as object member name)",
"data": "/foo/-/bar",
"valid": true
},
{
"description": "valid JSON-pointer (multiple escaped characters)",
"data": "/~1~0~0~1~1",
"valid": true
},
{
"description": "valid JSON-pointer (escaped with fraction part) #1",
"data": "/~1.1",
"valid": true
},
{
"description": "valid JSON-pointer (escaped with fraction part) #2",
"data": "/~0.1",
"valid": true
},
{
"description": "not a valid JSON-pointer (URI Fragment Identifier) #1",
"data": "#",
"valid": false
},
{
"description": "not a valid JSON-pointer (URI Fragment Identifier) #2",
"data": "#/",
"valid": false
},
{
"description": "not a valid JSON-pointer (URI Fragment Identifier) #3",
"data": "#a",
"valid": false
},
{
"description": "not a valid JSON-pointer (some escaped, but not all) #1",
"data": "/~0~",
"valid": false
},
{
"description": "not a valid JSON-pointer (some escaped, but not all) #2",
"data": "/~0/~",
"valid": false
},
{
"description": "not a valid JSON-pointer (wrong escape character) #1",
"data": "/~2",
"valid": false
},
{
"description": "not a valid JSON-pointer (wrong escape character) #2",
"data": "/~-1",
"valid": false
},
{
"description": "not a valid JSON-pointer (multiple characters not escaped)",
"data": "/~~",
"valid": false
},
{
"description": "not a valid JSON-pointer (isn't empty nor starts with /) #1",
"data": "a",
"valid": false
},
{
"description": "not a valid JSON-pointer (isn't empty nor starts with /) #2",
"data": "0",
"valid": false
},
{
"description": "not a valid JSON-pointer (isn't empty nor starts with /) #3",
"data": "a/a",
"valid": false
}
]
}
]

View File

@ -0,0 +1,18 @@
[
{
"description": "validation of regular expressions",
"schema": {"format": "regex"},
"tests": [
{
"description": "a valid regular expression",
"data": "([abc])+\\s+$",
"valid": true
},
{
"description": "a regular expression with unclosed parens is invalid",
"data": "^(abc]",
"valid": false
}
]
}
]

View File

@ -0,0 +1,33 @@
[
{
"description": "validation of Relative JSON Pointers (RJP)",
"schema": {"format": "relative-json-pointer"},
"tests": [
{
"description": "a valid upwards RJP",
"data": "1",
"valid": true
},
{
"description": "a valid downwards RJP",
"data": "0/foo/bar",
"valid": true
},
{
"description": "a valid up and then down RJP, with array index",
"data": "2/0/baz/1/zip",
"valid": true
},
{
"description": "a valid RJP taking the member or index name",
"data": "0#",
"valid": true
},
{
"description": "an invalid RJP that is a valid JSON Pointer",
"data": "/foo/bar",
"valid": false
}
]
}
]

View File

@ -0,0 +1,23 @@
[
{
"description": "validation of time strings",
"schema": {"format": "time"},
"tests": [
{
"description": "a valid time string",
"data": "08:30:06.283185Z",
"valid": true
},
{
"description": "an invalid time string",
"data": "08:30:06 PST",
"valid": false
},
{
"description": "only RFC3339 not all of ISO 8601 are valid",
"data": "01:01:01,1111",
"valid": false
}
]
}
]

View File

@ -0,0 +1,43 @@
[
{
"description": "validation of URI References",
"schema": {"format": "uri-reference"},
"tests": [
{
"description": "a valid URI",
"data": "http://foo.bar/?baz=qux#quux",
"valid": true
},
{
"description": "a valid protocol-relative URI Reference",
"data": "//foo.bar/?baz=qux#quux",
"valid": true
},
{
"description": "a valid relative URI Reference",
"data": "/abc",
"valid": true
},
{
"description": "an invalid URI Reference",
"data": "\\\\WINDOWS\\fileshare",
"valid": false
},
{
"description": "a valid URI Reference",
"data": "abc",
"valid": true
},
{
"description": "a valid URI fragment",
"data": "#fragment",
"valid": true
},
{
"description": "an invalid URI fragment",
"data": "#frag\\ment",
"valid": false
}
]
}
]

View File

@ -0,0 +1,30 @@
[
{
"description": "format: uri-template",
"schema": {
"format": "uri-template"
},
"tests": [
{
"description": "a valid uri-template",
"data": "http://example.com/dictionary/{term:1}/{term}",
"valid": true
},
{
"description": "an invalid uri-template",
"data": "http://example.com/dictionary/{term:1}/{term",
"valid": false
},
{
"description": "a valid uri-template without variables",
"data": "http://example.com/dictionary",
"valid": true
},
{
"description": "a valid relative uri-template",
"data": "dictionary/{term:1}/{term}",
"valid": true
}
]
}
]

View File

@ -0,0 +1,103 @@
[
{
"description": "validation of URIs",
"schema": {"format": "uri"},
"tests": [
{
"description": "a valid URL with anchor tag",
"data": "http://foo.bar/?baz=qux#quux",
"valid": true
},
{
"description": "a valid URL with anchor tag and parantheses",
"data": "http://foo.com/blah_(wikipedia)_blah#cite-1",
"valid": true
},
{
"description": "a valid URL with URL-encoded stuff",
"data": "http://foo.bar/?q=Test%20URL-encoded%20stuff",
"valid": true
},
{
"description": "a valid puny-coded URL ",
"data": "http://xn--nw2a.xn--j6w193g/",
"valid": true
},
{
"description": "a valid URL with many special characters",
"data": "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com",
"valid": true
},
{
"description": "a valid URL based on IPv4",
"data": "http://223.255.255.254",
"valid": true
},
{
"description": "a valid URL with ftp scheme",
"data": "ftp://ftp.is.co.za/rfc/rfc1808.txt",
"valid": true
},
{
"description": "a valid URL for a simple text file",
"data": "http://www.ietf.org/rfc/rfc2396.txt",
"valid": true
},
{
"description": "a valid URL ",
"data": "ldap://[2001:db8::7]/c=GB?objectClass?one",
"valid": true
},
{
"description": "a valid mailto URI",
"data": "mailto:John.Doe@example.com",
"valid": true
},
{
"description": "a valid newsgroup URI",
"data": "news:comp.infosystems.www.servers.unix",
"valid": true
},
{
"description": "a valid tel URI",
"data": "tel:+1-816-555-1212",
"valid": true
},
{
"description": "a valid URN",
"data": "urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
"valid": true
},
{
"description": "an invalid protocol-relative URI Reference",
"data": "//foo.bar/?baz=qux#quux",
"valid": false
},
{
"description": "an invalid relative URI Reference",
"data": "/abc",
"valid": false
},
{
"description": "an invalid URI",
"data": "\\\\WINDOWS\\fileshare",
"valid": false
},
{
"description": "an invalid URI though valid URI reference",
"data": "abc",
"valid": false
},
{
"description": "an invalid URI with spaces",
"data": "http:// shouldfail.com",
"valid": false
},
{
"description": "an invalid URI with spaces and missing scheme",
"data": ":// should fail",
"valid": false
}
]
}
]

View File

@ -6,9 +6,9 @@
},
"tests": [
{
"description": "a float is not an integer even without fractional part",
"description": "a float without fractional part is an integer",
"data": 1.0,
"valid": false
"valid": true
}
]
}

View File

@ -30,12 +30,12 @@
},
{
"description": "ignores arrays",
"data": [],
"data": ["foo"],
"valid": true
},
{
"description": "ignores strings",
"data": "",
"data": "foo",
"valid": true
},
{
@ -116,5 +116,36 @@
"valid": false
}
]
},
{
"description": "patternProperties with boolean schemas",
"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": "empty object is valid",
"data": {},
"valid": true
}
]
}
]

View File

@ -93,5 +93,36 @@
"valid": false
}
]
},
{
"description": "properties with boolean schema",
"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
}
]
}
]

View File

@ -0,0 +1,78 @@
[
{
"description": "propertyNames validation",
"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": {"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": {"propertyNames": false},
"tests": [
{
"description": "object with any properties is invalid",
"data": {"foo": 1},
"valid": false
},
{
"description": "empty object is valid",
"data": {},
"valid": true
}
]
}
]

View File

@ -175,7 +175,7 @@
},
{
"description": "remote ref, containing refs itself",
"schema": {"$ref": "http://json-schema.org/draft-04/schema#"},
"schema": {"$ref": "http://json-schema.org/draft-07/schema#"},
"tests": [
{
"description": "remote ref valid",
@ -209,10 +209,42 @@
}
]
},
{
"description": "$ref to boolean schema true",
"schema": {
"$ref": "#/definitions/bool",
"definitions": {
"bool": true
}
},
"tests": [
{
"description": "any value is valid",
"data": "foo",
"valid": true
}
]
},
{
"description": "$ref to boolean schema false",
"schema": {
"$ref": "#/definitions/bool",
"definitions": {
"bool": false
}
},
"tests": [
{
"description": "any value is invalid",
"data": "foo",
"valid": false
}
]
},
{
"description": "Recursive references between schemas",
"schema": {
"id": "http://localhost:1234/tree",
"$id": "http://localhost:1234/tree",
"description": "tree of nodes",
"type": "object",
"properties": {
@ -225,7 +257,7 @@
"required": ["meta", "nodes"],
"definitions": {
"node": {
"id": "http://localhost:1234/node",
"$id": "http://localhost:1234/node",
"description": "node",
"type": "object",
"properties": {
@ -239,7 +271,7 @@
"tests": [
{
"description": "valid tree",
"data": {
"data": {
"meta": "root",
"nodes": [
{
@ -268,7 +300,7 @@
},
{
"description": "invalid tree",
"data": {
"data": {
"meta": "root",
"nodes": [
{

View File

@ -52,9 +52,9 @@
{
"description": "base URI change",
"schema": {
"id": "http://localhost:1234/",
"$id": "http://localhost:1234/",
"items": {
"id": "folder/",
"$id": "folder/",
"items": {"$ref": "folderInteger.json"}
}
},
@ -74,14 +74,14 @@
{
"description": "base URI change - change folder",
"schema": {
"id": "http://localhost:1234/scope_change_defs1.json",
"$id": "http://localhost:1234/scope_change_defs1.json",
"type" : "object",
"properties": {
"list": {"$ref": "#/definitions/baz"}
},
"definitions": {
"baz": {
"id": "folder/",
"$id": "folder/",
"type": "array",
"items": {"$ref": "folderInteger.json"}
}
@ -103,14 +103,14 @@
{
"description": "base URI change - change folder in subschema",
"schema": {
"id": "http://localhost:1234/scope_change_defs2.json",
"$id": "http://localhost:1234/scope_change_defs2.json",
"type" : "object",
"properties": {
"list": {"$ref": "#/definitions/baz/definitions/bar"}
},
"definitions": {
"baz": {
"id": "folder/",
"$id": "folder/",
"definitions": {
"bar": {
"type": "array",
@ -136,7 +136,7 @@
{
"description": "root ref in remote ref",
"schema": {
"id": "http://localhost:1234/object",
"$id": "http://localhost:1234/object",
"type": "object",
"properties": {
"name": {"$ref": "name.json#/definitions/orNull"}

View File

@ -50,5 +50,21 @@
"valid": true
}
]
},
{
"description": "required with empty array",
"schema": {
"properties": {
"foo": {}
},
"required": []
},
"tests": [
{
"description": "property not required",
"data": {},
"valid": true
}
]
}
]

146
test/id-ref.cpp Normal file
View File

@ -0,0 +1,146 @@
#include <nlohmann/json.hpp>
#include "../src/json-schema.hpp"
#include <iostream>
using nlohmann::json;
auto schema_draft = R"(
{
"$id": "http://example.com/root.json",
"definitions": {
"A": { "$id": "#foo" },
"B": {
"$id": "other.json",
"definitions": {
"X": { "$id": "#bar" },
"Y": { "$id": "t/inner.json" }
}
},
"C": {
"$id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f",
"definitions": {
"Z": { "$id": "#bar" },
"9": { "$id": "http://example.com/drole.json" }
}
}
}
}
)"_json;
/* # (document root)
http://example.com/root.json
http://example.com/root.json#
#/definitions/A
http://example.com/root.json#foo
http://example.com/root.json#/definitions/A
#/definitions/B
http://example.com/other.json
http://example.com/other.json#
http://example.com/root.json#/definitions/B
#/definitions/B/definitions/X
http://example.com/other.json#bar
http://example.com/other.json#/definitions/X
http://example.com/root.json#/definitions/B/definitions/X
#/definitions/B/definitions/Y
http://example.com/t/inner.json
http://example.com/t/inner.json#
http://example.com/other.json#/definitions/Y
http://example.com/root.json#/definitions/B/definitions/Y
#/definitions/C
urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f
urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f#
http://example.com/root.json#/definitions/C
*/
auto schema = R"(
{
"id": "http://localhost:1234/scope_change_defs2.json",
"type" : "object",
"properties": {
"list": {"$ref": "#/definitions/baz/definitions/bar"}
},
"definitions": {
"baz": {
"id": "folder/",
"definitions": {
"bar": {
"type": "array",
"items": {"$ref": "folderInteger.json"}
}
}
}
}
})"_json;
class json_schema_validator
{
public:
std::vector<json> schemas_;
std::map<nlohmann::json_uri, const json *> schema_store_;
public:
void insert_schema(json &s, std::vector<nlohmann::json_uri> base_uris)
{
auto id = s.find("$id");
if (id != s.end())
base_uris.push_back(base_uris.back().derive(id.value()));
for (auto &u : base_uris)
schema_store_[u] = &s;
for (auto i = s.begin();
i != s.end();
++i) {
switch (i.value().type()) {
case json::value_t::object: { // child is object, thus a schema
std::vector<nlohmann::json_uri> subschema_uri = base_uris;
for (auto &ss : subschema_uri)
ss = ss.append(nlohmann::json_uri::escape(i.key()));
insert_schema(i.value(), subschema_uri);
} break;
case json::value_t::string:
// this schema is a reference
if (i.key() == "$ref") {
auto id = base_uris.back().derive(i.value());
i.value() = id.to_string();
}
break;
default:
break;
}
}
}
};
int main(void)
{
json_schema_validator store;
store.insert_schema(schema_draft, {{"#"}});
for (auto &i : store.schema_store_) {
std::cerr << i.first << " " << *i.second << "\n";
}
return 0;
}

View File

@ -1,29 +0,0 @@
{
"id": "http://example.com/root.json",
"definitions": {
"A": { "id": "#foo" },
"B": {
"id": "other.json",
"definitions": {
"X": { "id": "#bar" },
"Y": { "id": "t/inner.json" }
}
},
"C": {
"id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f"
},
"single": {
"id": "#item",
"type": "integer"
},
"a": {"type": "integer"},
"b": {"$ref": "#/definitions/a"},
"c": {"$ref": "#/definitions/b"},
"remote": { "$ref": "http://localhost:1234/subSchemas.json#/refToInteger" }
},
"items": {
"type": "array",
"items": [ { "$ref": "#item" }, { "$ref": "other.json#bar" } ]
}
}

View File

@ -0,0 +1,3 @@
add_test_simple_schema(Issue::9
${CMAKE_CURRENT_SOURCE_DIR}/base.json
${CMAKE_CURRENT_SOURCE_DIR}/instance.json)

13
test/issue-9/bar.json Normal file
View File

@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Describes bar",
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
}

9
test/issue-9/base.json Normal file
View File

@ -0,0 +1,9 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Describes foo",
"type": "object",
"allOf": [
{ "$ref": "bar.json" },
{ "$ref": "foo/foo.json" }
]
}

View File

@ -0,0 +1,5 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Describes baz",
"$ref": "qux/qux.json"
}

View File

@ -0,0 +1,13 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Describes qux",
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
}

View File

@ -0,0 +1,5 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Describes foo",
"$ref": "baz/baz.json"
}

View File

@ -0,0 +1,3 @@
{
"name": "name"
}

View File

@ -1,16 +0,0 @@
#!/bin/bash
PWD=$(realpath `dirname $0`/../JSON-Schema-Test-Suite/tests/draft4)
TESTS=`find $PWD | grep json$`
FAILCOUNT=0
for T in $TESTS
do
./json-schema-test < $T
FAILCOUNT=$(($FAILCOUNT + $?))
done
echo $FAILCOUNT tests failed

156
test/uri.cpp Normal file
View File

@ -0,0 +1,156 @@
/*
* JSON schema validator for JSON for modern C++
*
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* SPDX-License-Identifier: MIT
*
*/
#include <cstdlib>
#include <json-schema.hpp>
#include <iostream>
using nlohmann::json;
using nlohmann::json_uri;
static int errors;
#define EXPECT_EQ(a, b) \
do { \
if (a.to_string() != b) { \
std::cerr << "Failed: '" << a << "' != '" << b << "'\n"; \
errors++; \
} \
} while (0)
// test-schema taken from spec with modifications
auto schema = R"(
{
"$id": "http://example.com/root.json",
"definitions": {
"A": { "$id": "#foo" },
"B": {
"$id": "other.json",
"definitions": {
"X": { "$id": "#bar" },
"Y": { "$id": "t/inner.json" }
}
},
"C": {
"$id": "urn:uuid:ee564b8a-7a87-4125-8c96-e9f123d6766f",
"definitions": {
"Z": { "$id": "#bar" },
"9": { "$id": "http://example.com/drole.json" }
}
}
}
}
)"_json;
// resolve all refs
class store
{
public:
std::vector<json> schemas_;
std::map<nlohmann::json_uri, const json *> schema_store_;
public:
void insert_schema(json &s, std::vector<nlohmann::json_uri> base_uris)
{
auto id = s.find("$id");
if (id != s.end())
base_uris.push_back(base_uris.back().derive(id.value()));
for (auto &u : base_uris)
schema_store_[u] = &s;
for (auto i = s.begin();
i != s.end();
++i) {
switch (i.value().type()) {
case json::value_t::object: { // child is object, thus a schema
std::vector<nlohmann::json_uri> subschema_uri = base_uris;
for (auto &ss : subschema_uri)
ss = ss.append(nlohmann::json_uri::escape(i.key()));
insert_schema(i.value(), subschema_uri);
} break;
case json::value_t::string:
// this schema is a reference
if (i.key() == "$ref") {
auto id = base_uris.back().derive(i.value());
i.value() = id.to_string();
}
break;
default:
break;
}
}
}
};
//static void store_test(void)
//{
// store s;
//
// s.insert_schema(schema, {{""}});
//
// for (auto &i : s.schema_store_) {
// std::cerr << i.first << " " << *i.second << "\n";
// }
//}
static void paths(json_uri start,
const std::string &full,
const std::string &full_path,
const std::string &no_path)
{
EXPECT_EQ(start, full + " # ");
auto a = start.derive("other.json");
EXPECT_EQ(a, full_path + "/other.json # ");
auto b = a.derive("base.json");
EXPECT_EQ(b, full_path + "/base.json # ");
auto c = b.derive("subdir/base.json");
EXPECT_EQ(c, full_path + "/subdir/base.json # ");
auto d = c.derive("subdir2/base.json");
EXPECT_EQ(d, full_path + "/subdir/subdir2/base.json # ");
auto e = c.derive("/subdir2/base.json");
EXPECT_EQ(e, no_path + "/subdir2/base.json # ");
auto f = c.derive("new.json");
EXPECT_EQ(f, full_path + "/subdir/new.json # ");
auto g = c.derive("/new.json");
EXPECT_EQ(g, no_path + "/new.json # ");
}
int main(void)
{
json_uri empty("");
paths(empty,
"",
"",
"");
json_uri http("http://json-schema.org/draft-07/schema#");
paths(http,
"http://json-schema.org/draft-07/schema",
"http://json-schema.org/draft-07",
"http://json-schema.org");
// plain name fragments instead of JSON-pointers, are not supported, yet
//store_test();
return errors;
}