validator: add some basic format-checking

This commit is contained in:
Patrick Boettcher 2016-12-28 13:27:16 +01:00
parent c4a9cdb280
commit f42d5fb587
3 changed files with 70 additions and 45 deletions

View File

@ -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";

View File

@ -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)
{
}

View File

@ -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);
}
}
}
}