Skip to content

Commit 7beb40b

Browse files
committed
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
1 parent 2785ce0 commit 7beb40b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

88 files changed

+3803
-1430
lines changed

.clang-format

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ AllowShortFunctionsOnASingleLine: Inline
77
BreakBeforeBraces: Linux
88
ColumnLimit: 0
99
ConstructorInitializerAllOnOneLineOrOnePerLine: true
10-
IndentWidth: 2
11-
ObjCBlockIndentWidth: 2
10+
IndentWidth: 4
11+
IndentPPDirectives: AfterHash
12+
ObjCBlockIndentWidth: 0
1213
SpaceAfterCStyleCast: true
13-
TabWidth: 2
14+
TabWidth: 4
15+
AccessModifierOffset: -4
1416
UseTab: ForIndentation
1517
...

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ script:
6060
- $CXX --version
6161

6262
# put json.hpp to nlohmann
63-
- mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.1.2/json.hpp -O nlohmann/json.hpp
63+
- mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.5.0/json.hpp -O nlohmann/json.hpp
6464

6565
# compile and execute unit tests
6666
- mkdir -p build && cd build

CMakeLists.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ endif()
3232

3333
# and one for the validator
3434
add_library(json-schema-validator
35-
src/json-schema-draft4.json.cpp
35+
src/json-schema-draft7.json.cpp
3636
src/json-uri.cpp
3737
src/json-validator.cpp)
3838

@@ -95,8 +95,13 @@ if (BUILD_EXAMPLES)
9595
# simple json-schema-validator-executable
9696
add_executable(json-schema-validate app/json-schema-validate.cpp)
9797
target_link_libraries(json-schema-validate json-schema-validator)
98+
99+
add_executable(readme app/readme.cpp)
100+
target_link_libraries(readme json-schema-validator)
98101
endif()
99102

103+
#add_subdirectory(ng)
104+
100105
if (BUILD_TESTS)
101106
# test-zone
102107
enable_testing()

README.md

+88-56
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,68 @@
11

