MQTTSuite
Loading...
Searching...
No Matches
issue-105-verbose-combination-errors.cpp
Go to the documentation of this file.
1#include "nlohmann/json-schema.hpp"
2#include "nlohmann/json.hpp"
3
4#include <iostream>
5#include <regex>
6#include <string>
7#include <vector>
8
9//==============================================================================
10// Test macros
11//==============================================================================
12#define LOG_ERROR(LOG_ERROR__ARGS)
13 std::cerr << __FILE__ << ":" << __LINE__ << ": " << LOG_ERROR__ARGS << std::endl
14
15#define EXPECT_THROW_WITH_MESSAGE(EXPRESSION, MESSAGE)
16 do {
17 try {
18 EXPRESSION;
19 LOG_ERROR("Expected exception not thrown with matching regex: \"" << MESSAGE << "\"");
20 ++g_error_count;
21 } catch (const std::exception &error) {
22 const std::regex error_re{MESSAGE};
23 if (!std::regex_search(error.what(), error_re)) {
24 LOG_ERROR("Expected exception with matching regex: \"" << MESSAGE << "\", but got this instead: " << error.what());
25 ++g_error_count;
26 }
27 }
28 } while (false)
29
30#define ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, RETURN_IN_CASE_OF_ERROR)
31 do {
32 if ((FIRST_THING) != (SECOND_THING)) {
33 LOG_ERROR("The two values of " << (FIRST_THING) << " (" #FIRST_THING << ") and " << (SECOND_THING) << " (" #SECOND_THING << ") should be equal");
34 if (RETURN_IN_CASE_OF_ERROR) {
35 return;
36 }
37 }
38 } while (false)
39
40#define ASSERT_EQ(FIRST_THING, SECOND_THING) ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, true)
41#define EXPECT_EQ(FIRST_THING, SECOND_THING) ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, true)
42
43#define EXPECT_MATCH(STRING, REGEX)
44 do {
45 if (!std::regex_search((STRING), std::regex{(REGEX)})) {
46 LOG_ERROR("String \"" << (STRING) << "\" doesn't match with regex: \"" << (REGEX) << "\"");
47 ++g_error_count;
48 }
49 } while (false)
50
51namespace
52{
53
54//==============================================================================
55// Test environment
56//==============================================================================
58
59//==============================================================================
60// The schema used for testing
61//==============================================================================
62const std::string g_schema_template = R"(
63{
64 "properties": {
65 "first": {
66 "%COMBINATION_FIRST_LEVEL%": [
67 {
68 "properties": {
69 "second": {
70 "%COMBINATION_SECOND_LEVEL%": [
71 {
72 "minimum": 5,
73 "type": "integer"
74 },
75 {
76 "multipleOf": 2,
77 "type": "integer"
78 }
79 ]
80 }
81 },
82 "type": "object"
83 },
84 {
85 "minimum": 20,
86 "type": "integer"
87 },
88 {
89 "minLength": 10,
90 "type": "string"
91 }
92 ]
93 }
94 },
95 "type": "object"
96}
97)";
98
99auto generateSchema(const std::string &first_combination, const std::string &second_combination) -> nlohmann::json
100{
101 static const std::regex first_replace_re{"%COMBINATION_FIRST_LEVEL%"};
102 static const std::regex second_replace_re{"%COMBINATION_SECOND_LEVEL%"};
103
104 std::string intermediate = std::regex_replace(g_schema_template, first_replace_re, first_combination);
105
106 return nlohmann::json::parse(std::regex_replace(intermediate, second_replace_re, second_combination));
107}
108
109//==============================================================================
110// Error handler to catch all the errors generated by the validator - also inside the combinations
111//==============================================================================
113{
114public:
115 struct ErrorEntry {
116 nlohmann::json::json_pointer ptr;
118 std::string message;
119 };
120
121 using ErrorEntryList = std::vector<ErrorEntry>;
122
123 auto getErrors() const -> const ErrorEntryList &
124 {
125 return m_error_list;
126 }
127
128private:
129 auto error(const nlohmann::json::json_pointer &ptr, const nlohmann::json &instance, const std::string &message) -> void override
130 {
131 m_error_list.push_back(ErrorEntry{ptr, instance, message});
132 }
133
135};
136
137//==============================================================================
138// Error string helpers
139//==============================================================================
140auto operator<<(std::string first, const std::string &second) -> std::string
141{
142 first += ".*";
143 first += second;
144 return first;
145}
146
147auto rootError(const std::string &combination_type, std::size_t number_of_subschemas) -> std::string
148{
149 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);
150}
151
152auto combinationError(const std::string &combination_type, std::size_t test_case_number) -> std::string
153{
154 return "[combination: " + combination_type + " / case#" + std::to_string(test_case_number) + "]";
155}
156
157//==============================================================================
158// Validator function - for simplicity
159//==============================================================================
160auto validate(const nlohmann::json &schema, const nlohmann::json &instance, nlohmann::json_schema::error_handler *error_handler = nullptr) -> void
161{
163 validator.set_root_schema(schema);
164
165 if (error_handler) {
166 validator.validate(instance, *error_handler);
167 } else {
168 validator.validate(instance);
169 }
170}
171
172//==============================================================================
173// The test cases
174//==============================================================================
175auto simpleTest(const std::string &first_combination, const std::string &second_combination) -> void
176{
177 const nlohmann::json schema = generateSchema(first_combination, second_combination);
178 EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", {{"second", 1}}}}), rootError(first_combination, 3));
179 if (second_combination == "oneOf") {
180 EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", {{"second", 8}}}}), rootError(first_combination, 3));
181 }
182 EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", 10}}), rootError(first_combination, 3));
183 EXPECT_THROW_WITH_MESSAGE(validate(schema, nlohmann::json{{"first", "short"}}), rootError(first_combination, 3));
184}
185
186auto verboseTest(const std::string &first_combination, const std::string &second_combination) -> void
187{
188 const nlohmann::json schema = generateSchema(first_combination, second_combination);
189
190 {
191 MyErrorHandler error_handler;
192 validate(schema, nlohmann::json{{"first", {{"second", 1}}}}, &error_handler);
193
194 const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
195 EXPECT_EQ(error_list.size(), 6);
196
197 EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
198 EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
199
200 EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
201 EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << rootError(second_combination, 2));
202
203 EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first/second"});
204 EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 0) << combinationError(second_combination, 0) << "instance is below minimum of 5");
205
206 EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first/second"});
207 EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 0) << combinationError(second_combination, 1) << "instance is not a multiple of 2.0");
208
209 EXPECT_EQ(error_list[4].ptr, nlohmann::json::json_pointer{"/first"});
210 EXPECT_MATCH(error_list[4].message, combinationError(first_combination, 1) << "unexpected instance type");
211
212 EXPECT_EQ(error_list[5].ptr, nlohmann::json::json_pointer{"/first"});
213 EXPECT_MATCH(error_list[5].message, combinationError(first_combination, 2) << "unexpected instance type");
214 }
215
216 {
217 MyErrorHandler error_handler;
218 validate(schema, nlohmann::json{{"first", {{"second", "not-an-integer"}}}}, &error_handler);
219
220 const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
221 EXPECT_EQ(error_list.size(), 6);
222
223 EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
224 EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
225
226 EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
227 EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << rootError(second_combination, 2));
228
229 EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first/second"});
230 EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 0) << combinationError(second_combination, 0) << "unexpected instance type");
231
232 EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first/second"});
233 EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 0) << combinationError(second_combination, 1) << "unexpected instance type");
234
235 EXPECT_EQ(error_list[4].ptr, nlohmann::json::json_pointer{"/first"});
236 EXPECT_MATCH(error_list[4].message, combinationError(first_combination, 1) << "unexpected instance type");
237
238 EXPECT_EQ(error_list[5].ptr, nlohmann::json::json_pointer{"/first"});
239 EXPECT_MATCH(error_list[5].message, combinationError(first_combination, 2) << "unexpected instance type");
240 }
241
242 if (second_combination == "oneOf") {
243 MyErrorHandler error_handler;
244 validate(schema, nlohmann::json{{"first", {{"second", 8}}}}, &error_handler);
245
246 const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
247 EXPECT_EQ(error_list.size(), 4);
248
249 EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
250 EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
251
252 EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first/second"});
253 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");
254
255 EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
256 EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "unexpected instance type");
257
258 EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
259 EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "unexpected instance type");
260 }
261
262 {
263 MyErrorHandler error_handler;
264 validate(schema, nlohmann::json{{"first", 10}}, &error_handler);
265
266 const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
267 EXPECT_EQ(error_list.size(), 4);
268
269 EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
270 EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
271
272 EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first"});
273 EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "unexpected instance type");
274
275 EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
276 EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "instance is below minimum of 20");
277
278 EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
279 EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "unexpected instance type");
280 }
281
282 {
283 MyErrorHandler error_handler;
284 validate(schema, nlohmann::json{{"first", "short"}}, &error_handler);
285
286 const MyErrorHandler::ErrorEntryList &error_list = error_handler.getErrors();
287 EXPECT_EQ(error_list.size(), 4);
288
289 EXPECT_EQ(error_list[0].ptr, nlohmann::json::json_pointer{"/first"});
290 EXPECT_MATCH(error_list[0].message, rootError(first_combination, 3));
291
292 EXPECT_EQ(error_list[1].ptr, nlohmann::json::json_pointer{"/first"});
293 EXPECT_MATCH(error_list[1].message, combinationError(first_combination, 0) << "unexpected instance type");
294
295 EXPECT_EQ(error_list[2].ptr, nlohmann::json::json_pointer{"/first"});
296 EXPECT_MATCH(error_list[2].message, combinationError(first_combination, 1) << "unexpected instance type");
297
298 EXPECT_EQ(error_list[3].ptr, nlohmann::json::json_pointer{"/first"});
299 EXPECT_MATCH(error_list[3].message, combinationError(first_combination, 2) << "instance is too short as per minLength:10");
300 }
301}
302
303} // namespace
304
305//==============================================================================
306// MAIN - calling the test cases
307//==============================================================================
308auto main() -> int
309{
310 simpleTest("anyOf", "anyOf");
311 simpleTest("anyOf", "oneOf");
312 simpleTest("oneOf", "anyOf");
313 simpleTest("oneOf", "oneOf");
314
315 verboseTest("anyOf", "anyOf");
316 verboseTest("anyOf", "oneOf");
317 verboseTest("oneOf", "anyOf");
318 verboseTest("oneOf", "oneOf");
319
320 return g_error_count;
321}
#define EXPECT_EQ(a, b)
auto error(const nlohmann::json::json_pointer &ptr, const nlohmann::json &instance, const std::string &message) -> void override
json validate(const json &, error_handler &, const json_uri &initial_uri=json_uri("#")) const
int main()
Definition format.cpp:34
#define ASSERT_OR_EXPECT_EQ(FIRST_THING, SECOND_THING, RETURN_IN_CASE_OF_ERROR)
#define EXPECT_THROW_WITH_MESSAGE(EXPRESSION, MESSAGE)
#define LOG_ERROR(LOG_ERROR__ARGS)
#define EXPECT_MATCH(STRING, REGEX)
auto combinationError(const std::string &combination_type, std::size_t test_case_number) -> std::string
auto simpleTest(const std::string &first_combination, const std::string &second_combination) -> void
auto validate(const nlohmann::json &schema, const nlohmann::json &instance, nlohmann::json_schema::error_handler *error_handler=nullptr) -> void
auto verboseTest(const std::string &first_combination, const std::string &second_combination) -> void
auto operator<<(std::string first, const std::string &second) -> std::string
auto rootError(const std::string &combination_type, std::size_t number_of_subschemas) -> std::string
auto generateSchema(const std::string &first_combination, const std::string &second_combination) -> nlohmann::json