From a3f3a59a0ec29e5e4ec6467cf4c0b6ee82af1812 Mon Sep 17 00:00:00 2001 From: Patrick Boettcher Date: Sat, 23 May 2020 10:14:10 +0200 Subject: [PATCH] add content-checker-callback Used when contentEncoding or mediaType attributes are present on a string-value. --- src/json-validator.cpp | 69 ++++++++++++++++--- src/nlohmann/json-schema.hpp | 7 +- test/JSON-Schema-Test-Suite/CMakeLists.txt | 1 - .../json-schema-test.cpp | 47 ++++++++++++- 4 files changed, 111 insertions(+), 13 deletions(-) diff --git a/src/json-validator.cpp b/src/json-validator.cpp index f16f3ef..d956037 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -105,6 +105,7 @@ class root_schema : public schema { schema_loader loader_; format_checker format_check_; + content_checker content_check_; std::shared_ptr 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 &s) { @@ -461,8 +470,24 @@ class type_schema : public schema else_->validate(ptr, instance, patch, e); } } + + // special treatment + if (instance.type() == json::value_t::string && 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()); + } + } + } } +protected: + std::tuple content_{false, "", ""}; + public: type_schema(json &sch, root_schema *root, @@ -583,6 +608,20 @@ public: } sch.erase(attr); } + + attr = sch.find("contentEncoding"); + if (attr != sch.end()) { + std::get<0>(content_) = true; + std::get<1>(content_) = attr.value().get(); + sch.erase(attr); + } + + attr = sch.find("contentMediaType"); + if (attr != sch.end()) { + std::get<0>(content_) = true; + std::get<2>(content_) = attr.value().get(); + sch.erase(attr); + } } }; @@ -1209,19 +1248,33 @@ namespace json_schema { json_validator::json_validator(schema_loader loader, - format_checker format) - : root_(std::unique_ptr(new root_schema(std::move(loader), std::move(format)))) + format_checker format, + content_checker content) + : root_(std::unique_ptr(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)); } diff --git a/src/nlohmann/json-schema.hpp b/src/nlohmann/json-schema.hpp index baa9719..c25ca72 100644 --- a/src/nlohmann/json-schema.hpp +++ b/src/nlohmann/json-schema.hpp @@ -133,6 +133,7 @@ extern json draft7_schema_builtin; typedef std::function schema_loader; typedef std::function format_checker; +typedef std::function 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_; 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 &&); diff --git a/test/JSON-Schema-Test-Suite/CMakeLists.txt b/test/JSON-Schema-Test-Suite/CMakeLists.txt index fc52df2..61d5fd4 100644 --- a/test/JSON-Schema-Test-Suite/CMakeLists.txt +++ b/test/JSON-Schema-Test-Suite/CMakeLists.txt @@ -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 diff --git a/test/JSON-Schema-Test-Suite/json-schema-test.cpp b/test/JSON-Schema-Test-Suite/json-schema-test.cpp index 05a255d..deee4c8 100644 --- a/test/JSON-Schema-Test-Suite/json-schema-test.cpp +++ b/test/JSON-Schema-Test-Suite/json-schema-test.cpp @@ -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 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);