22
[![Build Status](https://travis-ci.org/pboettch/json-schema-validator.svg?branch=master)](https://travis-ci.org/pboettch/json-schema-validator)
33

4-
# Modern C++ JSON schema validator
4+
# JSON schema validator for JSON for Modern C++
55

66
# What is it?
77

88
This is a C++ library for validating JSON documents based on a
99
[JSON Schema](http://json-schema.org/) which itself should validate with
10-
[draft-4 of JSON Schema Validation](http://json-schema.org/schema).
10+
[draft-7 of JSON Schema Validation](http://json-schema.org/schema).
1111

1212
First a disclaimer: *It is work in progress and
13-
contributions or hints or discussions are welcome.*
13+
contributions or hints or discussions are welcome.* Even though a 2.0.0 release is immenent.
1414

1515
Niels Lohmann et al develop a great JSON parser for C++ called [JSON for Modern
1616
C++](https://github.com/nlohmann/json). This validator is based on this
1717
library, hence the name.
1818

19-
The name is for the moment purely marketing, because there is, IMHO, not so much
20-
modern C++ inside. There is plenty of space to make it more modern.
21-
2219
External documentation is missing as well. However the API of the validator
23-
will be rather simple.
20+
is rather simple.
21+
22+
# New in version 2
23+
24+
Although significant changes have been coorporate to the 2 version
25+
(a complete rewrite) the API is compatible with the 1.0.0 release. Except for
26+
the namespace which is now `nlohmann::json_schema.
27+
28+
Version **2** supports JSON schema draft 7, whereas 1 was supporting draft 4
29+
only. Please update your schemas.
30+
31+
The primary change in 2 is the way a schema is used. While in version 1 the schema was
32+
kept as a JSON-document and used again and again during validation, in versin 2 the schema
33+
is parsed into compiled C++ objects which are then used during validation. There are surely
34+
still optimizations to be done, but validation speed has improved by factor 100
35+
or more.
36+
37+
In JSON-schema one sub-schema can be
2438

2539
# Design goals
2640

2741
The main goal of this validator is to produce *human-comprehensible* error
28-
messages if a JSON-document/instance does not comply with its schema. This is
29-
done with exceptions thrown at the users with a helpful message telling what's
30-
wrong with the document while validating.
42+
messages if a JSON-document/instance does not comply to its schema.
43+
44+
By default this is done with exceptions thrown at the users with a helpful
45+
message telling what's wrong with the document while validating.
46+
47+
With **2.0.0** the user can passed a `json_scheam::basic_error_handler` derived object
48+
along with the instance to validate to receive a each time a validation error occurs
49+
and decice what to do (throwing, counting, collecting).
3150

3251
Another goal was to use Niels Lohmann's JSON-library. This is why the validator
3352
lives in his namespace.
3453

3554
# Weaknesses
3655

37-
Schema-reference resolution is not recursivity-proven: If there is a nested
38-
cross-schema reference, it will not stop. (Though I haven't tested it)
39-
40-
Numerical validation uses `int64_t`, `uint64_t` or `double`, depending on if
56+
Numerical validation uses nlohmann integer, unsigned and floating point types, depending on if
4157
the schema type is "integer" or "number". Bignum (i.e. arbitrary precision and
4258
range) is not supported at this time.
4359

44-
Unsigned integer validation will only take place if the following two conditions are true:
45-
- The nlohmann `type()` of the json object under validation is `nlohmann::json::value_t::number_unsigned`
46-
- The schema specifies a numerical minimum greater than or equal to 0
60+
Currently JSON-URI with "plain name fragments" are not supported. So referring to an URI
61+
with `$ref: "file.json#plain"` will not work.
4762

4863
# How to use
4964

50-
The current state of the build-system needs at least version **3.1.1** of NLohmann's
65+
The current state of the build-system needs at least version **3.5.0** of NLohmann's
5166
JSON library. It is looking for the `json.hpp` within a `nlohmann/`-path.
5267

5368
When build the library you need to provide the path to the directory where the include-file
@@ -66,7 +81,7 @@ cmake .. \
6681
-DNLOHMANN_JSON_DIR=<path/to/>nlohmann/json.hpp \
6782
-DJSON_SCHEMA_TEST_SUITE_PATH=<path/to/JSON-Schema-test-suite> # optional
6883
make # install
69-
ctest # if test-suite has been given
84+
ctest # run unit, non-regression and test-suite tests
7085
```
7186
### As a subdirectory from within
7287

@@ -93,7 +108,6 @@ In your initial call to cmake simply add:
93108
```bash
94109
cmake -DBUILD_SHARED_LIBS=ON
95110
```
96-
97111
## Code
98112

99113
See also `app/json-schema-validate.cpp`.
@@ -104,13 +118,12 @@ See also `app/json-schema-validate.cpp`.
104118
#include "json-schema.hpp"
105119

106120
using nlohmann::json;
107-
using nlohmann::json_uri;
108-
using nlohmann::json_schema_draft4::json_validator;
121+
using nlohmann::json_schema::json_validator;
109122

110123
// The schema is defined based upon a string literal
111124
static json person_schema = R"(
112125
{
113-
"$schema": "http://json-schema.org/draft-04/schema#",
126+
"$schema": "http://json-schema.org/draft-07/schema#",
114127
"title": "A person",
115128
"properties": {
116129
"name": {
@@ -137,55 +150,74 @@ static json person_schema = R"(
137150
static json bad_person = {{"age", 42}};
138151
static json good_person = {{"name", "Albert"}, {"age", 42}};
139152

140-
int main(){
141-
142-
/* json-parse the schema */
143-
144-
json_validator validator; // create validator
145-
146-
try {
147-
validator.set_root_schema(person_schema); // insert root-schema
148-
} catch (const std::exception &e) {
149-
std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n";
150-
return EXIT_FAILURE;
151-
}
152-
153-
/* json-parse the people */
154-
155-
for (auto &person : {bad_person, good_person})
156-
{
157-
std::cout << "About to validate this person:\n" << std::setw(2) << person << std::endl;
158-
try {
159-
validator.validate(person); // validate the document
160-
std::cout << "Validation succeeded\n";
161-
} catch (const std::exception &e) {
162-
std::cerr << "Validation failed, here is why: " << e.what() << "\n";
163-
}
164-
}
165-
return EXIT_SUCCESS;
153+
int main()
154+
{
155+
/* json-parse the schema */
156+
157+
json_validator validator; // create validator
158+
159+
try {
160+
validator.set_root_schema(person_schema); // insert root-schema
161+
} catch (const std::exception &e) {
162+
std::cerr << "Validation of schema failed, here is why: " << e.what() << "\n";
163+
return EXIT_FAILURE;
164+
}
165+
166+
/* json-parse the people - API of 1.0.0, default throwing error handler */
167+
168+
for (auto &person : {bad_person, good_person}) {
169+
std::cout << "About to validate this person:\n"
170+
<< std::setw(2) << person << std::endl;
171+
try {
172+
validator.validate(person); // validate the document
173+
std::cout << "Validation succeeded\n";
174+
} catch (const std::exception &e) {
175+
std::cerr << "Validation failed, here is why: " << e.what() << "\n";
176+
}
177+
}
178+
179+
/* json-parse the people - with custom error handler */
180+
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
181+
{
182+
void error(const std::string &path, const json &instance, const std::string &message) override
183+
{
184+
nlohmann::json_schema::basic_error_handler::error(path, instance, message);
185+
std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n";
186+
}
187+
};
188+
189+
190+
for (auto &person : {bad_person, good_person}) {
191+
std::cout << "About to validate this person:\n"
192+
<< std::setw(2) << person << std::endl;
193+
194+
custom_error_handler err;
195+
validator.validate(person, err); // validate the document - uses the default throwing error-handler
196+
197+
if (err)
198+
std::cerr << "Validation failed\n";
199+
else
200+
std::cout << "Validation succeeded\n";
201+
}
202+
203+
return EXIT_SUCCESS;
166204
}
167-
168205
```
169206
170207
# Compliance
171208
172209
There is an application which can be used for testing the validator with the
173210
[JSON-Schema-Test-Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite).
211+
In order to simplify the testing, the test-suite is included in the repository.
174212
175213
If you have cloned this repository providing a path the repository-root via the
176214
cmake-variable `JSON_SCHEMA_TEST_SUITE_PATH` will enable the test-target(s).
177215
178216
All required tests are **OK**.
179217
180-
**12** optional tests of **305** total (required + optional) tests are failing:
181-
182-
- 10 of them are `format`-strings which are not supported.
183-
- big numbers are not working (2)
184-
185218
# Additional features
186219
187220
## Default values
188221
189222
The goal is to create an empty document, based on schema-defined
190223
default-values, recursively populated.
191-

app/json-schema-validate.cpp

+24-25
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,10 @@
11
/*
2-
* Modern C++ JSON schema validator
2+
* JSON schema validator for JSON for modern C++
33
*
4-
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
4+
* Copyright (c) 2016-2019 Patrick Boettcher <[email protected]>.
55
*
6-
* Copyright (c) 2016 Patrick Boettcher <[email protected]>.
6+
* SPDX-License-Identifier: MIT
77
*
8-
* Permission is hereby granted, free of charge, to any person obtaining a
9-
* copy of this software and associated documentation files (the "Software"),
10-
* to deal in the Software without restriction, including without limitation
11-
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
12-
* and/or sell copies of the Software, and to permit persons to whom
13-
* the Software is furnished to do so, subject to the following conditions:
14-
*
15-
* The above copyright notice and this permission notice shall be included in
16-
* all copies or substantial portions of the Software.
17-
*
18-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19-
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20-
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21-
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22-
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
23-
* OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
24-
* THE USE OR OTHER DEALINGS IN THE SOFTWARE.
258
*/
269
#include <json-schema.hpp>
2710

@@ -30,7 +13,7 @@
3013

3114
using nlohmann::json;
3215
using nlohmann::json_uri;
33-
using nlohmann::json_schema_draft4::json_validator;
16+
using nlohmann::json_schema::json_validator;
3417

3518
static void usage(const char *name)
3619
{
@@ -47,9 +30,10 @@ static void usage(const char *name)
4730

4831
static void loader(const json_uri &uri, json &schema)
4932
{
50-
std::fstream lf("." + uri.path());
33+
std::string filename = "./" + uri.path();
34+
std::fstream lf(filename);
5135
if (!lf.good())
52-
throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path());
36+
throw std::invalid_argument("could not open " + uri.url() + " tried with " + filename);
5337

5438
try {
5539
lf >> schema;
@@ -58,6 +42,15 @@ static void loader(const json_uri &uri, json &schema)
5842
}
5943
}
6044

45+
class custom_error_handler : public nlohmann::json_schema::basic_error_handler
46+
{
47+
void error(const std::string &path, const json &instance, const std::string &message) override
48+
{
49+
nlohmann::json_schema::basic_error_handler::error(path, instance, message);
50+
std::cerr << "ERROR: '" << path << "' - '" << instance << "': " << message << "\n";
51+
}
52+
};
53+
6154
int main(int argc, char *argv[])
6255
{
6356
if (argc != 2)
@@ -95,10 +88,16 @@ int main(int argc, char *argv[])
9588

9689
try {
9790
std::cin >> document;
98-
validator.validate(document);
9991
} catch (std::exception &e) {
92+
std::cerr << "json parsing failed: " << e.what() << " at offset: " << std::cin.tellg() << "\n";
93+
return EXIT_FAILURE;
94+
}
95+
96+
custom_error_handler err;
97+
validator.validate(document, err);
98+
99+
if (err) {
100100
std::cerr << "schema validation failed\n";
101-
std::cerr << e.what() << " at offset: " << std::cin.tellg() << "\n";
102101
return EXIT_FAILURE;
103102
}
104103

0 commit comments

Comments
 (0)