validator: add some basic format-checking
This commit is contained in:
parent
c4a9cdb280
commit
f42d5fb587
@ -26,11 +26,32 @@
|
||||
#include "json-schema.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <regex>
|
||||
|
||||
using nlohmann::json;
|
||||
using nlohmann::json_uri;
|
||||
using nlohmann::json_schema_draft4::json_validator;
|
||||
|
||||
static void format_check(const std::string &format, const std::string &value)
|
||||
{
|
||||
if (format == "hostname") {
|
||||
// from http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
||||
std::regex re(R"(^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])(\.([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]{0,61}[a-zA-Z0-9]))*$)");
|
||||
if (!std::regex_match(value, re))
|
||||
throw std::invalid_argument(value + " is not a valid hostname.");
|
||||
} else if (format == "ipv4") {
|
||||
std::regex re(R"(^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$)");
|
||||
if (!std::regex_match(value, re))
|
||||
throw std::invalid_argument(value + " is not a IPv4-address.");
|
||||
|
||||
} else if (format == "ipv6") {
|
||||
std::regex re(R"((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])))");
|
||||
if (!std::regex_match(value, re))
|
||||
throw std::invalid_argument(value + " is not a IPv6-address.");
|
||||
} else
|
||||
throw std::logic_error("don't know how to validate " + format);
|
||||
}
|
||||
|
||||
static void loader(const json_uri &uri, json &schema)
|
||||
{
|
||||
std::map<std::string, std::string> external_schemas =
|
||||
@ -80,9 +101,9 @@ int main(void)
|
||||
|
||||
const auto &schema = test_group["schema"];
|
||||
|
||||
json_validator validator(loader);
|
||||
json_validator validator(loader, format_check);
|
||||
|
||||
validator.set_root_schema(schema);
|
||||
validator.set_root_schema(schema);
|
||||
|
||||
for (auto &test_case : test_group["tests"]) {
|
||||
std::cout << " Testing Case " << test_case["description"] << "\n";
|
||||
|
||||
@ -160,18 +160,21 @@ class 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(json &instance, const json &schema_, const std::string &name);
|
||||
void validate_array(json &instance, const json &schema_, const std::string &name);
|
||||
void validate_object(json &instance, const json &schema_, const std::string &name);
|
||||
void validate_string(json &instance, const json &schema, const std::string &name);
|
||||
|
||||
void insert_schema(const json &input, const json_uri &id);
|
||||
|
||||
public:
|
||||
json_validator(std::function<void(const json_uri &, json &)> loader = nullptr)
|
||||
: schema_loader_(loader)
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@ -118,12 +118,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
void not_yet_implemented(const json &schema, const std::string &field, const std::string &type)
|
||||
{
|
||||
if (schema.find(field) != schema.end())
|
||||
throw std::logic_error(field + " for " + type + " is not yet implemented");
|
||||
}
|
||||
|
||||
void validate_type(const json &schema, const std::string &expected_type, const std::string &name)
|
||||
{
|
||||
const auto &type_it = schema.find("type");
|
||||
@ -171,41 +165,6 @@ void validate_enum(json &instance, const json &schema, const std::string &name)
|
||||
throw std::invalid_argument(s.str());
|
||||
}
|
||||
|
||||
void validate_string(json &instance, const json &schema, const std::string &name)
|
||||
{
|
||||
// possible but unhandled keywords
|
||||
not_yet_implemented(schema, "format", "string");
|
||||
|
||||
validate_type(schema, "string", name);
|
||||
|
||||
// minLength
|
||||
auto attr = schema.find("minLength");
|
||||
if (attr != schema.end())
|
||||
if (instance.get<std::string>().size() < attr.value()) {
|
||||
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 (instance.get<std::string>().size() > attr.value()) {
|
||||
std::ostringstream s;
|
||||
s << "'" << name << "' of value '" << instance << "' is too long as per maxLength ("
|
||||
<< attr.value() << ")";
|
||||
throw std::out_of_range(s.str());
|
||||
}
|
||||
|
||||
attr = schema.find("pattern");
|
||||
if (attr != schema.end()) {
|
||||
std::regex re(attr.value().get<std::string>(), std::regex::ECMAScript);
|
||||
if (!std::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>());
|
||||
}
|
||||
}
|
||||
|
||||
void validate_boolean(json & /*instance*/, const json &schema, const std::string &name)
|
||||
{
|
||||
validate_type(schema, "boolean", name);
|
||||
@ -687,5 +646,47 @@ void json_validator::validate_object(json &instance, const json &schema, const s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void json_validator::validate_string(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 (instance.get<std::string>().size() < attr.value()) {
|
||||
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 (instance.get<std::string>().size() > attr.value()) {
|
||||
std::ostringstream s;
|
||||
s << "'" << name << "' of value '" << instance << "' is too long as per maxLength ("
|
||||
<< attr.value() << ")";
|
||||
throw std::out_of_range(s.str());
|
||||
}
|
||||
|
||||
// pattern
|
||||
attr = schema.find("pattern");
|
||||
if (attr != schema.end()) {
|
||||
std::regex re(attr.value().get<std::string>(), std::regex::ECMAScript);
|
||||
if (!std::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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user