Add json-schema-validator library

This commit is contained in:
qu1ck 2021-03-11 21:59:23 -08:00 committed by Seth Hillbrand
parent 2ba41c9b27
commit 1d17e12371
12 changed files with 2430 additions and 0 deletions

View File

@ -19,6 +19,7 @@ Licensed under MIT:
- tinyspline_lib in thirdparty/tinyspline_lib
- nlohmann/json in thirdparty/nlohmann_json
- nlohmann/fifo_map in thirdparty/nlohmann_json
- pboettch/json-schema-validator in thirdparty/json_schema_validator
- picoSHA2 in thirdparty/picosha2
Licensed under GPLv2 (or later):
- dxflib in thirdparty/dxflib_qcad

View File

@ -38,3 +38,4 @@ add_subdirectory( tinyspline_lib )
add_subdirectory( potrace )
add_subdirectory( nlohmann_json )
add_subdirectory( picosha2 )
add_subdirectory( json_schema_validator )

View File

@ -0,0 +1,17 @@
add_library( nlohmann_json_schema_validator
json-schema-draft7.json.cpp
json-uri.cpp
json-validator.cpp
json-patch.cpp
string-format-check.cpp
)
target_include_directories( nlohmann_json_schema_validator
PUBLIC
$<INSTALL_INTERFACE:include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
)
target_link_libraries( nlohmann_json_schema_validator
PUBLIC nlohmann_json
)

View File

@ -0,0 +1,22 @@
Modern C++ JSON schema validator is licensed under the MIT License
<http://opensource.org/licenses/MIT>:
Copyright (c) 2016 Patrick Boettcher
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.

View File

@ -0,0 +1,7 @@
This directory contains the pboettch/json-schema-validator project
from https://github.com/pboettch/json-schema-validator
with a patch to allow validating objects against specific sub definitions
in a schema from
https://github.com/pboettch/json-schema-validator/pull/154
The project is licensed under MIT, with the license text in this directory.

View File

@ -0,0 +1,115 @@
#include "json-patch.hpp"
#include <nlohmann/json-schema.hpp>
namespace
{
// originally from http://jsonpatch.com/, http://json.schemastore.org/json-patch
// with fixes
const nlohmann::json patch_schema = R"patch({
"title": "JSON schema for JSONPatch files",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"oneOf": [
{
"additionalProperties": false,
"required": [ "value", "op", "path"],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "add", "replace", "test" ]
},
"value": {
"description": "The value to add, replace or test."
}
}
},
{
"additionalProperties": false,
"required": [ "op", "path"],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "remove" ]
}
}
},
{
"additionalProperties": false,
"required": [ "from", "op", "path" ],
"properties": {
"path" : { "$ref": "#/definitions/path" },
"op": {
"description": "The operation to perform.",
"type": "string",
"enum": [ "move", "copy" ]
},
"from": {
"$ref": "#/definitions/path",
"description": "A JSON Pointer path pointing to the location to move/copy from."
}
}
}
]
},
"definitions": {
"path": {
"description": "A JSON Pointer path.",
"type": "string"
}
}
})patch"_json;
} // namespace
namespace nlohmann
{
json_patch::json_patch(json &&patch)
: j_(std::move(patch))
{
validateJsonPatch(j_);
}
json_patch::json_patch(const json &patch)
: j_(std::move(patch))
{
validateJsonPatch(j_);
}
json_patch &json_patch::add(const json::json_pointer &ptr, json value)
{
j_.push_back(json{{"op", "add"}, {"path", ptr}, {"value", std::move(value)}});
return *this;
}
json_patch &json_patch::replace(const json::json_pointer &ptr, json value)
{
j_.push_back(json{{"op", "replace"}, {"path", ptr}, {"value", std::move(value)}});
return *this;
}
json_patch &json_patch::remove(const json::json_pointer &ptr)
{
j_.push_back(json{{"op", "remove"}, {"path", ptr}});
return *this;
}
void json_patch::validateJsonPatch(json const &patch)
{
// static put here to have it created at the first usage of validateJsonPatch
static nlohmann::json_schema::json_validator patch_validator(patch_schema);
patch_validator.validate(patch);
for (auto const &op : patch)
json::json_pointer(op["path"].get<std::string>());
}
} // namespace nlohmann

View File

