diff --git a/src/json-validator.cpp b/src/json-validator.cpp index 592a075..1d72874 100644 --- a/src/json-validator.cpp +++ b/src/json-validator.cpp @@ -472,7 +472,8 @@ class type_schema : public schema } // special treatment - if (instance.type() == json::value_t::string && std::get<0>(content_)) { + if ((instance.type() == json::value_t::string || instance.type() == json::value_t::binary) && + 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 { @@ -613,6 +614,27 @@ public: if (attr != sch.end()) { std::get<0>(content_) = true; std::get<1>(content_) = attr.value().get(); + + // 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 + + if (attr.value() == "binary") { + // clear out all other type-schemas + for (auto &type_valid : type_) + type_valid = nullptr; + + // when no schema-type is explicitly given, we accept binary-values + type_[(uint8_t) json::value_t::binary] = type_schema::make(sch, json::value_t::binary, root, uris, known_keywords); + } + sch.erase(attr); } @@ -1157,6 +1179,14 @@ std::shared_ptr type_schema::make(json &schema, case json::value_t::discarded: // not a real type - silence please break; + + case json::value_t::binary: { + // can be used for validate bson or other binary representation of json + // - specific to nlohmann::json - this type is not standardized + // json-schema-draft-7 + json tmp = true; + return std::make_shared(tmp, root); // always true - content-check with do the work + } } return nullptr; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 569e01e..d82f41d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -60,3 +60,8 @@ add_test(NAME json-patch COMMAND json-patch) add_executable(issue-117-format-error issue-117-format-error.cpp) target_link_libraries(issue-117-format-error nlohmann_json_schema_validator) add_test(NAME issue-117-format-error COMMAND issue-117-format-error) + +add_executable(binary-validation binary-validation.cpp) +target_include_directories(binary-validation PRIVATE ${PROJECT_SOURCE_DIR}/src) +target_link_libraries(binary-validation PRIVATE nlohmann_json_schema_validator) +add_test(NAME binary-validation COMMAND binary-validation) diff --git a/test/binary-validation.cpp b/test/binary-validation.cpp new file mode 100644 index 0000000..d5ae031 --- /dev/null +++ b/test/binary-validation.cpp @@ -0,0 +1,182 @@ +// bson-validate.cpp + +#include +#include +#include + +static int error_count = 0; + +#define EXPECT_EQ(a, b) \ + do { \ + if (a != b) { \ + std::cerr << "Failed: '" << a << "' != '" << b << "'\n"; \ + error_count++; \ + } \ + } while (0) + +using json = nlohmann::json; +using validator = nlohmann::json_schema::json_validator; + +// check binary data validation +const json bson_schema = json::parse(R"( +{ + "type": "object", + "properties": { + "standard_string": { + "type": "string" + }, + "binary_data": { + "type": "string", + "contentEncoding": "binary" + } + }, + "additionalProperties": false +} +)"); + +const json array_of_types = json::parse(R"( +{ + "type": "object", + "properties": { + "something": { + "type": ["string", "number", "boolean"], + "contentEncoding": "binary" + } + } +} +)"); + +const json array_of_types_without_binary = json::parse(R"( +{ + "type": "object", + "properties": { + "something": { + "type": ["string", "number", "boolean"] + } + } +} +)"); + +class store_ptr_err_handler : public nlohmann::json_schema::basic_error_handler +{ + void error(const nlohmann::json::json_pointer &ptr, const json &, const std::string &message) override + { + nlohmann::json_schema::basic_error_handler::error(ptr, "", message); + std::cerr << "ERROR: '" << ptr << "' - '" + << "" + << "': " << message << "\n"; + failed_pointers.push_back(ptr); + } + +public: + std::vector failed_pointers; + + void reset() override + { + nlohmann::json_schema::basic_error_handler::reset(); + failed_pointers.clear(); + } +}; + +static void content(const std::string &contentEncoding, const std::string &contentMediaType, const json &instance) +{ + if (instance.type() != json::value_t::binary) + throw std::invalid_argument("invalid instance type for binary content checker"); + + std::cerr << "mediaType: '" << contentMediaType << "', encoding: '" << contentEncoding << "'\n"; + + if (contentMediaType != "") + throw std::invalid_argument("unable to check for contentMediaType " + contentMediaType); + + if (contentEncoding == "binary") { + + } else if (contentEncoding != "") + throw std::invalid_argument("unable to check for contentEncoding " + contentEncoding); +} + +int main() +{ + validator val(nullptr, nullptr, content); + + // create some bson doc + json::binary_t arr; + std::string as_binary = "hello world"; + std::copy(as_binary.begin(), as_binary.end(), std::back_inserter(arr)); + + json binary = json::binary(arr); + + store_ptr_err_handler err; + + ///////////////////////////////////// + val.set_root_schema(bson_schema); + + // all right + val.validate({{"standard_string", "some string"}, {"binary_data", binary}}, err); + EXPECT_EQ(err.failed_pointers.size(), 0); + err.reset(); + + // invalid binary data + val.validate({{"binary_data", "string, but expect binary data"}}, err); + EXPECT_EQ(err.failed_pointers.size(), 2); + EXPECT_EQ(err.failed_pointers[0].to_string(), "/binary_data"); + EXPECT_EQ(err.failed_pointers[1].to_string(), "/binary_data"); // second error comes from content-checker + err.reset(); + + // also check that simple string not accept binary data + val.validate({{"standard_string", binary}, {"binary_data", binary}}, err); + EXPECT_EQ(err.failed_pointers.size(), 1); + EXPECT_EQ(err.failed_pointers[0].to_string(), "/standard_string"); + err.reset(); + + ///////////////////////////////////// + // check with array of types + + // 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); + EXPECT_EQ(err.failed_pointers.size(), 4); // binary encoding invalidated all other types + err.reset(); + + // check binary data + val.validate({{"something", binary}}, err); + 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); + EXPECT_EQ(err.failed_pointers.size(), 1); + EXPECT_EQ(err.failed_pointers[0], "/something"); + err.reset(); + + // check without content-callback everything fails + validator val_no_content; + + ///////////////////////////////////// + val_no_content.set_root_schema(bson_schema); + + // all right + val_no_content.validate({{"standard_string", "some string"}, {"binary_data", binary}}, err); + EXPECT_EQ(err.failed_pointers.size(), 1); + err.reset(); + + // invalid binary data + val_no_content.validate({{"binary_data", "string, but expect binary data"}}, err); + EXPECT_EQ(err.failed_pointers.size(), 2); + EXPECT_EQ(err.failed_pointers[0].to_string(), "/binary_data"); + EXPECT_EQ(err.failed_pointers[1].to_string(), "/binary_data"); // second error comes from content-checker + err.reset(); + + // also check that simple string not accept binary data + val_no_content.validate({{"standard_string", binary}, {"binary_data", binary}}, err); + EXPECT_EQ(err.failed_pointers.size(), 2); + EXPECT_EQ(err.failed_pointers[0].to_string(), "/binary_data"); + EXPECT_EQ(err.failed_pointers[1].to_string(), "/standard_string"); + err.reset(); + + + return error_count; +}