Merge remote-tracking branch 'origin/binary_support_changes' into binary_support
This commit is contained in:
commit
b56d1eac77
@ -1,12 +1,12 @@
|
||||
project(nlohmann_json_schema_validator
|
||||
LANGUAGES CXX)
|
||||
LANGUAGES CXX)
|
||||
|
||||
set(PROJECT_VERSION 2.1.0)
|
||||
set(PROJECT_VERSION 2.1.1)
|
||||
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
|
||||
option(BUILD_TESTS "Build tests" ON)
|
||||
option(BUILD_EXAMPLES "Build examples" ON)
|
||||
option(BUILD_TESTS "Build tests" ON)
|
||||
option(BUILD_EXAMPLES "Build examples" ON)
|
||||
|
||||
# the library
|
||||
add_library(nlohmann_json_schema_validator
|
||||
|
||||
@ -105,6 +105,7 @@ class root_schema : public schema
|
||||
{
|
||||
schema_loader loader_;
|
||||
format_checker format_check_;
|
||||
content_checker content_check_;
|
||||
|
||||
std::shared_ptr<schema> root_;
|
||||
|
||||
@ -128,10 +129,18 @@ class root_schema : public schema
|
||||
|
||||
public:
|
||||
root_schema(schema_loader &&loader,
|
||||
format_checker &&format)
|
||||
: schema(this), loader_(std::move(loader)), format_check_(std::move(format)) {}
|
||||
format_checker &&format,
|
||||
content_checker &&content)
|
||||
|
||||
: schema(this),
|
||||
loader_(std::move(loader)),
|
||||
format_check_(std::move(format)),
|
||||
content_check_(std::move(content))
|
||||
{
|
||||
}
|
||||
|
||||
format_checker &format_check() { return format_check_; }
|
||||
content_checker &content_check() { return content_check_; }
|
||||
|
||||
void insert(const json_uri &uri, const std::shared_ptr<schema> &s)
|
||||
{
|
||||
@ -478,7 +487,6 @@ public:
|
||||
{"boolean", json::value_t::boolean},
|
||||
{"integer", json::value_t::number_integer},
|
||||
{"number", json::value_t::number_float},
|
||||
{"binary", json::value_t::binary},
|
||||
};
|
||||
|
||||
std::set<std::string> known_keywords;
|
||||
@ -492,37 +500,17 @@ public:
|
||||
|
||||
case json::value_t::string: {
|
||||
auto schema_type = attr.value().get<std::string>();
|
||||
|
||||
// add supporting validation binary types
|
||||
if (schema_type == "string") {
|
||||
auto found = sch.find("contentEncoding");
|
||||
if (found != sch.end() && found->get<std::string>() == "binary") {
|
||||
schema_type = "binary";
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &t : schema_types)
|
||||
if (t.first == schema_type)
|
||||
type_[(uint8_t) t.second] = type_schema::make(sch, t.second, root, uris, known_keywords);
|
||||
} break;
|
||||
|
||||
case json::value_t::array: // "type": ["type1", "type2"]
|
||||
{
|
||||
json type_array = attr.value();
|
||||
|
||||
auto has_string_type = std::find(type_array.begin(), type_array.end(), "string");
|
||||
if (has_string_type != type_array.end()) {
|
||||
auto encodingFound = sch.find("contentEncoding");
|
||||
if (encodingFound != sch.end() && encodingFound.value() == "binary") {
|
||||
type_array.emplace_back("binary");
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &schema_type : type_array)
|
||||
for (auto &schema_type : attr.value())
|
||||
for (auto &t : schema_types)
|
||||
if (t.first == schema_type)
|
||||
type_[(uint8_t) t.second] = type_schema::make(sch, t.second, root, uris, known_keywords);
|
||||
} break;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
@ -548,6 +536,11 @@ public:
|
||||
// we stick with JSON-schema: use the integer-validator if instance-value is unsigned
|
||||
type_[(uint8_t) json::value_t::number_unsigned] = type_[(uint8_t) json::value_t::number_integer];
|
||||
|
||||
// special for binary types
|
||||
if (type_[(uint8_t) json::value_t::string]) {
|
||||
type_[(uint8_t) json::value_t::binary] = type_[(uint8_t) json::value_t::string];
|
||||
}
|
||||
|
||||
attr = sch.find("enum");
|
||||
if (attr != sch.end()) {
|
||||
enum_ = {true, attr.value()};
|
||||
@ -618,11 +611,12 @@ class string : public schema
|
||||
#endif
|
||||
|
||||
std::pair<bool, std::string> format_;
|
||||
std::tuple<bool, std::string, std::string> content_{false, "", ""};
|
||||
|
||||
std::size_t utf8_length(const std::string &s) const
|
||||
{
|
||||
size_t len = 0;
|
||||
for (const unsigned char &c : s)
|
||||
for (unsigned char c : s)
|
||||
if ((c & 0xc0) != 0x80)
|
||||
len++;
|
||||
return len;
|
||||
@ -646,6 +640,24 @@ class string : public schema
|
||||
}
|
||||
}
|
||||
|
||||
if (std::get<0>(content_)) {
|
||||
if (root_->content_check() == nullptr)
|
||||
e.error(ptr, instance, std::string("a content checker was not provided but a contentEncoding or contentMediaType for this string have been present: '") + std::get<1>(content_) + "' '" + std::get<2>(content_) + "'");
|
||||
else {
|
||||
try {
|
||||
root_->content_check()(std::get<1>(content_), std::get<2>(content_), instance);
|
||||
} catch (const std::exception &ex) {
|
||||
e.error(ptr, instance, std::string("content-checking failed: ") + ex.what());
|
||||
}
|
||||
}
|
||||
} else if (instance.type() == json::value_t::binary) {
|
||||
e.error(ptr, instance, "expected string, but get binary data");
|
||||
}
|
||||
|
||||
if (instance.type() != json::value_t::string) {
|
||||
return; // next checks only for strings
|
||||
}
|
||||
|
||||
#ifndef NO_STD_REGEX
|
||||
if (pattern_.first &&
|
||||
!REGEX_NAMESPACE::regex_search(instance.get<std::string>(), pattern_.second))
|
||||
@ -681,6 +693,37 @@ public:
|
||||
sch.erase(attr);
|
||||
}
|
||||
|
||||
attr = sch.find("contentEncoding");
|
||||
if (attr != sch.end()) {
|
||||
std::get<0>(content_) = true;
|
||||
std::get<1>(content_) = attr.value().get<std::string>();
|
||||
|
||||
// special case for nlohmann::json-binary-types
|
||||
//
|
||||
// https://github.com/pboettch/json-schema-validator/pull/114
|
||||
//
|
||||
// We cannot use explicitly in a schema: {"type": "binary"} or
|
||||
// "type": ["binary", "number"] we have to be implicit. For a
|
||||
// schema where "contentEncoding" is set to "binary", an instance
|
||||
// of type json::value_t::binary is accepted. If a
|
||||
// contentEncoding-callback has to be provided and is called
|
||||
// accordingly. For encoding=binary, no other type validations are done
|
||||
|
||||
sch.erase(attr);
|
||||
}
|
||||
|
||||
attr = sch.find("contentMediaType");
|
||||
if (attr != sch.end()) {
|
||||
std::get<0>(content_) = true;
|
||||
std::get<2>(content_) = attr.value().get<std::string>();
|
||||
|
||||
sch.erase(attr);
|
||||
}
|
||||
|
||||
if (std::get<0>(content_) == true && root_->content_check() == nullptr) {
|
||||
throw std::invalid_argument{"schema contains contentEncoding/contentMediaType but content checker was not set"};
|
||||
}
|
||||
|
||||
#ifndef NO_STD_REGEX
|
||||
attr = sch.find("pattern");
|
||||
if (attr != sch.end()) {
|
||||
@ -1110,21 +1153,6 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/**\brief just a placeholder
|
||||
*/
|
||||
class binary : public schema
|
||||
{
|
||||
void validate(const json::json_pointer &, const json &, json_patch &, error_handler &) const override
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
binary(json &, root_schema *root)
|
||||
: schema(root)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<schema> type_schema::make(json &schema,
|
||||
json::value_t type,
|
||||
root_schema *root,
|
||||
@ -1152,8 +1180,8 @@ std::shared_ptr<schema> type_schema::make(json &schema,
|
||||
case json::value_t::discarded: // not a real type - silence please
|
||||
break;
|
||||
|
||||
case json::value_t::binary: // can use for validate bson or other binary representation of json
|
||||
return std::make_shared<binary>(schema, root);
|
||||
case json::value_t::binary:
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@ -1248,19 +1276,33 @@ namespace json_schema
|
||||
{
|
||||
|
||||
json_validator::json_validator(schema_loader loader,
|
||||
format_checker format)
|
||||
: root_(std::unique_ptr<root_schema>(new root_schema(std::move(loader), std::move(format))))
|
||||
format_checker format,
|
||||
content_checker content)
|
||||
: root_(std::unique_ptr<root_schema>(new root_schema(std::move(loader),
|
||||
std::move(format),
|
||||
std::move(content))))
|
||||
{
|
||||
}
|
||||
|
||||
json_validator::json_validator(const json &schema, schema_loader loader, format_checker format)
|
||||
: json_validator(std::move(loader), std::move(format))
|
||||
json_validator::json_validator(const json &schema,
|
||||
schema_loader loader,
|
||||
format_checker format,
|
||||
content_checker content)
|
||||
: json_validator(std::move(loader),
|
||||
std::move(format),
|
||||
std::move(content))
|
||||
{
|
||||
set_root_schema(schema);
|
||||
}
|
||||
|
||||
json_validator::json_validator(json &&schema, schema_loader loader, format_checker format)
|
||||
: json_validator(std::move(loader), std::move(format))
|
||||
json_validator::json_validator(json &&schema,
|
||||
schema_loader loader,
|
||||
format_checker format,
|
||||
content_checker content)
|
||||
|
||||
: json_validator(std::move(loader),
|
||||
std::move(format),
|
||||
std::move(content))
|
||||
{
|
||||
set_root_schema(std::move(schema));
|
||||
}
|
||||
|
||||
@ -133,6 +133,7 @@ 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
|
||||
@ -168,10 +169,10 @@ class JSON_SCHEMA_VALIDATOR_API json_validator
|
||||
std::unique_ptr<root_schema> root_;
|
||||
|
||||
public:
|
||||
json_validator(schema_loader = nullptr, format_checker = nullptr);
|
||||
json_validator(schema_loader = nullptr, format_checker = nullptr, content_checker = nullptr);
|
||||
|
||||
json_validator(const json &, schema_loader = nullptr, format_checker = nullptr);
|
||||
json_validator(json &&, schema_loader = nullptr, format_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 &&);
|
||||
|
||||
@ -50,7 +50,6 @@ if(JSON_SCHEMA_TEST_SUITE_PATH)
|
||||
# some optional tests will fail
|
||||
set_tests_properties(
|
||||
JSON-Suite::Optional::bignum
|
||||
JSON-Suite::Optional::content
|
||||
JSON-Suite::Optional::zeroTerminatedFloats
|
||||
JSON-Suite::Optional::non-bmp-regex
|
||||
|
||||
|
||||
@ -39,6 +39,50 @@ static void loader(const json_uri &uri, json &schema)
|
||||
}
|
||||
}
|
||||
|
||||
// from here
|
||||
// https://stackoverflow.com/a/34571089/880584
|
||||
static std::string base64_decode(const std::string &in)
|
||||
{
|
||||
std::string out;
|
||||
|
||||
std::vector<int> T(256, -1);
|
||||
for (int i = 0; i < 64; i++)
|
||||
T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
|
||||
|
||||
unsigned val = 0;
|
||||
int valb = -8;
|
||||
for (uint8_t c : in) {
|
||||
if (c == '=')
|
||||
break;
|
||||
|
||||
if (T[c] == -1) {
|
||||
throw std::invalid_argument("base64-decode: unexpected character in encode string: '" + std::string(1, c) + "'");
|
||||
}
|
||||
val = (val << 6) + T[c];
|
||||
valb += 6;
|
||||
if (valb >= 0) {
|
||||
out.push_back(char((val >> valb) & 0xFF));
|
||||
valb -= 8;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void content(const std::string &contentEncoding, const std::string &contentMediaType, const json &instance)
|
||||
{
|
||||
std::string content = instance;
|
||||
|
||||
if (contentEncoding == "base64")
|
||||
content = base64_decode(instance);
|
||||
else if (contentEncoding != "")
|
||||
throw std::invalid_argument("unable to check for contentEncoding '" + contentEncoding + "'");
|
||||
|
||||
if (contentMediaType == "application/json")
|
||||
auto dummy = json::parse(content); // throws if conversion fails
|
||||
else if (contentMediaType != "")
|
||||
throw std::invalid_argument("unable to check for contentMediaType '" + contentMediaType + "'");
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
json validation; // a validation case following the JSON-test-suite-schema
|
||||
@ -62,7 +106,8 @@ int main(void)
|
||||
const auto &schema = test_group["schema"];
|
||||
|
||||
json_validator validator(loader,
|
||||
nlohmann::json_schema::default_string_format_check);
|
||||
nlohmann::json_schema::default_string_format_check,
|
||||
content);
|
||||
|
||||
validator.set_root_schema(schema);
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#include <nlohmann/json-schema.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
int error_count = 0;
|
||||
static int error_count = 0;
|
||||
|
||||
#define EXPECT_EQ(a, b) \
|
||||
do { \
|
||||
@ -14,6 +14,19 @@ int error_count = 0;
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define EXPECT_THROW(foo) \
|
||||
{ \
|
||||
bool ok = false; \
|
||||
try { \
|
||||
foo; \
|
||||
} catch (std::exception &) { \
|
||||
ok = true; \
|
||||
} \
|
||||
if (ok == false) { \
|
||||
error_count++; \
|
||||
} \
|
||||
}
|
||||
|
||||
using json = nlohmann::json;
|
||||
using validator = nlohmann::json_schema::json_validator;
|
||||
|
||||
@ -78,10 +91,24 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
static void content(const std::string &contentEncoding, const std::string &contentMediaType, const json &instance)
|
||||
{
|
||||
std::cerr << "mediaType: '" << contentMediaType << "', encoding: '" << contentEncoding << "'\n";
|
||||
|
||||
if (contentEncoding == "binary") {
|
||||
if (instance.type() != json::value_t::binary) {
|
||||
throw std::invalid_argument{"expected binary data"};
|
||||
}
|
||||
} else {
|
||||
if (instance.type() == json::value_t::binary) {
|
||||
throw std::invalid_argument{"expected string, but get binary"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
validator val;
|
||||
val.set_root_schema(bson_schema);
|
||||
validator val(nullptr, nullptr, content);
|
||||
|
||||
// create some bson doc
|
||||
json::binary_t arr;
|
||||
@ -90,8 +117,12 @@ int main()
|
||||
|
||||
json binary = json::binary(arr);
|
||||
|
||||
store_ptr_err_handler err;
|
||||
|
||||
/////////////////////////////////////
|
||||
val.set_root_schema(bson_schema);
|
||||
|
||||
// all right
|
||||
store_ptr_err_handler err{};
|
||||
val.validate({{"standard_string", "some string"}, {"binary_data", binary}}, err);
|
||||
EXPECT_EQ(err.failed_pointers.size(), 0);
|
||||
err.reset();
|
||||
@ -113,9 +144,12 @@ int main()
|
||||
|
||||
// check simple types
|
||||
val.set_root_schema(array_of_types);
|
||||
val.validate({{"something", "string"}}, err);
|
||||
val.validate({{"something", 1}}, err);
|
||||
val.validate({{"something", false}}, err);
|
||||
// TODO when we set `string` in array and set `contentEncoding` = "binary" - what it means? We expected string or binary?
|
||||
// Or we expect only binary? Now if you set `contentEncoding` = "binary", then it means that you expect only binary data,
|
||||
// not string
|
||||
//val.validate({{"something", "string"}}, err); -> produce error about type
|
||||
EXPECT_EQ(err.failed_pointers.size(), 0);
|
||||
err.reset();
|
||||
|
||||
@ -124,6 +158,7 @@ int main()
|
||||
EXPECT_EQ(err.failed_pointers.size(), 0);
|
||||
err.reset();
|
||||
|
||||
/////////////////////////////////////
|
||||
// and check that you can't set binary data if contentEncoding don't set
|
||||
val.set_root_schema(array_of_types_without_binary);
|
||||
val.validate({{"something", binary}}, err);
|
||||
@ -131,5 +166,10 @@ int main()
|
||||
EXPECT_EQ(err.failed_pointers[0], "/something");
|
||||
err.reset();
|
||||
|
||||
// check that without content callback you get exception with schema with contentEncoding or contentMeditType
|
||||
validator val_no_content;
|
||||
|
||||
EXPECT_THROW(val_no_content.set_root_schema(bson_schema));
|
||||
|
||||
return error_count;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user