@ -0,0 +1,38 @@
#pragma once
#include <nlohmann/json.hpp>
#include <string>
namespace nlohmann
{
class JsonPatchFormatException : public std::exception
{
public:
explicit JsonPatchFormatException(std::string msg)
: ex_{std::move(msg)} {}
inline const char *what() const noexcept override final { return ex_.c_str(); }
private:
std::string ex_;
};
class json_patch
{
public:
json_patch() = default;
json_patch(json &&patch);
json_patch(const json &patch);
json_patch &add(const json::json_pointer &, json value);
json_patch &replace(const json::json_pointer &, json value);
json_patch &remove(const json::json_pointer &);
operator json() const { return j_; }
private:
json j_;
static void validateJsonPatch(json const &patch);
};
} // namespace nlohmann

View File

@ -0,0 +1,185 @@
/*
* JSON schema validator for JSON for modern C++
*
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* SPDX-License-Identifier: MIT
*
*/
#include <nlohmann/json.hpp>
namespace nlohmann
{
namespace json_schema
{
json draft7_schema_builtin = R"( {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": { "$ref": "#" }
},
"nonNegativeInteger": {
"type": "integer",
"minimum": 0
},
"nonNegativeIntegerDefault0": {
"allOf": [
{ "$ref": "#/definitions/nonNegativeInteger" },
{ "default": 0 }
]
},
"simpleTypes": {
"enum": [
"array",
"boolean",
"integer",
"null",
"number",
"object",
"string"
]
},
"stringArray": {
"type": "array",
"items": { "type": "string" },
"uniqueItems": true,
"default": []
}
},
"type": ["object", "boolean"],
"properties": {
"$id": {
"type": "string",
"format": "uri-reference"
},
"$schema": {
"type": "string",
"format": "uri"
},
"$ref": {
"type": "string",
"format": "uri-reference"
},
"$comment": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": true,
"readOnly": {
"type": "boolean",
"default": false
},
"examples": {
"type": "array",
"items": true
},
"multipleOf": {
"type": "number",
"exclusiveMinimum": 0
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "number"
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "number"
},
"maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
"minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": { "$ref": "#" },
"items": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/schemaArray" }
],
"default": true
},
"maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
"minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"uniqueItems": {
"type": "boolean",
"default": false
},
"contains": { "$ref": "#" },
"maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
"minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
"required": { "$ref": "#/definitions/stringArray" },
"additionalProperties": { "$ref": "#" },
"definitions": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": { "$ref": "#" },
"propertyNames": { "format": "regex" },
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [
{ "$ref": "#" },
{ "$ref": "#/definitions/stringArray" }
]
}
},
"propertyNames": { "$ref": "#" },
"const": true,
"enum": {
"type": "array",
"items": true,
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{ "$ref": "#/definitions/simpleTypes" },
{
"type": "array",
"items": { "$ref": "#/definitions/simpleTypes" },
"minItems": 1,
"uniqueItems": true
}
]
},
"format": { "type": "string" },
"contentMediaType": { "type": "string" },
"contentEncoding": { "type": "string" },
"if": { "$ref": "#" },
"then": { "$ref": "#" },
"else": { "$ref": "#" },
"allOf": { "$ref": "#/definitions/schemaArray" },
"anyOf": { "$ref": "#/definitions/schemaArray" },
"oneOf": { "$ref": "#/definitions/schemaArray" },
"not": { "$ref": "#" }
},
"default": true
} )"_json;
}
} // namespace nlohmann

View File

