Merge remote-tracking branch 'origin/master' into json_pointer-pop-push
This commit is contained in:
commit
11b188cfef
@ -60,7 +60,7 @@ script:
|
||||
- $CXX --version
|
||||
|
||||
# put json.hpp to nlohmann
|
||||
- mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.5.0/json.hpp -O nlohmann/json.hpp
|
||||
- mkdir -p nlohmann && wget https://github.com/nlohmann/json/releases/download/v3.6.0/json.hpp -O nlohmann/json.hpp
|
||||
|
||||
# compile and execute unit tests
|
||||
- mkdir -p build && cd build
|
||||
|
||||
@ -46,7 +46,8 @@ set_target_properties(json-schema-validator
|
||||
|
||||
install(TARGETS json-schema-validator
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib)
|
||||
ARCHIVE DESTINATION lib
|
||||
RUNTIME DESTINATION bin)
|
||||
|
||||
install(DIRECTORY src/
|
||||
DESTINATION include
|
||||
@ -59,12 +60,13 @@ target_include_directories(json-schema-validator
|
||||
target_compile_features(json-schema-validator
|
||||
PUBLIC
|
||||
cxx_range_for) # for C++11 - flags
|
||||
# Enable more compiler warnings, except when using Visual Studio compiler
|
||||
if(NOT MSVC)
|
||||
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
target_compile_options(json-schema-validator
|
||||
PRIVATE
|
||||
-Wall -Wextra)
|
||||
endif()
|
||||
|
||||
target_link_libraries(json-schema-validator
|
||||
PUBLIC
|
||||
json-hpp)
|
||||
|
||||
20
README.md
20
README.md
@ -10,7 +10,7 @@ This is a C++ library for validating JSON documents based on a
|
||||
[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.* Even though a 2.0.0 release is immenent.
|
||||
contributions or hints or discussions are welcome.* Even though a 2.0.0 release is imminent.
|
||||
|
||||
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
|
||||
@ -21,7 +21,7 @@ is rather simple.
|
||||
|
||||
# New in version 2
|
||||
|
||||
Although significant changes have been coorporate to the 2 version
|
||||
Although significant changes have been done for the 2nd version
|
||||
(a complete rewrite) the API is compatible with the 1.0.0 release. Except for
|
||||
the namespace which is now `nlohmann::json_schema.
|
||||
|
||||
@ -29,7 +29,7 @@ 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
|
||||
kept as a JSON-document and used again and again during validation, in version 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.
|
||||
@ -42,9 +42,9 @@ 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 pass 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).
|
||||
With **2.0.0** the user can pass a `json_scheam::basic_error_handler`-derived
|
||||
object along with the instance to validate to receive a callback each time a
|
||||
validation error occurs and decide 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.
|
||||
@ -55,12 +55,12 @@ Numerical validation uses nlohmann integer, unsigned and floating point types, d
|
||||
the schema type is "integer" or "number". Bignum (i.e. arbitrary precision and
|
||||
range) is not supported at this time.
|
||||
|
||||
Currently JSON-URI with "plain name fragments" are not supported. So referring to an URI
|
||||
Currently JSON-URI with "plain name fragments" are not supported: 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.5.0** of NLohmann's
|
||||
The current state of the build-system needs at least version **3.6.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
|
||||
@ -167,7 +167,7 @@ int main()
|
||||
std::cout << "About to validate this person:\n"
|
||||
<< std::setw(2) << person << std::endl;
|
||||
try {
|
||||
validator.validate(person); // validate the document
|
||||
validator.validate(person); // validate the document - uses the default throwing error-handler
|
||||
std::cout << "Validation succeeded\n";
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Validation failed, here is why: " << e.what() << "\n";
|
||||
@ -190,7 +190,7 @@ int main()
|
||||
<< std::setw(2) << person << std::endl;
|
||||
|
||||
custom_error_handler err;
|
||||
validator.validate(person, err); // validate the document - uses the default throwing error-handler
|
||||
validator.validate(person, err); // validate the document
|
||||
|
||||
if (err)
|
||||
std::cerr << "Validation failed\n";
|
||||
|
||||
@ -55,7 +55,7 @@ int main()
|
||||
std::cout << "About to validate this person:\n"
|
||||
<< std::setw(2) << person << std::endl;
|
||||
try {
|
||||
validator.validate(person); // validate the document
|
||||
validator.validate(person); // validate the document - uses the default throwing error-handler
|
||||
std::cout << "Validation succeeded\n";
|
||||
} catch (const std::exception &e) {
|
||||
std::cerr << "Validation failed, here is why: " << e.what() << "\n";
|
||||
@ -78,7 +78,7 @@ int main()
|
||||
<< std::setw(2) << person << std::endl;
|
||||
|
||||
custom_error_handler err;
|
||||
validator.validate(person, err); // validate the document - uses the default throwing error-handler
|
||||
validator.validate(person, err); // validate the document
|
||||
|
||||
if (err)
|
||||
std::cerr << "Validation failed\n";
|
||||
|
||||
@ -24,11 +24,11 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#ifdef NLOHMANN_JSON_VERSION_MAJOR
|
||||
# if NLOHMANN_JSON_VERSION_MAJOR < 3 || NLOHMANN_JSON_VERSION_MINOR < 5 || NLOHMANN_JSON_VERSION_PATCH < 1
|
||||
# error "Please use this library with NLohmann's JSON version 3.5.1 or higher"
|
||||
# if (NLOHMANN_JSON_VERSION_MAJOR * 10000 + NLOHMANN_JSON_VERSION_MINOR * 100 + NLOHMANN_JSON_VERSION_PATCH) < 30600
|
||||
# error "Please use this library with NLohmann's JSON version 3.6.0 or higher"
|
||||
# endif
|
||||
#else
|
||||
# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.5.1"
|
||||
# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.6.0"
|
||||
#endif
|
||||
|
||||
// make yourself a home - welcome to nlohmann's namespace
|
||||
@ -51,7 +51,7 @@ class JSON_SCHEMA_VALIDATOR_API json_uri
|
||||
std::string proto_;
|
||||
std::string hostname_;
|
||||
std::string path_;
|
||||
nlohmann::json::json_pointer pointer_;
|
||||
json::json_pointer pointer_;
|
||||
|
||||
protected:
|
||||
// decodes a JSON uri and replaces all or part of the currently stored values
|
||||
@ -72,7 +72,7 @@ public:
|
||||
const std::string hostname() const { return hostname_; }
|
||||
const std::string path() const { return path_; }
|
||||
|
||||
const nlohmann::json::json_pointer pointer() const { return pointer_; }
|
||||
const json::json_pointer pointer() const { return pointer_; }
|
||||
|
||||
const std::string url() const { return location(); }
|
||||
const std::string location() const;
|
||||
@ -92,7 +92,7 @@ public:
|
||||
json_uri append(const std::string &field) const
|
||||
{
|
||||
json_uri u = *this;
|
||||
u.pointer_.push_back(field);
|
||||
u.pointer_ /= field;
|
||||
return u;
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ class basic_error_handler
|
||||
bool error_{false};
|
||||
|
||||
public:
|
||||
virtual void error(const nlohmann::json::json_pointer & /*path*/, const json & /* instance */, const std::string & /*message*/)
|
||||
virtual void error(const json::json_pointer & /*path*/, const json & /* instance */, const std::string & /*message*/)
|
||||
{
|
||||
error_ = true;
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ void json_uri::update(const std::string &uri)
|
||||
}
|
||||
}
|
||||
|
||||
pointer_ = nlohmann::json::json_pointer(pointer);
|
||||
pointer_ = json::json_pointer(pointer);
|
||||
}
|
||||
|
||||
const std::string json_uri::location() const
|
||||
|
||||
@ -39,7 +39,7 @@ public:
|
||||
schema(root_schema *root)
|
||||
: root_(root) {}
|
||||
|
||||
virtual void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const = 0;
|
||||
virtual void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const = 0;
|
||||
|
||||
static std::shared_ptr<schema> make(json &schema,
|
||||
root_schema *root,
|
||||
@ -52,7 +52,7 @@ class schema_ref : public schema
|
||||
const std::string id_;
|
||||
std::shared_ptr<schema> target_;
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
{
|
||||
if (target_)
|
||||
target_->validate(ptr, instance, e);
|
||||
@ -83,8 +83,8 @@ class root_schema : public schema
|
||||
std::shared_ptr<schema> root_;
|
||||
|
||||
struct schema_file {
|
||||
std::map<nlohmann::json::json_pointer, std::shared_ptr<schema>> schemas;
|
||||
std::map<nlohmann::json::json_pointer, std::shared_ptr<schema_ref>> unresolved; // contains all unresolved references from any other file seen during parsing
|
||||
std::map<json::json_pointer, std::shared_ptr<schema>> schemas;
|
||||
std::map<json::json_pointer, std::shared_ptr<schema_ref>> unresolved; // contains all unresolved references from any other file seen during parsing
|
||||
json unknown_keywords;
|
||||
};
|
||||
|
||||
@ -211,7 +211,7 @@ public:
|
||||
} while (1);
|
||||
}
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
{
|
||||
if (root_)
|
||||
root_->validate(ptr, instance, e);
|
||||
@ -230,7 +230,7 @@ class logical_not : public schema
|
||||
{
|
||||
std::shared_ptr<schema> subschema_;
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
{
|
||||
basic_error_handler err;
|
||||
subschema_->validate(ptr, instance, err);
|
||||
@ -260,7 +260,7 @@ class logical_combination : public schema
|
||||
{
|
||||
std::vector<std::shared_ptr<schema>> subschemata_;
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const final
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
@ -268,27 +268,21 @@ class logical_combination : public schema
|
||||
basic_error_handler err;
|
||||
s->validate(ptr, instance, err);
|
||||
|
||||
if (err) {
|
||||
//sub_schema_err << " one schema failed because: " << e.what() << "\n";
|
||||
if (combine_logic == allOf) {
|
||||
e.error(ptr, instance, "at least one schema has failed, but all of them are required to validate.");
|
||||
return;
|
||||
}
|
||||
} else
|
||||
if (!err)
|
||||
count++;
|
||||
|
||||
if (combine_logic == oneOf && count > 1) {
|
||||
e.error(ptr, instance, "more than one schema has succeeded, but exactly one of them is required to validate.");
|
||||
return;
|
||||
}
|
||||
if (combine_logic == anyOf && count == 1)
|
||||
if (is_validate_complete(instance, ptr, e, err, count))
|
||||
return;
|
||||
}
|
||||
|
||||
if ((combine_logic == anyOf || combine_logic == oneOf) && count == 0)
|
||||
if (count == 0)
|
||||
e.error(ptr, instance, "no validation has succeeded but one of them is required to validate.");
|
||||
}
|
||||
|
||||
// specialized for each of the logical_combination_types
|
||||
static const std::string key;
|
||||
static bool is_validate_complete(const json &, const json::json_pointer &, basic_error_handler &, bool, size_t);
|
||||
|
||||
public:
|
||||
logical_combination(json &sch,
|
||||
root_schema *root,
|
||||
@ -296,24 +290,43 @@ public:
|
||||
: schema(root)
|
||||
{
|
||||
size_t c = 0;
|
||||
std::string key;
|
||||
switch (combine_logic) {
|
||||
case allOf:
|
||||
key = "allOf";
|
||||
break;
|
||||
case oneOf:
|
||||
key = "oneOf";
|
||||
break;
|
||||
case anyOf:
|
||||
key = "anyOf";
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto &subschema : sch)
|
||||
subschemata_.push_back(schema::make(subschema, root, {key, std::to_string(c++)}, uris));
|
||||
|
||||
// value of allOf, anyOf, and oneOf "MUST be a non-empty array"
|
||||
// TODO error/throw? when subschemata_.empty()
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
const std::string logical_combination<allOf>::key = "allOf";
|
||||
template <>
|
||||
const std::string logical_combination<anyOf>::key = "anyOf";
|
||||
template <>
|
||||
const std::string logical_combination<oneOf>::key = "oneOf";
|
||||
|
||||
template <>
|
||||
bool logical_combination<allOf>::is_validate_complete(const json &instance, const json::json_pointer &ptr, basic_error_handler &e, bool err, size_t)
|
||||
{
|
||||
if (err)
|
||||
e.error(ptr, instance, "at least one schema has failed, but all of them are required to validate.");
|
||||
return err;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool logical_combination<anyOf>::is_validate_complete(const json &, const json::json_pointer &, basic_error_handler &, bool, size_t count)
|
||||
{
|
||||
return count == 1;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool logical_combination<oneOf>::is_validate_complete(const json &instance, const json::json_pointer &ptr, basic_error_handler &e, bool, size_t count)
|
||||
{
|
||||
if (count > 1)
|
||||
e.error(ptr, instance, "more than one schema has succeeded, but exactly one of them is required to validate.");
|
||||
return count > 1;
|
||||
}
|
||||
|
||||
class type_schema : public schema
|
||||
{
|
||||
std::vector<std::shared_ptr<schema>> type_;
|
||||
@ -328,7 +341,7 @@ class type_schema : public schema
|
||||
|
||||
std::shared_ptr<schema> if_, then_, else_;
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final
|
||||
{
|
||||
// depending on the type of instance run the type specific validator - if present
|
||||
auto type = type_[(uint8_t) instance.type()];
|
||||
@ -340,8 +353,8 @@ class type_schema : public schema
|
||||
|
||||
if (enum_.first) {
|
||||
bool seen_in_enum = false;
|
||||
for (auto &e : enum_.second)
|
||||
if (instance == e) {
|
||||
for (auto &v : enum_.second)
|
||||
if (instance == v) {
|
||||
seen_in_enum = true;
|
||||
break;
|
||||
}
|
||||
@ -509,7 +522,7 @@ class string : public schema
|
||||
return len;
|
||||
}
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
{
|
||||
if (minLength_.first) {
|
||||
if (utf8_length(instance) < minLength_.second) {
|
||||
@ -591,15 +604,15 @@ class numeric : public schema
|
||||
|
||||
std::pair<bool, json::number_float_t> multipleOf_{false, 0};
|
||||
|
||||
// multipleOf - if the rest of the division is 0 -> OK
|
||||
bool violates_multiple_of(json::number_float_t x) const
|
||||
// multipleOf - if the remainder of the division is 0 -> OK
|
||||
bool violates_multiple_of(T x) const
|
||||
{
|
||||
json::number_integer_t n = static_cast<json::number_integer_t>(x / multipleOf_.second);
|
||||
double res = (x - n * multipleOf_.second);
|
||||
return fabs(res) > std::numeric_limits<json::number_float_t>::epsilon();
|
||||
double res = std::remainder(x, multipleOf_.second);
|
||||
double eps = std::nextafter(x, 0) - x;
|
||||
return std::fabs(res) > std::fabs(eps);
|
||||
}
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
{
|
||||
T value = instance; // conversion of json to value_type
|
||||
|
||||
@ -658,7 +671,7 @@ public:
|
||||
|
||||
class null : public schema
|
||||
{
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
{
|
||||
if (!instance.is_null())
|
||||
e.error(ptr, instance, "expected to be null");
|
||||
@ -671,7 +684,7 @@ public:
|
||||
|
||||
class boolean_type : public schema
|
||||
{
|
||||
void validate(const nlohmann::json::json_pointer &, const json &, basic_error_handler &) const override {}
|
||||
void validate(const json::json_pointer &, const json &, basic_error_handler &) const override {}
|
||||
|
||||
public:
|
||||
boolean_type(json &, root_schema *root)
|
||||
@ -681,7 +694,7 @@ public:
|
||||
class boolean : public schema
|
||||
{
|
||||
bool true_;
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
{
|
||||
if (!true_) { // false schema
|
||||
// empty array
|
||||
@ -705,7 +718,7 @@ class required : public schema
|
||||
{
|
||||
const std::vector<std::string> required_;
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override final
|
||||
{
|
||||
for (auto &r : required_)
|
||||
if (instance.find(r) == instance.end())
|
||||
@ -733,7 +746,7 @@ class object : public schema
|
||||
|
||||
std::shared_ptr<schema> propertyNames_;
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
{
|
||||
if (maxProperties_.first && instance.size() > maxProperties_.second)
|
||||
e.error(ptr, instance, "too many properties.");
|
||||
@ -771,7 +784,7 @@ class object : public schema
|
||||
|
||||
for (auto &dep : dependencies_) {
|
||||
auto prop = instance.find(dep.first);
|
||||
if (prop != instance.end()) // if dependency-property is present in instance
|
||||
if (prop != instance.end()) // if dependency-property is present in instance
|
||||
dep.second->validate(ptr / dep.first, instance, e); // validate
|
||||
}
|
||||
}
|
||||
@ -867,7 +880,7 @@ class array : public schema
|
||||
|
||||
std::shared_ptr<schema> contains_;
|
||||
|
||||
void validate(const nlohmann::json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
void validate(const json::json_pointer &ptr, const json &instance, basic_error_handler &e) const override
|
||||
{
|
||||
if (maxItems_.first && instance.size() > maxItems_.second)
|
||||
e.error(ptr, instance, "has too many items.");
|
||||
@ -1109,7 +1122,7 @@ void json_validator::validate(const json &instance)
|
||||
|
||||
void json_validator::validate(const json &instance, basic_error_handler &err)
|
||||
{
|
||||
nlohmann::json::json_pointer ptr;
|
||||
json::json_pointer ptr;
|
||||
root_->validate(ptr, instance, err);
|
||||
}
|
||||
|
||||
|
||||
3
test/issue-48/CMakeLists.txt
Normal file
3
test/issue-48/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
add_test_simple_schema(Issue::48
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/schema.json
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/instance.json)
|
||||
1
test/issue-48/instance.json
Normal file
1
test/issue-48/instance.json
Normal file
@ -0,0 +1 @@
|
||||
1.2
|
||||
3
test/issue-48/schema.json
Normal file
3
test/issue-48/schema.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"multipleOf": 0.1
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user