Merge remote-tracking branch 'origin/master' into json_pointer-pop-push

This commit is contained in:
garethsb-sony 2019-02-01 09:18:26 +00:00
commit 11b188cfef
10 changed files with 97 additions and 75 deletions

View File

@ -60,7 +60,7 @@ script:
- $CXX --version - $CXX --version
# put json.hpp to nlohmann # 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 # compile and execute unit tests
- mkdir -p build && cd build - mkdir -p build && cd build

View File

@ -46,7 +46,8 @@ set_target_properties(json-schema-validator
install(TARGETS json-schema-validator install(TARGETS json-schema-validator
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib) ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin)
install(DIRECTORY src/ install(DIRECTORY src/
DESTINATION include DESTINATION include
@ -59,12 +60,13 @@ target_include_directories(json-schema-validator
target_compile_features(json-schema-validator target_compile_features(json-schema-validator
PUBLIC PUBLIC
cxx_range_for) # for C++11 - flags 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 target_compile_options(json-schema-validator
PRIVATE PRIVATE
-Wall -Wextra) -Wall -Wextra)
endif() endif()
target_link_libraries(json-schema-validator target_link_libraries(json-schema-validator
PUBLIC PUBLIC
json-hpp) json-hpp)

View File

@ -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). [draft-7 of JSON Schema Validation](http://json-schema.org/schema).
First a disclaimer: *It is work in progress and 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 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 C++](https://github.com/nlohmann/json). This validator is based on this
@ -21,7 +21,7 @@ is rather simple.
# New in version 2 # 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 (a complete rewrite) the API is compatible with the 1.0.0 release. Except for
the namespace which is now `nlohmann::json_schema. 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. only. Please update your schemas.
The primary change in 2 is the way a schema is used. While in version 1 the schema was 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 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 still optimizations to be done, but validation speed has improved by factor 100
or more. 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 By default this is done with exceptions thrown at the users with a helpful
message telling what's wrong with the document while validating. 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 With **2.0.0** the user can pass a `json_scheam::basic_error_handler`-derived
along with the instance to validate to receive a each time a validation error occurs object along with the instance to validate to receive a callback each time a
and decice what to do (throwing, counting, collecting). 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 Another goal was to use Niels Lohmann's JSON-library. This is why the validator
lives in his namespace. 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 the schema type is "integer" or "number". Bignum (i.e. arbitrary precision and
range) is not supported at this time. 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. with `$ref: "file.json#plain"` will not work.
# How to use # 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. 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 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::cout << "About to validate this person:\n"
<< std::setw(2) << person << std::endl; << std::setw(2) << person << std::endl;
try { try {
validator.validate(person); // validate the document validator.validate(person); // validate the document - uses the default throwing error-handler
std::cout << "Validation succeeded\n"; std::cout << "Validation succeeded\n";
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << "Validation failed, here is why: " << e.what() << "\n"; std::cerr << "Validation failed, here is why: " << e.what() << "\n";
@ -190,7 +190,7 @@ int main()
<< std::setw(2) << person << std::endl; << std::setw(2) << person << std::endl;
custom_error_handler err; 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) if (err)
std::cerr << "Validation failed\n"; std::cerr << "Validation failed\n";

View File

@ -55,7 +55,7 @@ int main()
std::cout << "About to validate this person:\n" std::cout << "About to validate this person:\n"
<< std::setw(2) << person << std::endl; << std::setw(2) << person << std::endl;
try { try {
validator.validate(person); // validate the document validator.validate(person); // validate the document - uses the default throwing error-handler
std::cout << "Validation succeeded\n"; std::cout << "Validation succeeded\n";
} catch (const std::exception &e) { } catch (const std::exception &e) {
std::cerr << "Validation failed, here is why: " << e.what() << "\n"; std::cerr << "Validation failed, here is why: " << e.what() << "\n";
@ -78,7 +78,7 @@ int main()
<< std::setw(2) << person << std::endl; << std::setw(2) << person << std::endl;
custom_error_handler err; 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) if (err)
std::cerr << "Validation failed\n"; std::cerr << "Validation failed\n";

View File

@ -24,11 +24,11 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#ifdef NLOHMANN_JSON_VERSION_MAJOR #ifdef NLOHMANN_JSON_VERSION_MAJOR
# if NLOHMANN_JSON_VERSION_MAJOR < 3 || NLOHMANN_JSON_VERSION_MINOR < 5 || NLOHMANN_JSON_VERSION_PATCH < 1 # 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.5.1 or higher" # error "Please use this library with NLohmann's JSON version 3.6.0 or higher"
# endif # endif
#else #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 #endif
// make yourself a home - welcome to nlohmann's namespace // 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 proto_;
std::string hostname_; std::string hostname_;
std::string path_; std::string path_;
nlohmann::json::json_pointer pointer_; json::json_pointer pointer_;
protected: protected:
// decodes a JSON uri and replaces all or part of the currently stored values // 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 hostname() const { return hostname_; }
const std::string path() const { return path_; } 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 url() const { return location(); }
const std::string location() const; const std::string location() const;
@ -92,7 +92,7 @@ public:
json_uri append(const std::string &field) const json_uri append(const std::string &field) const
{ {
json_uri u = *this; json_uri u = *this;
u.pointer_.push_back(field); u.pointer_ /= field;
return u; return u;
} }
@ -121,7 +121,7 @@ class basic_error_handler
bool error_{false}; bool error_{false};
public: 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; error_ = true;
} }

View File

@ -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 const std::string json_uri::location() const

View File

@ -39,7 +39,7 @@ public:
schema(root_schema *root) schema(root_schema *root)
: root_(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, static std::shared_ptr<schema> make(json &schema,
root_schema *root, root_schema *root,
@ -52,7 +52,7 @@ class schema_ref : public schema
const std::string id_; const std::string id_;
std::shared_ptr<schema> target_; 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_) if (target_)
target_->validate(ptr, instance, e); target_->validate(ptr, instance, e);
@ -83,8 +83,8 @@ class root_schema : public schema
std::shared_ptr<schema> root_; std::shared_ptr<schema> root_;
struct schema_file { struct schema_file {
std::map<nlohmann::json::json_pointer, std::shared_ptr<schema>> schemas; std::map<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_ref>> unresolved; // contains all unresolved references from any other file seen during parsing
json unknown_keywords; json unknown_keywords;
}; };
@ -211,7 +211,7 @@ public:
} while (1); } 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_) if (root_)
root_->validate(ptr, instance, e); root_->validate(ptr, instance, e);
@ -230,7 +230,7 @@ class logical_not : public schema
{ {
std::shared_ptr<schema> subschema_; 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; basic_error_handler err;
subschema_->validate(ptr, instance, err); subschema_->validate(ptr, instance, err);
@ -260,7 +260,7 @@ class logical_combination : public schema
{ {
std::vector<std::shared_ptr<schema>> subschemata_; 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; size_t count = 0;
@ -268,27 +268,21 @@ class logical_combination : public schema
basic_error_handler err; basic_error_handler err;
s->validate(ptr, instance, err); s->validate(ptr, instance, err);
if (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
count++; count++;
if (combine_logic == oneOf && count > 1) { if (is_validate_complete(instance, ptr, e, err, count))
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)
return; 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."); 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: public:
logical_combination(json &sch, logical_combination(json &sch,
root_schema *root, root_schema *root,
@ -296,24 +290,43 @@ public:
: schema(root) : schema(root)
{ {
size_t c = 0; 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) for (auto &subschema : sch)
subschemata_.push_back(schema::make(subschema, root, {key, std::to_string(c++)}, uris)); 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 class type_schema : public schema
{ {
std::vector<std::shared_ptr<schema>> type_; std::vector<std::shared_ptr<schema>> type_;
@ -328,7 +341,7 @@ class type_schema : public schema
std::shared_ptr<schema> if_, then_, else_; 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 // depending on the type of instance run the type specific validator - if present
auto type = type_[(uint8_t) instance.type()]; auto type = type_[(uint8_t) instance.type()];
@ -340,8 +353,8 @@ class type_schema : public schema
if (enum_.first) { if (enum_.first) {
bool seen_in_enum = false; bool seen_in_enum = false;
for (auto &e : enum_.second) for (auto &v : enum_.second)
if (instance == e) { if (instance == v) {
seen_in_enum = true; seen_in_enum = true;
break; break;
} }
@ -509,7 +522,7 @@ class string : public schema
return len; 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 (minLength_.first) {
if (utf8_length(instance) < minLength_.second) { if (utf8_length(instance) < minLength_.second) {
@ -591,15 +604,15 @@ class numeric : public schema
std::pair<bool, json::number_float_t> multipleOf_{false, 0}; std::pair<bool, json::number_float_t> multipleOf_{false, 0};
// multipleOf - if the rest of the division is 0 -> OK // multipleOf - if the remainder of the division is 0 -> OK
bool violates_multiple_of(json::number_float_t x) const bool violates_multiple_of(T x) const
{ {
json::number_integer_t n = static_cast<json::number_integer_t>(x / multipleOf_.second); double res = std::remainder(x, multipleOf_.second);
double res = (x - n * multipleOf_.second); double eps = std::nextafter(x, 0) - x;
return fabs(res) > std::numeric_limits<json::number_float_t>::epsilon(); 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 T value = instance; // conversion of json to value_type
@ -658,7 +671,7 @@ public:
class null : public schema 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()) if (!instance.is_null())
e.error(ptr, instance, "expected to be null"); e.error(ptr, instance, "expected to be null");
@ -671,7 +684,7 @@ public:
class boolean_type : public schema 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: public:
boolean_type(json &, root_schema *root) boolean_type(json &, root_schema *root)
@ -681,7 +694,7 @@ public:
class boolean : public schema class boolean : public schema
{ {
bool true_; 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 if (!true_) { // false schema
// empty array // empty array
@ -705,7 +718,7 @@ class required : public schema
{ {
const std::vector<std::string> required_; 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_) for (auto &r : required_)
if (instance.find(r) == instance.end()) if (instance.find(r) == instance.end())
@ -733,7 +746,7 @@ class object : public schema
std::shared_ptr<schema> propertyNames_; 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) if (maxProperties_.first && instance.size() > maxProperties_.second)
e.error(ptr, instance, "too many properties."); e.error(ptr, instance, "too many properties.");
@ -771,7 +784,7 @@ class object : public schema
for (auto &dep : dependencies_) { for (auto &dep : dependencies_) {
auto prop = instance.find(dep.first); 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 dep.second->validate(ptr / dep.first, instance, e); // validate
} }
} }
@ -867,7 +880,7 @@ class array : public schema
std::shared_ptr<schema> contains_; 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) if (maxItems_.first && instance.size() > maxItems_.second)
e.error(ptr, instance, "has too many items."); 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) 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); root_->validate(ptr, instance, err);
} }

View File

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

View File

@ -0,0 +1 @@
1.2

View File

@ -0,0 +1,3 @@
{
"multipleOf": 0.1
}