@ -0,0 +1,159 @@
/*
* JSON schema validator for JSON for modern C++
*
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* SPDX-License-Identifier: MIT
*
*/
#include <nlohmann/json-schema.hpp>
#include <sstream>
namespace nlohmann
{
void json_uri::update(const std::string &uri)
{
std::string pointer = ""; // default pointer is document-root
// first split the URI into location and pointer
auto pointer_separator = uri.find('#');
if (pointer_separator != std::string::npos) { // and extract the pointer-string if found
pointer = uri.substr(pointer_separator + 1); // remove #
// unescape %-values IOW, decode JSON-URI-formatted JSON-pointer
std::size_t pos = pointer.size() - 1;
do {
pos = pointer.rfind('%', pos);
if (pos == std::string::npos)
break;
if (pos >= pointer.size() - 2) {
pos--;
continue;
}
std::string hex = pointer.substr(pos + 1, 2);
char ascii = (char) std::strtoul(hex.c_str(), nullptr, 16);
pointer.replace(pos, 3, 1, ascii);
pos--;
} while (1);
}
auto location = uri.substr(0, pointer_separator);
if (location.size()) { // a location part has been found
// if it is an URN take it as it is
if (location.find("urn:") == 0) {
urn_ = location;
// and clear URL members
scheme_ = "";
authority_ = "";
path_ = "";
} else { // it is an URL
// split URL in protocol, hostname and path
std::size_t pos = 0;
auto proto = location.find("://", pos);
if (proto != std::string::npos) { // extract the protocol
urn_ = ""; // clear URN-member if URL is parsed
scheme_ = location.substr(pos, proto - pos);
pos = 3 + proto; // 3 == "://"
auto authority = location.find("/", pos);
if (authority != std::string::npos) { // and the hostname (no proto without hostname)
authority_ = location.substr(pos, authority - pos);
pos = authority;
}
}
auto path = location.substr(pos);
// URNs cannot of have paths
if (urn_.size() && path.size())
throw std::invalid_argument("Cannot add a path (" + path + ") to an URN URI (" + urn_ + ")");
if (path[0] == '/') // if it starts with a / it is root-path
path_ = path;
else if (pos == 0) { // the URL contained only a path and the current path has no / at the end, strip last element until / and append
auto last_slash = path_.rfind('/');
path_ = path_.substr(0, last_slash) + '/' + path;
} else // otherwise it is a subfolder
path_.append(path);
}
}
pointer_ = ""_json_pointer;
identifier_ = "";
if (pointer[0] == '/')
pointer_ = json::json_pointer(pointer);
else
identifier_ = pointer;
}
std::string json_uri::location() const
{
if (urn_.size())
return urn_;
std::stringstream s;
if (scheme_.size() > 0)
s << scheme_ << "://";
s << authority_
<< path_;
return s.str();
}
std::string json_uri::to_string() const
{
std::stringstream s;
s << location() << " # ";
if (identifier_ == "")
s << pointer_.to_string();
else
s << identifier_;
return s.str();
}
std::ostream &operator<<(std::ostream &os, const json_uri &u)
{
return os << u.to_string();
}
std::string json_uri::escape(const std::string &src)
{
std::vector<std::pair<std::string, std::string>> chars = {
{"~", "~0"},
{"/", "~1"}};
std::string l = src;
for (const auto &c : chars) {
std::size_t pos = 0;
do {
pos = l.find(c.first, pos);
if (pos == std::string::npos)
break;
l.replace(pos, 1, c.second);
pos += c.second.size();
} while (1);
}
return l;
}
} // namespace nlohmann

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
/*
* JSON schema validator for JSON for modern C++
*
* Copyright (c) 2016-2019 Patrick Boettcher <p@yai.se>.
*
* SPDX-License-Identifier: MIT
*
*/
#ifndef NLOHMANN_JSON_SCHEMA_HPP__
#define NLOHMANN_JSON_SCHEMA_HPP__
#ifdef _WIN32
# if defined(JSON_SCHEMA_VALIDATOR_EXPORTS)
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllexport)
# elif defined(JSON_SCHEMA_VALIDATOR_IMPORTS)
# define JSON_SCHEMA_VALIDATOR_API __declspec(dllimport)
# else
# define JSON_SCHEMA_VALIDATOR_API
# endif
#else
# define JSON_SCHEMA_VALIDATOR_API
#endif
#include <nlohmann/json.hpp>
#ifdef NLOHMANN_JSON_VERSION_MAJOR
# if (NLOHMANN_JSON_VERSION_MAJOR * 10000 + NLOHMANN_JSON_VERSION_MINOR * 100 + NLOHMANN_JSON_VERSION_PATCH) < 30800
# error "Please use this library with NLohmann's JSON version 3.8.0 or higher"
# endif
#else
# error "expected existing NLOHMANN_JSON_VERSION_MAJOR preproc variable, please update to NLohmann's JSON 3.8.0"
#endif
// make yourself a home - welcome to nlohmann's namespace
namespace nlohmann
{
// A class representing a JSON-URI for schemas derived from
// section 8 of JSON Schema: A Media Type for Describing JSON Documents
// draft-wright-json-schema-00
//
// New URIs can be derived from it using the derive()-method.
// This is useful for resolving refs or subschema-IDs in json-schemas.
//
// This is done implement the requirements described in section 8.2.
//
class JSON_SCHEMA_VALIDATOR_API json_uri
{
std::string urn_;
std::string scheme_;
std::string authority_;
std::string path_;
json::json_pointer pointer_; // fragment part if JSON-Pointer
std::string identifier_; // fragment part if Locatation Independent ID
protected:
// decodes a JSON uri and replaces all or part of the currently stored values
void update(const std::string &uri);
std::tuple<std::string, std::string, std::string, std::string, std::string> as_tuple() const
{
return std::make_tuple(urn_, scheme_, authority_, path_, identifier_ != "" ? identifier_ : pointer_);
}
public:
json_uri(const std::string &uri)
{
update(uri);
}
const std::string &scheme() const { return scheme_; }
const std::string &authority() const { return authority_; }
const std::string &path() const { return path_; }
const json::json_pointer &pointer() const { return pointer_; }
const std::string &identifier() const { return identifier_; }
std::string fragment() const
{
if (identifier_ == "")
return pointer_;
else
return identifier_;
}
std::string url() const { return location(); }
std::string location() const;
static std::string escape(const std::string &);
// create a new json_uri based in this one and the given uri
// resolves relative changes (pathes or pointers) and resets part if proto or hostname changes
json_uri derive(const std::string &uri) const
{
json_uri u = *this;
u.update(uri);
return u;
}
// append a pointer-field to the pointer-part of this uri
json_uri append(const std::string &field) const
{
if (identifier_ != "")
return *this;
json_uri u = *this;
u.pointer_ /= field;
return u;
}
std::string to_string() const;
friend bool operator<(const json_uri &l, const json_uri &r)
{
return l.as_tuple() < r.as_tuple();
}
friend bool operator==(const json_uri &l, const json_uri &r)
{
return l.as_tuple() == r.as_tuple();
}
friend std::ostream &operator<<(std::ostream &os, const json_uri &u);
};
namespace json_schema
{
extern json draft7_schema_builtin;
typedef std::function<void(const json_uri & /*id*/, json & /*value*/)> schema_loader;
typedef std::function<void(const std::string & /*format*/, const std::string & /*value*/)> format_checker;
typedef std::function<void(const std::string & /*contentEncoding*/, const std::string & /*contentMediaType*/, const json & /*instance*/)> content_checker;
// Interface for validation error handlers
class JSON_SCHEMA_VALIDATOR_API error_handler
{
public:
virtual ~error_handler() {}
virtual void error(const json::json_pointer & /*ptr*/, const json & /*instance*/, const std::string & /*message*/) = 0;
};
class JSON_SCHEMA_VALIDATOR_API basic_error_handler : public error_handler
{
bool error_{false};
public:
void error(const json::json_pointer & /*ptr*/, const json & /*instance*/, const std::string & /*message*/) override
{
error_ = true;
}
virtual void reset() { error_ = false; }
operator bool() const { return error_; }
};
/**
* Checks validity of JSON schema built-in string format specifiers like 'date-time', 'ipv4', ...
*/
void default_string_format_check(const std::string &format, const std::string &value);
class root_schema;
class JSON_SCHEMA_VALIDATOR_API json_validator
{
std::unique_ptr<root_schema> root_;
public:
json_validator(schema_loader = nullptr, format_checker = nullptr, content_checker = nullptr);
json_validator(const json &, schema_loader = nullptr, format_checker = nullptr, content_checker = nullptr);
json_validator(json &&, schema_loader = nullptr, format_checker = nullptr, content_checker = nullptr);
json_validator(json_validator &&);
json_validator &operator=(json_validator &&);
json_validator(json_validator const &) = delete;
json_validator &operator=(json_validator const &) = delete;
~json_validator();
// insert and set the root-schema
void set_root_schema(const json &);
void set_root_schema(json &&);
// validate a json-document based on the root-schema
json validate(const json &) const;
// validate a json-document based on the root-schema with a custom error-handler
json validate(const json &, error_handler &, const json_uri &initial_uri = json_uri("#")) const;
};
} // namespace json_schema
} // namespace nlohmann
#endif /* NLOHMANN_JSON_SCHEMA_HPP__ */

