739 lines
21 KiB
C++
739 lines
21 KiB
C++
|
/*
|
||
|
* Modern C++ JSON schema validator
|
||
|
*
|
||
|
* Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
||
|
*
|
||
|
* Copyright (c) 2016 Patrick Boettcher <patrick.boettcher@posteo.de>.
|
||
|
*
|
||
|
* 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 <set>
|
||
|
|
||
|
using nlohmann::json;
|
||
|
using nlohmann::json_uri;
|
||
|
|
||
|
#ifdef JSON_SCHEMA_BOOST_REGEX
|
||
|
#include <boost/regex.hpp>
|
||
|
#define REGEX_NAMESPACE boost
|
||
|
#elif defined(JSON_SCHEMA_NO_REGEX)
|
||
|
#define NO_STD_REGEX
|
||
|
#else
|
||
|
#include <regex>
|
||
|
#define REGEX_NAMESPACE std
|
||
|
#endif
|
||
|
|
||
|
namespace
|
||
|
{
|
||
|
|
||
|
class resolver
|
||
|
{
|
||
|
void resolve(json &schema, json_uri id)
|
||
|
{
|
||
|
// look for the id-field in this schema
|
||
|
auto fid = schema.find("id");
|
||
|
|
||
|
// found?
|
||
|
if (fid != schema.end() &&
|
||
|
fid.value().type() == json::value_t::string)
|
||
|
id = id.derive(fid.value()); // resolve to a full id with URL + path based on the parent
|
||
|
|
||
|
// already existing - error
|
||
|
if (schema_refs.find(id) != schema_refs.end())
|
||
|
throw std::invalid_argument("schema " + id.to_string() + " already present in local resolver");
|
||
|
|
||
|
// store a raw pointer to this (sub-)schema referenced by its absolute json_uri
|
||
|
// this (sub-)schema is part of a schema stored inside schema_store_ so we can the a raw-pointer-ref
|
||
|
schema_refs[id] = &schema;
|
||
|
|
||
|
for (auto i = schema.begin(), end = schema.end(); i != end; ++i) {
|
||
|
// FIXME: this inhibits the user adding properties with the key "default"
|
||
|
if (i.key() == "default") /* default value can be objects, but are not schemas */
|
||
|
continue;
|
||
|
|
||
|
switch (i.value().type()) {
|
||
|
|
||
|
case json::value_t::object: // child is object, it is a schema
|
||
|
resolve(i.value(), id.append(json_uri::escape(i.key())));
|
||
|
break;
|
||
|
|
||
|
case json::value_t::array: {
|
||
|
std::size_t index = 0;
|
||
|
auto child_id = id.append(json_uri::escape(i.key()));
|
||
|
for (auto &v : i.value()) {
|
||
|
if (v.type() == json::value_t::object) // array element is object
|
||
|
resolve(v, child_id.append(std::to_string(index)));
|
||
|
index++;
|
||
|
}
|
||
|
} break;
|
||
|
|
||
|
case json::value_t::string:
|
||
|
if (i.key() == "$ref") {
|
||
|
json_uri ref = id.derive(i.value());
|
||
|
i.value() = ref.to_string();
|
||
|
refs.insert(ref);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::set<json_uri> refs;
|
||
|
|
||
|
public:
|
||
|
std::set<json_uri> undefined_refs;
|
||
|
|
||
|
std::map<json_uri, const json *> schema_refs;
|
||
|
|
||
|
resolver(json &schema, json_uri id)
|
||
|
{
|
||
|
// if schema has an id use it as name and to retrieve the namespace (URL)
|
||
|
auto fid = schema.find("id");
|
||
|
if (fid != schema.end())
|
||
|
id = id.derive(fid.value());
|
||
|
|
||
|
resolve(schema, id);
|
||
|
|
||
|
// refs now contains all references
|
||
|
//
|
||
|
// local references should be resolvable inside the same URL
|
||
|
//
|
||
|
// undefined_refs will only contain external references
|
||
|
for (auto r : refs) {
|
||
|
if (schema_refs.find(r) == schema_refs.end()) {
|
||
|
if (r.url() == id.url()) // same url means referencing a sub-schema
|
||
|
// of the same document, which has not been found
|
||
|
throw std::invalid_argument("sub-schema " + r.pointer().to_string() +
|
||
|
" in schema " + id.to_string() + " not found");
|
||
|
undefined_refs.insert(r.url());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void validate_type(const json &schema, const std::string &expected_type, const std::string &name)
|
||
|
{
|
||
|
const auto &type_it = schema.find("type");
|
||
|
if (type_it == schema.end())
|
||
|
/* TODO something needs to be done here, I think */
|
||
|
return;
|
||
|
|
||
|
const auto &type_instance = type_it.value();
|
||
|
|
||
|
// any of the types in this array
|
||
|
if (type_instance.type() == json::value_t::array) {
|
||
|
if ((std::find(type_instance.begin(),
|
||
|
type_instance.end(),
|
||
|
expected_type) != type_instance.end()) ||
|
||
|
(expected_type == "integer" &&
|
||
|
std::find(type_instance.begin(),
|
||
|
type_instance.end(),
|
||
|
"number") != type_instance.end()))
|
||
|
return;
|
||
|
|
||
|
std::ostringstream s;
|
||
|
s << expected_type << " is not any of " << type_instance << " for " << name;
|
||
|
throw std::invalid_argument(s.str());
|
||
|
|
||
|
} else { // type_instance is a string
|
||
|
if (type_instance == expected_type ||
|
||
|
(type_instance == "number" && expected_type == "integer"))
|
||
|
return;
|
||
|
|
||
|
throw std::invalid_argument(name + " is " + expected_type +
|
||
|
", but required type is " + type_instance.get<std::string>());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void validate_enum(const json &instance, const json &schema, const std::string &name)
|
||
|
{
|
||
|
const auto &enum_value = schema.find("enum");
|
||
|
if (enum_value == schema.end())
|
||
|
return;
|
||
|
|
||
|
if (std::find(enum_value.value().begin(), enum_value.value().end(), instance) != enum_value.value().end())
|
||
|
return;
|
||
|
|
||
|
std::ostringstream s;
|
||
|
s << "invalid enum-value '" << instance << "' "
|
||
|
<< "for instance '" << name << "'. Candidates are " << enum_value.value() << ".";
|
||
|
|
||
|
throw std::invalid_argument(s.str());
|
||
|
}
|
||
|
|
||
|
void validate_boolean(const json & /*instance*/, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "boolean", name);
|
||
|
}
|
||
|
|
||
|
void validate_numeric(const json &schema, const std::string &name, double value)
|
||
|
{
|
||
|
// multipleOf - if the rest of the division is 0 -> OK
|
||
|
const auto &multipleOf = schema.find("multipleOf");
|
||
|
if (multipleOf != schema.end()) {
|
||
|
if (multipleOf.value().get<double>() != 0.0) {
|
||
|
|
||
|
double v = value;
|
||
|
v /= multipleOf.value().get<double>();
|
||
|
|
||
|
if (v != (double) (long) v)
|
||
|
throw std::out_of_range(name + " is not a multiple ...");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const auto &maximum = schema.find("maximum");
|
||
|
if (maximum != schema.end()) {
|
||
|
double maxi = maximum.value();
|
||
|
auto ex = std::out_of_range(name + " exceeds maximum of " + std::to_string(maxi));
|
||
|
if (schema.find("exclusiveMaximum") != schema.end()) {
|
||
|
if (value >= maxi)
|
||
|
throw ex;
|
||
|
} else {
|
||
|
if (value > maxi)
|
||
|
throw ex;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const auto &minimum = schema.find("minimum");
|
||
|
if (minimum != schema.end()) {
|
||
|
double mini = minimum.value();
|
||
|
auto ex = std::out_of_range(name + " exceeds minimum of " + std::to_string(mini));
|
||
|
if (schema.find("exclusiveMinimum") != schema.end()) {
|
||
|
if (value <= mini)
|
||
|
throw ex;
|
||
|
} else {
|
||
|
if (value < mini)
|
||
|
throw ex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void validate_integer(const json &instance, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "integer", name);
|
||
|
validate_numeric(schema, name, instance.get<int>());
|
||
|
}
|
||
|
|
||
|
void validate_unsigned(const json &instance, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "integer", name);
|
||
|
validate_numeric(schema, name, instance.get<unsigned>());
|
||
|
}
|
||
|
|
||
|
void validate_float(const json &instance, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "number", name);
|
||
|
validate_numeric(schema, name, instance.get<double>());
|
||
|
}
|
||
|
|
||
|
void validate_null(const json & /*instance*/, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "null", name);
|
||
|
}
|
||
|
|
||
|
} // anonymous namespace
|
||
|
|
||
|
namespace nlohmann
|
||
|
{
|
||
|
namespace json_schema_draft4
|
||
|
{
|
||
|
|
||
|
void json_validator::insert_schema(const json &input, const json_uri &id)
|
||
|
{
|
||
|
// allocate create a copy for later storage - if resolving reference works
|
||
|
std::shared_ptr<json> schema = std::make_shared<json>(input);
|
||
|
|
||
|
do {
|
||
|
// resolve all local schemas and references
|
||
|
resolver r(*schema, id);
|
||
|
|
||
|
// check whether all undefined schema references can be resolved with existing ones
|
||
|
std::set<json_uri> undefined;
|
||
|
for (auto &ref : r.undefined_refs)
|
||
|
if (schema_refs_.find(ref) == schema_refs_.end()) // exact schema reference not found
|
||
|
undefined.insert(ref);
|
||
|
|
||
|
if (undefined.size() == 0) { // no undefined references
|
||
|
// now insert all schema-references
|
||
|
// check whether all schema-references are new
|
||
|
for (auto &sref : r.schema_refs) {
|
||
|
if (schema_refs_.find(sref.first) != schema_refs_.end())
|
||
|
// HACK(syoyo): Skip duplicated schema.
|
||
|
break;
|
||
|
//throw std::invalid_argument("schema " + sref.first.to_string() + " already present in validator.");
|
||
|
}
|
||
|
// no undefined references and no duplicated schema - store the schema
|
||
|
schema_store_.push_back(schema);
|
||
|
|
||
|
// and insert all references
|
||
|
schema_refs_.insert(r.schema_refs.begin(), r.schema_refs.end());
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (schema_loader_ == nullptr)
|
||
|
throw std::invalid_argument("schema contains undefined references to other schemas, needed schema-loader.");
|
||
|
|
||
|
for (auto undef : undefined) {
|
||
|
json ext;
|
||
|
|
||
|
schema_loader_(undef, ext);
|
||
|
insert_schema(ext, undef.url());
|
||
|
}
|
||
|
} while (1);
|
||
|
|
||
|
// store the document root-schema
|
||
|
if (id == json_uri("#"))
|
||
|
root_schema_ = schema;
|
||
|
}
|
||
|
|
||
|
void json_validator::validate(const json &instance)
|
||
|
{
|
||
|
if (root_schema_ == nullptr)
|
||
|
throw std::invalid_argument("no root-schema has been inserted. Cannot validate an instance without it.");
|
||
|
|
||
|
validate(instance, *root_schema_, "root");
|
||
|
}
|
||
|
|
||
|
void json_validator::set_root_schema(const json &schema)
|
||
|
{
|
||
|
insert_schema(schema, json_uri("#"));
|
||
|
}
|
||
|
|
||
|
void json_validator::validate(const json &instance, const json &schema_, const std::string &name)
|
||
|
{
|
||
|
const json *schema = &schema_;
|
||
|
|
||
|
// $ref resolution
|
||
|
do {
|
||
|
const auto &ref = schema->find("$ref");
|
||
|
if (ref == schema->end())
|
||
|
break;
|
||
|
|
||
|
auto it = schema_refs_.find(ref.value().get<std::string>());
|
||
|
|
||
|
if (it == schema_refs_.end())
|
||
|
throw std::invalid_argument("schema reference " + ref.value().get<std::string>() + " not found. Make sure all schemas have been inserted before validation.");
|
||
|
|
||
|
schema = it->second;
|
||
|
} while (1); // loop in case of nested refs
|
||
|
|
||
|
// not
|
||
|
const auto attr = schema->find("not");
|
||
|
if (attr != schema->end()) {
|
||
|
bool ok;
|
||
|
|
||
|
try {
|
||
|
validate(instance, attr.value(), name);
|
||
|
ok = false;
|
||
|
} catch (std::exception &) {
|
||
|
ok = true;
|
||
|
}
|
||
|
if (!ok)
|
||
|
throw std::invalid_argument("schema match for " + name + " but a not-match is defined by schema.");
|
||
|
return; // return here - not cannot be mixed with based-schemas?
|
||
|
}
|
||
|
|
||
|
// allOf, anyOf, oneOf
|
||
|
const json *combined_schemas = nullptr;
|
||
|
enum {
|
||
|
none,
|
||
|
allOf,
|
||
|
anyOf,
|
||
|
oneOf
|
||
|
} combine_logic = none;
|
||
|
|
||
|
{
|
||
|
const auto &attr = schema->find("allOf");
|
||
|
if (attr != schema->end()) {
|
||
|
combine_logic = allOf;
|
||
|
combined_schemas = &attr.value();
|
||
|
}
|
||
|
}
|
||
|
{
|
||
|
const auto &attr = schema->find("anyOf");
|
||
|
if (attr != schema->end()) {
|
||
|
combine_logic = anyOf;
|
||
|
combined_schemas = &attr.value();
|
||
|
}
|
||
|
}
|
||
|
{
|
||
|
const auto &attr = schema->find("oneOf");
|
||
|
if (attr != schema->end()) {
|
||
|
combine_logic = oneOf;
|
||
|
combined_schemas = &attr.value();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (combine_logic != none) {
|
||
|
std::size_t count = 0;
|
||
|
std::ostringstream sub_schema_err;
|
||
|
|
||
|
for (const auto &s : *combined_schemas) {
|
||
|
try {
|
||
|
validate(instance, s, name);
|
||
|
count++;
|
||
|
} catch (std::exception &e) {
|
||
|
sub_schema_err << " one schema failed because: " << e.what() << "\n";
|
||
|
|
||
|
if (combine_logic == allOf)
|
||
|
throw std::out_of_range("At least one schema has failed for " + name + " where allOf them were requested.\n" + sub_schema_err.str());
|
||
|
}
|
||
|
if (combine_logic == oneOf && count > 1)
|
||
|
throw std::out_of_range("More than one schema has succeeded for " + name + " where only oneOf them was requested.\n" + sub_schema_err.str());
|
||
|
}
|
||
|
if ((combine_logic == anyOf || combine_logic == oneOf) && count == 0)
|
||
|
throw std::out_of_range("No schema has succeeded for " + name + " but anyOf/oneOf them should have worked.\n" + sub_schema_err.str());
|
||
|
}
|
||
|
|
||
|
// check (base) schema
|
||
|
validate_enum(instance, *schema, name);
|
||
|
|
||
|
switch (instance.type()) {
|
||
|
case json::value_t::object:
|
||
|
validate_object(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::array:
|
||
|
validate_array(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::string:
|
||
|
validate_string(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::number_unsigned:
|
||
|
validate_unsigned(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::number_integer:
|
||
|
validate_integer(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::number_float:
|
||
|
validate_float(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::boolean:
|
||
|
validate_boolean(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::null:
|
||
|
validate_null(instance, *schema, name);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
assert(0 && "unexpected instance type for validation");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void json_validator::validate_array(const json &instance, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "array", name);
|
||
|
|
||
|
// maxItems
|
||
|
const auto &maxItems = schema.find("maxItems");
|
||
|
if (maxItems != schema.end())
|
||
|
if (instance.size() > maxItems.value().get<size_t>())
|
||
|
throw std::out_of_range(name + " has too many items.");
|
||
|
|
||
|
// minItems
|
||
|
const auto &minItems = schema.find("minItems");
|
||
|
if (minItems != schema.end())
|
||
|
if (instance.size() < minItems.value().get<size_t>())
|
||
|
throw std::out_of_range(name + " has too few items.");
|
||
|
|
||
|
// uniqueItems
|
||
|
const auto &uniqueItems = schema.find("uniqueItems");
|
||
|
if (uniqueItems != schema.end())
|
||
|
if (uniqueItems.value().get<bool>() == true) {
|
||
|
std::set<json> array_to_set;
|
||
|
for (auto v : instance) {
|
||
|
auto ret = array_to_set.insert(v);
|
||
|
if (ret.second == false)
|
||
|
throw std::out_of_range(name + " should have only unique items.");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// items and additionalItems
|
||
|
// default to empty schemas
|
||
|
auto items_iter = schema.find("items");
|
||
|
json items = {};
|
||
|
if (items_iter != schema.end())
|
||
|
items = items_iter.value();
|
||
|
|
||
|
auto additionalItems_iter = schema.find("additionalItems");
|
||
|
json additionalItems = {};
|
||
|
if (additionalItems_iter != schema.end())
|
||
|
additionalItems = additionalItems_iter.value();
|
||
|
|
||
|
size_t i = 0;
|
||
|
bool validation_done = false;
|
||
|
|
||
|
for (auto &value : instance) {
|
||
|
std::string sub_name = name + "[" + std::to_string(i) + "]";
|
||
|
|
||
|
switch (items.type()) {
|
||
|
|
||
|
case json::value_t::array:
|
||
|
|
||
|
if (i < items.size())
|
||
|
validate(value, items[i], sub_name);
|
||
|
else {
|
||
|
switch (additionalItems.type()) { // items is an array
|
||
|
// we need to take into consideration additionalItems
|
||
|
case json::value_t::object:
|
||
|
validate(value, additionalItems, sub_name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::boolean:
|
||
|
if (additionalItems.get<bool>() == false)
|
||
|
throw std::out_of_range("additional values in array are not allowed for " + sub_name);
|
||
|
else
|
||
|
validation_done = true;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
|
||
|
case json::value_t::object: // items is a schema
|
||
|
validate(value, items, sub_name);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
if (validation_done)
|
||
|
break;
|
||
|
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void json_validator::validate_object(const json &instance, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "object", name);
|
||
|
|
||
|
json properties = {};
|
||
|
if (schema.find("properties") != schema.end())
|
||
|
properties = schema["properties"];
|
||
|
|
||
|
#if 0
|
||
|
// check for default values of properties
|
||
|
// and insert them into this object, if they don't exists
|
||
|
// works only for object properties for the moment
|
||
|
if (default_value_insertion)
|
||
|
for (auto it = properties.begin(); it != properties.end(); ++it) {
|
||
|
|
||
|
const auto &default_value = it.value().find("default");
|
||
|
if (default_value == it.value().end())
|
||
|
continue; /* no default value -> continue */
|
||
|
|
||
|
if (instance.find(it.key()) != instance.end())
|
||
|
continue; /* value is present */
|
||
|
|
||
|
/* create element from default value */
|
||
|
instance[it.key()] = default_value.value();
|
||
|
}
|
||
|
#endif
|
||
|
// maxProperties
|
||
|
const auto &maxProperties = schema.find("maxProperties");
|
||
|
if (maxProperties != schema.end())
|
||
|
if (instance.size() > maxProperties.value().get<size_t>())
|
||
|
throw std::out_of_range(name + " has too many properties.");
|
||
|
|
||
|
// minProperties
|
||
|
const auto &minProperties = schema.find("minProperties");
|
||
|
if (minProperties != schema.end())
|
||
|
if (instance.size() < minProperties.value().get<size_t>())
|
||
|
throw std::out_of_range(name + " has too few properties.");
|
||
|
|
||
|
// additionalProperties
|
||
|
enum {
|
||
|
True,
|
||
|
False,
|
||
|
Object
|
||
|
} additionalProperties = True;
|
||
|
|
||
|
const auto &additionalPropertiesVal = schema.find("additionalProperties");
|
||
|
if (additionalPropertiesVal != schema.end()) {
|
||
|
if (additionalPropertiesVal.value().type() == json::value_t::boolean)
|
||
|
additionalProperties = additionalPropertiesVal.value().get<bool>() == true ? True : False;
|
||
|
else
|
||
|
additionalProperties = Object;
|
||
|
}
|
||
|
|
||
|
// patternProperties
|
||
|
json patternProperties = {};
|
||
|
if (schema.find("patternProperties") != schema.end())
|
||
|
patternProperties = schema["patternProperties"];
|
||
|
|
||
|
// check all elements in object
|
||
|
for (auto child = instance.begin(); child != instance.end(); ++child) {
|
||
|
std::string child_name = name + "." + child.key();
|
||
|
|
||
|
bool property_or_patternProperties_has_validated = false;
|
||
|
// is this a property which is described in the schema
|
||
|
const auto &object_prop = properties.find(child.key());
|
||
|
if (object_prop != properties.end()) {
|
||
|
// validate the element with its schema
|
||
|
validate(child.value(), object_prop.value(), child_name);
|
||
|
property_or_patternProperties_has_validated = true;
|
||
|
}
|
||
|
|
||
|
for (auto pp = patternProperties.begin();
|
||
|
pp != patternProperties.end(); ++pp) {
|
||
|
#ifndef NO_STD_REGEX
|
||
|
REGEX_NAMESPACE::regex re(pp.key(), REGEX_NAMESPACE::regex::ECMAScript);
|
||
|
|
||
|
if (REGEX_NAMESPACE::regex_search(child.key(), re)) {
|
||
|
validate(child.value(), pp.value(), child_name);
|
||
|
property_or_patternProperties_has_validated = true;
|
||
|
}
|
||
|
#else
|
||
|
// accept everything in case of a patternProperty
|
||
|
property_or_patternProperties_has_validated = true;
|
||
|
break;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
if (property_or_patternProperties_has_validated)
|
||
|
continue;
|
||
|
|
||
|
switch (additionalProperties) {
|
||
|
case True:
|
||
|
break;
|
||
|
|
||
|
case Object:
|
||
|
validate(child.value(), additionalPropertiesVal.value(), child_name);
|
||
|
break;
|
||
|
|
||
|
case False:
|
||
|
throw std::invalid_argument("unknown property '" + child.key() + "' in object '" + name + "'");
|
||
|
break;
|
||
|
};
|
||
|
}
|
||
|
|
||
|
// required
|
||
|
const auto &required = schema.find("required");
|
||
|
if (required != schema.end())
|
||
|
for (const auto &element : required.value()) {
|
||
|
if (instance.find(element) == instance.end()) {
|
||
|
throw std::invalid_argument("required element '" + element.get<std::string>() +
|
||
|
"' not found in object '" + name + "'");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// dependencies
|
||
|
const auto &dependencies = schema.find("dependencies");
|
||
|
if (dependencies == schema.end())
|
||
|
return;
|
||
|
|
||
|
for (auto dep = dependencies.value().cbegin();
|
||
|
dep != dependencies.value().cend();
|
||
|
++dep) {
|
||
|
|
||
|
// property not present in this instance - next
|
||
|
if (instance.find(dep.key()) == instance.end())
|
||
|
continue;
|
||
|
|
||
|
std::string sub_name = name + ".dependency-of-" + dep.key();
|
||
|
|
||
|
switch (dep.value().type()) {
|
||
|
|
||
|
case json::value_t::object:
|
||
|
validate(instance, dep.value(), sub_name);
|
||
|
break;
|
||
|
|
||
|
case json::value_t::array:
|
||
|
for (const auto &prop : dep.value())
|
||
|
if (instance.find(prop) == instance.end())
|
||
|
throw std::invalid_argument("failed dependency for " + sub_name + ". Need property " + prop.get<std::string>());
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static std::size_t utf8_length(const std::string &s)
|
||
|
{
|
||
|
size_t len = 0;
|
||
|
for (const unsigned char &c : s)
|
||
|
if ((c & 0xc0) != 0x80)
|
||
|
len++;
|
||
|
return len;
|
||
|
}
|
||
|
|
||
|
void json_validator::validate_string(const json &instance, const json &schema, const std::string &name)
|
||
|
{
|
||
|
validate_type(schema, "string", name);
|
||
|
|
||
|
// minLength
|
||
|
auto attr = schema.find("minLength");
|
||
|
if (attr != schema.end())
|
||
|
if (utf8_length(instance) < attr.value().get<size_t>()) {
|
||
|
std::ostringstream s;
|
||
|
s << "'" << name << "' of value '" << instance << "' is too short as per minLength ("
|
||
|
<< attr.value() << ")";
|
||
|
throw std::out_of_range(s.str());
|
||
|
}
|
||
|
|
||
|
// maxLength
|
||
|
attr = schema.find("maxLength");
|
||
|
if (attr != schema.end())
|
||
|
if (utf8_length(instance) > attr.value().get<size_t>()) {
|
||
|
std::ostringstream s;
|
||
|
s << "'" << name << "' of value '" << instance << "' is too long as per maxLength ("
|
||
|
<< attr.value() << ")";
|
||
|
throw std::out_of_range(s.str());
|
||
|
}
|
||
|
|
||
|
#ifndef NO_STD_REGEX
|
||
|
// pattern
|
||
|
attr = schema.find("pattern");
|
||
|
if (attr != schema.end()) {
|
||
|
REGEX_NAMESPACE::regex re(attr.value().get<std::string>(), REGEX_NAMESPACE::regex::ECMAScript);
|
||
|
if (!REGEX_NAMESPACE::regex_search(instance.get<std::string>(), re))
|
||
|
throw std::invalid_argument(instance.get<std::string>() + " does not match regex pattern: " + attr.value().get<std::string>() + " for " + name);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// format
|
||
|
attr = schema.find("format");
|
||
|
if (attr != schema.end()) {
|
||
|
if (format_check_ == nullptr)
|
||
|
throw std::logic_error("A format checker was not provided but a format-attribute for this string is present. " +
|
||
|
name + " cannot be validated for " + attr.value().get<std::string>());
|
||
|
format_check_(attr.value(), instance);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|