322 lines
15 KiB
C++
322 lines
15 KiB
C++
#include "nlohmann/json-schema.hpp"
|
|
#include "nlohmann/json.hpp"
|
|
|
|
#include <iostream>
|
|
#include <regex>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
//==============================================================================
|
|
// Test macros
|
|
//==============================================================================
|
|
#define LOG_ERROR(LOG_ERROR__ARGS) \
|
|
std::cerr << __FILE__ << ":" << __LINE__ << ": " << LOG_ERROR__ARGS << std::endl
|
|
|
|
#define EXPECT_THROW_WITH_MESSAGE(EXPRESSION, MESSAGE) \
|
|
do { \
|
|
try { \
|
|
EXPRESSION; \
|
|
LOG_ERROR("Expected exception not thrown with matching regex: \"" << MESSAGE << "\""); \
|
|
++g_error_count; \
|
|
} catch (const std::exception &error) { \
|
|
const std::regex error_re{MESSAGE}; \
|
|
if (!std::regex_search(error.what(), error_re)) { \
|
|
LOG_ERROR("Expected exception with matching regex: \"" << MESSAGE << "\", but got this instead: " << error.what()); \
|
|
++g_error_count; \
|
|
} \
|
|
} \
|
|
} while (false)
|
|
|
|
#define ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, RETURN_IN_CASE_OF_ERROR) \
|
|
do { \
|
|
if ((FIRST_THING) != (SECOND_THING)) { \
|
|
LOG_ERROR("The two values of " << (FIRST_THING) << " (" #FIRST_THING << ") and " << (SECOND_THING) << " (" #SECOND_THING << ") should be equal"); \
|
|
if (RETURN_IN_CASE_OF_ERROR) { \
|
|
return; \
|
|
} \
|
|
} \
|
|
} while (false)
|
|
|
|
#define ASSERT_EQ(FIRST_THING, SECOND_THING) ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, true)
|
|
#define EXPECT_EQ(FIRST_THING, SECOND_THING) ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, true)
|
|
|
|
#define EXPECT_MATCH(STRING, REGEX) \
|
|
do { \
|
|
if (!std::regex_search((STRING), std::regex{(REGEX)})) { \
|
|
LOG_ERROR("String \"" << (STRING) << "\" doesn't match with regex: \"" << (REGEX) << "\""); \
|
|
++g_error_count; \
|
|
} \
|
|
} while (false)
|
|
|
|
namespace
|
|
{
|
|
|
|
//==============================================================================
|
|
// Test environment
|
|
//==============================================================================
|
|
int g_error_count = 0;
|
|
|
|
//==============================================================================
|
|
// The schema used for testing
|
|
//==============================================================================
|
|
const std::string g_schema_template = R"(
|
|
{
|
|
"properties": {
|
|
"first": {
|
|
"%COMBINATION_FIRST_LEVEL%": [
|
|
{
|
|
"properties": {
|
|
"second": {
|
|
"%COMBINATION_SECOND_LEVEL%": [
|
|
{
|
|
"minimum": 5,
|
|
"type": "integer"
|
|
},
|
|
{
|
|
"multipleOf": 2,
|
|
"type": "integer"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "object"
|
|
},
|
|
{
|
|
"minimum": 20,
|
|
"type": "integer"
|
|
},
|
|
{
|
|
"minLength": 10,
|
|
"type": "string"
|
|
}
|
|
]
|
|
}
|
|
},
|
|
"type": "object"
|
|
}
|
|
)";
|
|
|
|
auto generateSchema(const std::string &first_combination, const std::string &second_combination) -> nlohmann::json
|
|
{
|
|
static const std::regex first_replace_re{"%COMBINATION_FIRST_LEVEL%"};
|
|
static const std::regex second_replace_re{"%COMBINATION_SECOND_LEVEL%"};
|
|
|
|
std::string intermediate = std::regex_replace(g_schema_template, first_replace_re, first_combination);
|
|
|
|
return nlohmann::json::parse(std::regex_replace(intermediate, second_replace_re, second_combination));
|
|
}
|
|
|
|
//==============================================================================
|
|
// Error handler to catch all the errors generated by the validator - also inside the combinations
|
|
//==============================================================================
|
|
class MyErrorHandler : public nlohmann::json_schema::error_handler
|
|
{
|
|
public:
|
|
struct ErrorEntry {
|
|
nlohmann::json::json_pointer ptr;
|
|
nlohmann::json intance;
|
|
std::string message;
|
|
};
|
|
|
|
using ErrorEntryList = std::vector<ErrorEntry>;
|
|
|
|
auto getErrors() const -> const ErrorEntryList &
|
|
{
|
|
return m_error_list;
|
|
}
|
|
|
|
private:
|
|
auto error(const nlohmann::json::json_pointer &ptr, const nlohmann::json &instance, const std::string &message) -> void override
|
|
{
|
|
m_error_list.push_back(ErrorEntry{ptr, instance, message});
|
|
}
|
|
|
|
ErrorEntryList m_error_list;
|
|
};
|
|
|
|
//==============================================================================
|
|
// Error string helpers
|
|
//==============================================================================
|
|
auto operator<<(std::string first, const std::string &second) -> std::string
|
|
{
|
|
first += ".*";
|
|
first += second;
|
|
return first;
|
|
}
|
|
|
|
auto rootError(const std::string &combination_type, std::size_t number_of_subschemas) -> std::string
|
|
{
|
|
return "no subschema has succeeded, but one of them is required to validate. Type: " + combination_type + ", number of failed subschemas: " + std::to_string(number_of_subschemas);
|
|
}
|
|
|
|
auto combinationError(const std::string &combination_type, std::size_t test_case_number) -> std::string
|
|
{
|
|
return "[combination: " + combination_type + " / case#" + std::to_string(test_case_number) + "]";
|
|
}
|
|
|
|
//==============================================================================
|
|
// Validator function - for simplicity
|
|
//==============================================================================
|
|
auto validate(const nlohmann::json &schema, const nlohmann::json &instance, nlohmann::json_schema::error_handler *error_handler = nullptr) -> void
|
|
{
|
|
nlohmann::json_schema::json_validator validator;
|
|
validator.set_root_schema(schema);
|
|
|
|
if (error_handler) {
|
|
validator.validate(instance, *error_handler);
|
|
} else {
|
|
validator.validate(instance);
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
// The test cases
|
|
//==============================================================================
|
|
auto simpleTest(const std::string &first_combination, const std::string &second_combination) -> void
|
|
{
|
|
const nlohmann::json schema = generateSchema(first_combination, second_combination);
|
|
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", {{"second", 1}}}}), rootError(first_combination, 3));
|
|
if (second_combination == "oneOf") {
|
|
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", {{"second", 8}}}}), rootError(first_combination, 3));
|
|
}
|
|
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", 10}}), rootError(first_combination, 3));
|
|
EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", "short"}}), rootError(first_combination, 3));
|
|
}
|
|
|
|
auto verboseTest(const std::string &first_combination, const std::string &second_combination) -> void
|
|
{
|
|
const nlohmann::json schema = generateSchema(first_combination, second_combination);
|
|
|
|
{
|
|
MyErrorHandler error_handler;
|
|
validate(schema, nlohmann::json{{"first", {{"second", 1}}}}, &error_handler);
|
|
|
|
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
|
|
EXPECT_EQ(error_list.size(), 6);
|
|
|
|
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
|
|
|
|
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
|
|
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << rootError(second_combination, 2));
|
|
|
|
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first/second"});
|
|
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 0) << combinationError(second_combination, 0) << "instance is below minimum of 5");
|
|
|
|
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first/second"});
|
|
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 0) << combinationError(second_combination, 1) << "instance is not a multiple of 2.0");
|
|
|
|
EXPECT_EQ(error_list[4].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[4].message, combinationError(first_combination, 1) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[5].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[5].message, combinationError(first_combination, 2) << "unexpected instance type");
|
|
}
|
|
|
|
{
|
|
MyErrorHandler error_handler;
|
|
validate(schema, nlohmann::json{{"first", {{"second", "not-an-integer"}}}}, &error_handler);
|
|
|
|
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
|
|
EXPECT_EQ(error_list.size(), 6);
|
|
|
|
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
|
|
|
|
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
|
|
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << rootError(second_combination, 2));
|
|
|
|
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first/second"});
|
|
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 0) << combinationError(second_combination, 0) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first/second"});
|
|
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 0) << combinationError(second_combination, 1) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[4].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[4].message, combinationError(first_combination, 1) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[5].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[5].message, combinationError(first_combination, 2) << "unexpected instance type");
|
|
}
|
|
|
|
if (second_combination == "oneOf") {
|
|
MyErrorHandler error_handler;
|
|
validate(schema, nlohmann::json{{"first", {{"second", 8}}}}, &error_handler);
|
|
|
|
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
|
|
EXPECT_EQ(error_list.size(), 4);
|
|
|
|
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
|
|
|
|
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
|
|
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "more than one subschema has succeeded, but exactly one of them is required to validate");
|
|
|
|
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "unexpected instance type");
|
|
}
|
|
|
|
{
|
|
MyErrorHandler error_handler;
|
|
validate(schema, nlohmann::json{{"first", 10}}, &error_handler);
|
|
|
|
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
|
|
EXPECT_EQ(error_list.size(), 4);
|
|
|
|
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
|
|
|
|
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "instance is below minimum of 20");
|
|
|
|
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "unexpected instance type");
|
|
}
|
|
|
|
{
|
|
MyErrorHandler error_handler;
|
|
validate(schema, nlohmann::json{{"first", "short"}}, &error_handler);
|
|
|
|
const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
|
|
EXPECT_EQ(error_list.size(), 4);
|
|
|
|
EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
|
|
|
|
EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "unexpected instance type");
|
|
|
|
EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
|
|
EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "instance is too short as per minLength:10");
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
//==============================================================================
|
|
// MAIN - calling the test cases
|
|
//==============================================================================
|
|
auto main() -> int
|
|
{
|
|
simpleTest("anyOf", "anyOf");
|
|
simpleTest("anyOf", "oneOf");
|
|
simpleTest("oneOf", "anyOf");
|
|
simpleTest("oneOf", "oneOf");
|
|
|
|
verboseTest("anyOf", "anyOf");
|
|
verboseTest("anyOf", "oneOf");
|
|
verboseTest("oneOf", "anyOf");
|
|
verboseTest("oneOf", "oneOf");
|
|
|
|
return g_error_count;
|
|
}
|