View File

@ -0,0 +1,311 @@
#include <nlohmann/json-schema.hpp>
#include <algorithm>
#include <exception>
#include <iostream>
#include <regex>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
/**
* Many of the RegExes are from @see http://jmrware.com/articles/2009/uri_regexp/URI_regex.html
*/
namespace
{
template <typename T>
void range_check(const T value, const T min, const T max)
{
if (!((value >= min) && (value <= max))) {
std::stringstream out;
out << "Value " << value << " should be in interval [" << min << "," << max << "] but is not!";
throw std::invalid_argument(out.str());
}
}
/** @see date_time_check */
void rfc3339_date_check(const std::string &value)
{
const static std::regex dateRegex{R"(^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$)"};
std::smatch matches;
if (!std::regex_match(value, matches, dateRegex)) {
throw std::invalid_argument(value + " is not a date string according to RFC 3339.");
}
const auto year = std::stoi(matches[1].str());
const auto month = std::stoi(matches[2].str());
const auto mday = std::stoi(matches[3].str());
const auto isLeapYear = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
range_check(month, 1, 12);
if (month == 2) {
range_check(mday, 1, isLeapYear ? 29 : 28);
} else if (month <= 7) {
range_check(mday, 1, month % 2 == 0 ? 30 : 31);
} else {
range_check(mday, 1, month % 2 == 0 ? 31 : 30);
}
}
/** @see date_time_check */
void rfc3339_time_check(const std::string &value)
{
const static std::regex timeRegex{R"(^([0-9]{2})\:([0-9]{2})\:([0-9]{2})(\.[0-9]+)?(?:[Zz]|((?:\+|\-)[0-9]{2})\:([0-9]{2}))$)"};
std::smatch matches;
if (!std::regex_match(value, matches, timeRegex)) {
throw std::invalid_argument(value + " is not a time string according to RFC 3339.");
}
const auto hour = std::stoi(matches[1].str());
const auto minute = std::stoi(matches[2].str());
const auto second = std::stoi(matches[3].str());
// const auto secfrac = std::stof( matches[4].str() );
range_check(hour, 0, 23);
range_check(minute, 0, 59);
/**
* @todo Could be made more exact by querying a leap second database and choosing the
* correct maximum in {58,59,60}. This current solution might match some invalid dates
* but it won't lead to false negatives. This only works if we know the full date, however
*/
range_check(second, 0, 60);
/* don't check the numerical offset if time zone is specified as 'Z' */
if (!matches[5].str().empty()) {
const auto offsetHour = std::stoi(matches[5].str());
const auto offsetMinute = std::stoi(matches[6].str());
range_check(offsetHour, -23, 23);
range_check(offsetMinute, 0, 59);
}
}
/**
* @see https://tools.ietf.org/html/rfc3339#section-5.6
*
* @verbatim
* date-fullyear = 4DIGIT
* date-month = 2DIGIT ; 01-12
* date-mday = 2DIGIT ; 01-28, 01-29, 01-30, 01-31 based on
* ; month/year
* time-hour = 2DIGIT ; 00-23
* time-minute = 2DIGIT ; 00-59
* time-second = 2DIGIT ; 00-58, 00-59, 00-60 based on leap second
* ; rules
* time-secfrac = "." 1*DIGIT
* time-numoffset = ("+" / "-") time-hour ":" time-minute
* time-offset = "Z" / time-numoffset
*
* partial-time = time-hour ":" time-minute ":" time-second
* [time-secfrac]
* full-date = date-fullyear "-" date-month "-" date-mday
* full-time = partial-time time-offset
*
* date-time = full-date "T" full-time
* @endverbatim
* NOTE: Per [ABNF] and ISO8601, the "T" and "Z" characters in this
* syntax may alternatively be lower case "t" or "z" respectively.
*/
void rfc3339_date_time_check(const std::string &value)
{
const static std::regex dateTimeRegex{R"(^([0-9]{4}\-[0-9]{2}\-[0-9]{2})[Tt]([0-9]{2}\:[0-9]{2}\:[0-9]{2}(?:\.[0-9]+)?(?:[Zz]|(?:\+|\-)[0-9]{2}\:[0-9]{2}))$)"};
std::smatch matches;
if (!std::regex_match(value, matches, dateTimeRegex)) {
throw std::invalid_argument(value + " is not a date-time string according to RFC 3339.");
}
rfc3339_date_check(matches[1].str());
rfc3339_time_check(matches[2].str());
}
const std::string decOctet{R"((?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))"}; // matches numbers 0-255
const std::string ipv4Address{"(?:" + decOctet + R"(\.){3})" + decOctet};
const std::string h16{R"([0-9A-Fa-f]{1,4})"};
const std::string h16Left{"(?:" + h16 + ":)"};
const std::string ipv6Address{
"(?:"
"(?:" +
h16Left + "{6}"
"|::" +
h16Left + "{5}"
"|(?:" +
h16 + ")?::" + h16Left + "{4}"
"|(?:" +
h16Left + "{0,1}" + h16 + ")?::" + h16Left + "{3}"
"|(?:" +
h16Left + "{0,2}" + h16 + ")?::" + h16Left + "{2}"
"|(?:" +
h16Left + "{0,3}" + h16 + ")?::" + h16Left +
"|(?:" + h16Left + "{0,4}" + h16 + ")?::"
")(?:" +
h16Left + h16 + "|" + ipv4Address + ")"
"|(?:" +
h16Left + "{0,5}" + h16 + ")?::" + h16 +
"|(?:" + h16Left + "{0,6}" + h16 + ")?::"
")"};
const std::string ipvFuture{R"([Vv][0-9A-Fa-f]+\.[A-Za-z0-9\-._~!$&'()*+,;=:]+)"};
const std::string regName{R"((?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*)"};
const std::string host{
"(?:"
R"(\[(?:)" +
ipv6Address + "|" + ipvFuture + R"()\])" +
"|" + ipv4Address +
"|" + regName +
")"};
// from http://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
const std::string hostname{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]))*$)"};
/**
* @see https://tools.ietf.org/html/rfc5322#section-4.1
*
* @verbatim
* atom = [CFWS] 1*atext [CFWS]
* word = atom / quoted-string
* phrase = 1*word / obs-phrase
* obs-FWS = 1*WSP *(CRLF 1*WSP)
* FWS = ([*WSP CRLF] 1*WSP) / obs-FWS
* ; Folding white space
* ctext = %d33-39 / ; Printable US-ASCII
* %d42-91 / ; characters not including
* %d93-126 / ; "(", ")", or "\"
* obs-ctext
* ccontent = ctext / quoted-pair / comment
* comment = "(" *([FWS] ccontent) [FWS] ")"
* CFWS = (1*([FWS] comment) [FWS]) / FWS
* obs-local-part = word *("." word)
* obs-domain = atom *("." atom)
* obs-dtext = obs-NO-WS-CTL / quoted-pair
* quoted-pair = ("\" (VCHAR / WSP)) / obs-qp
* obs-NO-WS-CTL = %d1-8 / ; US-ASCII control
* %d11 / ; characters that do not
* %d12 / ; include the carriage
* %d14-31 / ; return, line feed, and
* %d127 ; white space characters
* obs-ctext = obs-NO-WS-CTL
* obs-qtext = obs-NO-WS-CTL
* obs-utext = %d0 / obs-NO-WS-CTL / VCHAR
* obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR)
* obs-body = *((*LF *CR *((%d0 / text) *LF *CR)) / CRLF)
* obs-unstruct = *((*LF *CR *(obs-utext *LF *CR)) / FWS)
* obs-phrase = word *(word / "." / CFWS)
* obs-phrase-list = [phrase / CFWS] *("," [phrase / CFWS])
* qtext = %d33 / ; Printable US-ASCII
* %d35-91 / ; characters not including
* %d93-126 / ; "\" or the quote character
* obs-qtext
* qcontent = qtext / quoted-pair
* quoted-string = [CFWS]
* DQUOTE *([FWS] qcontent) [FWS] DQUOTE
* [CFWS]
* atext = ALPHA / DIGIT / ; Printable US-ASCII
* "!" / "#" / ; characters not including
* "$" / "%" / ; specials. Used for atoms.
* "&" / "'" /
* "*" / "+" /
* "-" / "/" /
* "=" / "?" /
* "^" / "_" /
* "`" / "{" /
* "|" / "}" /
* "~"
* dot-atom-text = 1*atext *("." 1*atext)
* dot-atom = [CFWS] dot-atom-text [CFWS]
* addr-spec = local-part "@" domain
* local-part = dot-atom / quoted-string / obs-local-part
* domain = dot-atom / domain-literal / obs-domain
* domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS]
* dtext = %d33-90 / ; Printable US-ASCII
* %d94-126 / ; characters not including
* obs-dtext ; "[", "]", or "\"
* @endverbatim
* @todo Currently don't have a working tool for this larger ABNF to generate a regex.
* Other options:
* - https://github.com/ldthomas/apg-6.3
* - https://github.com/akr/abnf
*
* The problematic thing are the allowed whitespaces (even newlines) in the email.
* Ignoring those and starting with
* @see https://stackoverflow.com/questions/13992403/regex-validation-of-email-addresses-according-to-rfc5321-rfc5322
* and trying to divide up the complicated regex into understandable ABNF definitions from rfc5322 yields:
*/
const std::string obsnowsctl{R"([\x01-\x08\x0b\x0c\x0e-\x1f\x7f])"};
const std::string obsqp{R"(\\[\x01-\x09\x0b\x0c\x0e-\x7f])"};
const std::string qtext{R"((?:[\x21\x23-\x5b\x5d-\x7e]|)" + obsnowsctl + ")"};
const std::string dtext{R"([\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f])"};
const std::string quotedString{R"("(?:)" + qtext + "|" + obsqp + R"()*")"};
const std::string atext{R"([A-Za-z0-9!#$%&'*+/=?^_`{|}~-])"};
const std::string domainLiteral{R"(\[(?:(?:)" + decOctet + R"()\.){3}(?:)" + decOctet + R"(|[A-Za-z0-9-]*[A-Za-z0-9]:(?:)" + dtext + "|" + obsqp + R"()+)\])"};
const std::string dotAtom{"(?:" + atext + R"(+(?:\.)" + atext + "+)*)"};
const std::string stackoverflowMagicPart{R"((?:[[:alnum:]](?:[[:alnum:]-]*[[:alnum:]])?\.)+)"
R"([[:alnum:]](?:[[:alnum:]-]*[[:alnum:]])?)"};
const std::string email{"(?:" + dotAtom + "|" + quotedString + ")@(?:" + stackoverflowMagicPart + "|" + domainLiteral + ")"};
} // namespace
namespace nlohmann
{
namespace json_schema
{
/**
* Checks validity for built-ins by converting the definitions given as ABNF in the linked RFC from
* @see https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
* into regular expressions using @see https://www.msweet.org/abnf/ and some manual editing.
*
* @see https://json-schema.org/latest/json-schema-validation.html
*/
void default_string_format_check(const std::string &format, const std::string &value)
{
if (format == "date-time") {
rfc3339_date_time_check(value);
} else if (format == "date") {
rfc3339_date_check(value);
} else if (format == "time") {
rfc3339_time_check(value);
} else if (format == "email") {
static const std::regex emailRegex{email};
if (!std::regex_match(value, emailRegex)) {
throw std::invalid_argument(value + " is not a valid email according to RFC 5322.");
}
} else if (format == "hostname") {
static const std::regex hostRegex{hostname};
if (!std::regex_match(value, hostRegex)) {
throw std::invalid_argument(value + " is not a valid hostname according to RFC 3986 Appendix A.");
}
} else if (format == "ipv4") {
const static std::regex ipv4Regex{"^" + ipv4Address + "$"};
if (!std::regex_match(value, ipv4Regex)) {
throw std::invalid_argument(value + " is not an IPv4 string according to RFC 2673.");
}
} else if (format == "ipv6") {
static const std::regex ipv6Regex{ipv6Address};
if (!std::regex_match(value, ipv6Regex)) {
throw std::invalid_argument(value + " is not an IPv6 string according to RFC 5954.");
}
} else if (format == "regex") {
try {
std::regex re(value, std::regex::ECMAScript);
} catch (std::exception &exception) {
throw exception;
}
} else {
/* yet unsupported JSON schema draft 7 built-ins */
static const std::vector<std::string> jsonSchemaStringFormatBuiltIns{
"date-time", "time", "date", "email", "idn-email", "hostname", "idn-hostname", "ipv4", "ipv6", "uri",
"uri-reference", "iri", "iri-reference", "uri-template", "json-pointer", "relative-json-pointer", "regex"};
if (std::find(jsonSchemaStringFormatBuiltIns.begin(), jsonSchemaStringFormatBuiltIns.end(), format) != jsonSchemaStringFormatBuiltIns.end()) {
throw std::logic_error("JSON schema string format built-in " + format + " not yet supported. " +
"Please open an issue or use a custom format checker.");
}
throw std::logic_error("Don't know how to validate " + format);
}
}
} // namespace json_schema
} // namespace nlohmann