proxygen
JSONSchemaTest.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2015-present Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 // Copyright 2004-present Facebook. All Rights Reserved.
17 
19 #include <folly/json.h>
21 
22 using folly::dynamic;
23 using folly::parseJson;
24 using namespace folly::jsonschema;
25 using namespace std;
26 
27 bool check(const dynamic& schema, const dynamic& value, bool check = true) {
28  if (check) {
29  auto schemavalidator = makeSchemaValidator();
30  auto ew = schemavalidator->try_validate(schema);
31  if (ew) {
32  return false;
33  }
34  }
35 
36  auto validator = makeValidator(schema);
37  auto ew = validator->try_validate(value);
38  if (validator->try_validate(value)) {
39  return false;
40  }
41  return true;
42 }
43 
44 TEST(JSONSchemaTest, TestMultipleOfInt) {
45  dynamic schema = dynamic::object("multipleOf", 2);
46  ASSERT_TRUE(check(schema, "invalid"));
47  ASSERT_TRUE(check(schema, 30));
48  ASSERT_TRUE(check(schema, 24.0));
49  ASSERT_FALSE(check(schema, 5));
50  ASSERT_FALSE(check(schema, 2.01));
51 }
52 
53 TEST(JSONSchemaTest, TestMultipleOfDouble) {
54  dynamic schema = dynamic::object("multipleOf", 1.5);
55  ASSERT_TRUE(check(schema, "invalid"));
56  ASSERT_TRUE(check(schema, 30));
57  ASSERT_TRUE(check(schema, 24.0));
58  ASSERT_FALSE(check(schema, 5));
59  ASSERT_FALSE(check(schema, 2.01));
60 
61  schema = dynamic::object("multipleOf", 0.0001);
62  ASSERT_TRUE(check(schema, 0.0075));
63 }
64 
65 TEST(JSONSchemaTest, TestMinimumIntInclusive) {
66  dynamic schema = dynamic::object("minimum", 2);
67  ASSERT_TRUE(check(schema, "invalid"));
68  ASSERT_TRUE(check(schema, 30));
69  ASSERT_TRUE(check(schema, 24.0));
70  ASSERT_TRUE(check(schema, 2));
71  ASSERT_FALSE(check(schema, 1));
72  ASSERT_FALSE(check(schema, 1.9999));
73 }
74 
75 TEST(JSONSchemaTest, TestMinimumIntExclusive) {
76  dynamic schema = dynamic::object("minimum", 2)("exclusiveMinimum", true);
77  ASSERT_FALSE(check(schema, 2));
78 }
79 
80 TEST(JSONSchemaTest, TestMaximumIntInclusive) {
81  dynamic schema = dynamic::object("maximum", 12);
82  ASSERT_TRUE(check(schema, "invalid"));
83  ASSERT_TRUE(check(schema, 3));
84  ASSERT_TRUE(check(schema, 3.1));
85  ASSERT_TRUE(check(schema, 12));
86  ASSERT_FALSE(check(schema, 13));
87  ASSERT_FALSE(check(schema, 12.0001));
88 }
89 
90 TEST(JSONSchemaTest, TestMaximumIntExclusive) {
91  dynamic schema = dynamic::object("maximum", 2)("exclusiveMaximum", true);
92  ASSERT_FALSE(check(schema, 2));
93 }
94 
95 TEST(JSONSchemaTest, TestMinimumDoubleInclusive) {
96  dynamic schema = dynamic::object("minimum", 1.75);
97  ASSERT_TRUE(check(schema, "invalid"));
98  ASSERT_TRUE(check(schema, 30));
99  ASSERT_TRUE(check(schema, 24.0));
100  ASSERT_TRUE(check(schema, 1.75));
101  ASSERT_FALSE(check(schema, 1));
102  ASSERT_FALSE(check(schema, 1.74));
103 }
104 
105 TEST(JSONSchemaTest, TestMinimumDoubleExclusive) {
106  dynamic schema = dynamic::object("minimum", 1.75)("exclusiveMinimum", true);
107  ASSERT_FALSE(check(schema, 1.75));
108 }
109 
110 TEST(JSONSchemaTest, TestMaximumDoubleInclusive) {
111  dynamic schema = dynamic::object("maximum", 12.75);
112  ASSERT_TRUE(check(schema, "invalid"));
113  ASSERT_TRUE(check(schema, 3));
114  ASSERT_TRUE(check(schema, 3.1));
115  ASSERT_TRUE(check(schema, 12.75));
116  ASSERT_FALSE(check(schema, 13));
117  ASSERT_FALSE(check(schema, 12.76));
118 }
119 
120 TEST(JSONSchemaTest, TestMaximumDoubleExclusive) {
121  dynamic schema = dynamic::object("maximum", 12.75)("exclusiveMaximum", true);
122  ASSERT_FALSE(check(schema, 12.75));
123 }
124 
125 TEST(JSONSchemaTest, TestInvalidSchema) {
126  dynamic schema = dynamic::object("multipleOf", "invalid");
127  // don't check the schema since it's meant to be invalid
128  ASSERT_TRUE(check(schema, 30, false));
129 
130  schema = dynamic::object("minimum", "invalid")("maximum", "invalid");
131  ASSERT_TRUE(check(schema, 2, false));
132 
133  schema = dynamic::object("minLength", "invalid")("maxLength", "invalid");
134  ASSERT_TRUE(check(schema, 2, false));
135  ASSERT_TRUE(check(schema, "foo", false));
136 }
137 
138 TEST(JSONSchemaTest, TestMinimumStringLength) {
139  dynamic schema = dynamic::object("minLength", 3);
140  ASSERT_TRUE(check(schema, "abcde"));
141  ASSERT_TRUE(check(schema, "abc"));
142  ASSERT_FALSE(check(schema, "a"));
143 }
144 
145 TEST(JSONSchemaTest, TestMaximumStringLength) {
146  dynamic schema = dynamic::object("maxLength", 3);
147  ASSERT_FALSE(check(schema, "abcde"));
148  ASSERT_TRUE(check(schema, "abc"));
149  ASSERT_TRUE(check(schema, "a"));
150 }
151 
152 TEST(JSONSchemaTest, TestStringPattern) {
153  dynamic schema = dynamic::object("pattern", "[1-9]+");
154  ASSERT_TRUE(check(schema, "123"));
155  ASSERT_FALSE(check(schema, "abc"));
156 }
157 
158 TEST(JSONSchemaTest, TestMinimumArrayItems) {
159  dynamic schema = dynamic::object("minItems", 3);
160  ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3, 4, 5)));
161  ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3)));
162  ASSERT_FALSE(check(schema, dynamic::array(1)));
163 }
164 
165 TEST(JSONSchemaTest, TestMaximumArrayItems) {
166  dynamic schema = dynamic::object("maxItems", 3);
167  ASSERT_FALSE(check(schema, dynamic::array(1, 2, 3, 4, 5)));
168  ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3)));
169  ASSERT_TRUE(check(schema, dynamic::array(1)));
170  ASSERT_TRUE(check(schema, "foobar"));
171 }
172 
173 TEST(JSONSchemaTest, TestArrayUniqueItems) {
174  dynamic schema = dynamic::object("uniqueItems", true);
175  ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3)));
176  ASSERT_FALSE(check(schema, dynamic::array(1, 2, 3, 1)));
177  ASSERT_FALSE(check(schema, dynamic::array("cat", "dog", 1, 2, "cat")));
179  schema,
181  dynamic::object("foo", "bar"), dynamic::object("foo", "baz"))));
182 
183  schema = dynamic::object("uniqueItems", false);
184  ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3, 1)));
185 }
186 
187 TEST(JSONSchemaTest, TestArrayItems) {
188  dynamic schema = dynamic::object("items", dynamic::object("minimum", 2));
189  ASSERT_TRUE(check(schema, dynamic::array(2, 3, 4)));
190  ASSERT_FALSE(check(schema, dynamic::array(3, 4, 1)));
191 }
192 
193 TEST(JSONSchemaTest, TestArrayAdditionalItems) {
195  "items",
197  dynamic::object("minimum", 2), dynamic::object("minimum", 1)))(
198  "additionalItems", dynamic::object("minimum", 3));
199  ASSERT_TRUE(check(schema, dynamic::array(2, 1, 3, 3, 3, 3, 4)));
200  ASSERT_FALSE(check(schema, dynamic::array(2, 1, 3, 3, 3, 3, 1)));
201 }
202 
203 TEST(JSONSchemaTest, TestArrayNoAdditionalItems) {
204  dynamic schema =
205  dynamic::object("items", dynamic::array(dynamic::object("minimum", 2)))(
206  "additionalItems", false);
207  ASSERT_FALSE(check(schema, dynamic::array(3, 3, 3)));
208 }
209 
210 TEST(JSONSchemaTest, TestArrayItemsNotPresent) {
211  dynamic schema = dynamic::object("additionalItems", false);
212  ASSERT_TRUE(check(schema, dynamic::array(3, 3, 3)));
213 }
214 
215 TEST(JSONSchemaTest, TestRef) {
217  "definitions",
219  "positiveInteger", dynamic::object("minimum", 1)("type", "integer")))(
220  "items", dynamic::object("$ref", "#/definitions/positiveInteger"));
221  ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3, 4)));
222  ASSERT_FALSE(check(schema, dynamic::array(4, -5)));
223 }
224 
225 TEST(JSONSchemaTest, TestRecursiveRef) {
227  "properties", dynamic::object("more", dynamic::object("$ref", "#")));
229  ASSERT_TRUE(check(schema, d));
230  d["more"] = dynamic::object;
231  ASSERT_TRUE(check(schema, d));
232  d["more"]["more"] = dynamic::object;
233  ASSERT_TRUE(check(schema, d));
234  d["more"]["more"]["more"] = dynamic::object;
235  ASSERT_TRUE(check(schema, d));
236 }
237 
238 TEST(JSONSchemaTest, TestDoubleRecursiveRef) {
240  "properties",
241  dynamic::object("more", dynamic::object("$ref", "#"))(
242  "less", dynamic::object("$ref", "#")));
244  ASSERT_TRUE(check(schema, d));
245  d["more"] = dynamic::object;
246  d["less"] = dynamic::object;
247  ASSERT_TRUE(check(schema, d));
248  d["more"]["less"] = dynamic::object;
249  d["less"]["mode"] = dynamic::object;
250  ASSERT_TRUE(check(schema, d));
251 }
252 
253 TEST(JSONSchemaTest, TestInfinitelyRecursiveRef) {
254  dynamic schema = dynamic::object("not", dynamic::object("$ref", "#"));
255  auto validator = makeValidator(schema);
256  ASSERT_THROW(validator->validate(dynamic::array(1, 2)), std::runtime_error);
257 }
258 
259 TEST(JSONSchemaTest, TestRequired) {
260  dynamic schema = dynamic::object("required", dynamic::array("foo", "bar"));
261  ASSERT_FALSE(check(schema, dynamic::object("foo", 123)));
262  ASSERT_FALSE(check(schema, dynamic::object("bar", 123)));
263  ASSERT_TRUE(check(schema, dynamic::object("bar", 123)("foo", 456)));
264 }
265 
266 TEST(JSONSchemaTest, TestMinMaxProperties) {
267  dynamic schema = dynamic::object("minProperties", 1)("maxProperties", 3);
269  ASSERT_FALSE(check(schema, d));
270  d["a"] = 1;
271  ASSERT_TRUE(check(schema, d));
272  d["b"] = 2;
273  ASSERT_TRUE(check(schema, d));
274  d["c"] = 3;
275  ASSERT_TRUE(check(schema, d));
276  d["d"] = 4;
277  ASSERT_FALSE(check(schema, d));
278 }
279 
280 TEST(JSONSchemaTest, TestProperties) {
282  "properties", dynamic::object("p1", dynamic::object("minimum", 1)))(
283  "patternProperties", dynamic::object("[0-9]+", dynamic::object))(
284  "additionalProperties", dynamic::object("maximum", 5));
285  ASSERT_TRUE(check(schema, dynamic::object("p1", 1)));
286  ASSERT_FALSE(check(schema, dynamic::object("p1", 0)));
287  ASSERT_TRUE(check(schema, dynamic::object("123", "anything")));
288  ASSERT_TRUE(check(schema, dynamic::object("123", 500)));
289  ASSERT_TRUE(check(schema, dynamic::object("other_property", 4)));
290  ASSERT_FALSE(check(schema, dynamic::object("other_property", 6)));
291 }
292 TEST(JSONSchemaTest, TestPropertyAndPattern) {
294  "properties", dynamic::object("p1", dynamic::object("minimum", 1)))(
295  "patternProperties",
296  dynamic::object("p.", dynamic::object("maximum", 5)));
297  ASSERT_TRUE(check(schema, dynamic::object("p1", 3)));
298  ASSERT_FALSE(check(schema, dynamic::object("p1", 0)));
299  ASSERT_FALSE(check(schema, dynamic::object("p1", 6)));
300 }
301 
302 TEST(JSONSchemaTest, TestPropertyDependency) {
304  "dependencies", dynamic::object("p1", dynamic::array("p2")));
306  ASSERT_TRUE(check(schema, dynamic::object("p1", 1)("p2", 1)));
307  ASSERT_FALSE(check(schema, dynamic::object("p1", 1)));
308 }
309 
310 TEST(JSONSchemaTest, TestSchemaDependency) {
312  "dependencies",
313  dynamic::object("p1", dynamic::object("required", dynamic::array("p2"))));
315  ASSERT_TRUE(check(schema, dynamic::object("p1", 1)("p2", 1)));
316  ASSERT_FALSE(check(schema, dynamic::object("p1", 1)));
317 }
318 
319 TEST(JSONSchemaTest, TestEnum) {
320  dynamic schema = dynamic::object("enum", dynamic::array("a", 1));
321  ASSERT_TRUE(check(schema, "a"));
322  ASSERT_TRUE(check(schema, 1));
323  ASSERT_FALSE(check(schema, "b"));
324 }
325 
326 TEST(JSONSchemaTest, TestType) {
327  dynamic schema = dynamic::object("type", "object");
329  ASSERT_FALSE(check(schema, dynamic(5)));
330 }
331 
332 TEST(JSONSchemaTest, TestTypeArray) {
333  dynamic schema = dynamic::object("type", dynamic::array("array", "number"));
334  ASSERT_TRUE(check(schema, dynamic(5)));
335  ASSERT_TRUE(check(schema, dynamic(1.1)));
337 }
338 
339 TEST(JSONSchemaTest, TestAllOf) {
341  "allOf",
343  dynamic::object("minimum", 1), dynamic::object("type", "integer")));
344  ASSERT_TRUE(check(schema, 2));
345  ASSERT_FALSE(check(schema, 0));
346  ASSERT_FALSE(check(schema, 1.1));
347 }
348 
349 TEST(JSONSchemaTest, TestAnyOf) {
351  "anyOf",
353  dynamic::object("minimum", 1), dynamic::object("type", "integer")));
354  ASSERT_TRUE(check(schema, 2)); // matches both
355  ASSERT_FALSE(check(schema, 0.1)); // matches neither
356  ASSERT_TRUE(check(schema, 1.1)); // matches first one
357  ASSERT_TRUE(check(schema, 0)); // matches second one
358 }
359 
360 TEST(JSONSchemaTest, TestOneOf) {
362  "oneOf",
364  dynamic::object("minimum", 1), dynamic::object("type", "integer")));
365  ASSERT_FALSE(check(schema, 2)); // matches both
366  ASSERT_FALSE(check(schema, 0.1)); // matches neither
367  ASSERT_TRUE(check(schema, 1.1)); // matches first one
368  ASSERT_TRUE(check(schema, 0)); // matches second one
369 }
370 
371 TEST(JSONSchemaTest, TestNot) {
372  dynamic schema =
373  dynamic::object("not", dynamic::object("minimum", 5)("maximum", 10));
374  ASSERT_TRUE(check(schema, 4));
375  ASSERT_FALSE(check(schema, 7));
376  ASSERT_TRUE(check(schema, 11));
377 }
378 
379 // The tests below use some sample schema from json-schema.org
380 
381 TEST(JSONSchemaTest, TestMetaSchema) {
382  const char* example1 =
383  "\
384  { \
385  \"title\": \"Example Schema\", \
386  \"type\": \"object\", \
387  \"properties\": { \
388  \"firstName\": { \
389  \"type\": \"string\" \
390  }, \
391  \"lastName\": { \
392  \"type\": \"string\" \
393  }, \
394  \"age\": { \
395  \"description\": \"Age in years\", \
396  \"type\": \"integer\", \
397  \"minimum\": 0 \
398  } \
399  }, \
400  \"required\": [\"firstName\", \"lastName\"] \
401  }";
402 
403  auto val = makeSchemaValidator();
404  val->validate(parseJson(example1)); // doesn't throw
405 
406  ASSERT_THROW(val->validate("123"), std::runtime_error);
407 }
408 
409 TEST(JSONSchemaTest, TestProductSchema) {
410  const char* productSchema =
411  "\
412  { \
413  \"$schema\": \"http://json-schema.org/draft-04/schema#\", \
414  \"title\": \"Product\", \
415  \"description\": \"A product from Acme's catalog\", \
416  \"type\": \"object\", \
417  \"properties\": { \
418  \"id\": { \
419  \"description\": \"The unique identifier for a product\", \
420  \"type\": \"integer\" \
421  }, \
422  \"name\": { \
423  \"description\": \"Name of the product\", \
424  \"type\": \"string\" \
425  }, \
426  \"price\": { \
427  \"type\": \"number\", \
428  \"minimum\": 0, \
429  \"exclusiveMinimum\": true \
430  }, \
431  \"tags\": { \
432  \"type\": \"array\", \
433  \"items\": { \
434  \"type\": \"string\" \
435  }, \
436  \"minItems\": 1, \
437  \"uniqueItems\": true \
438  } \
439  }, \
440  \"required\": [\"id\", \"name\", \"price\"] \
441  }";
442  const char* product =
443  "\
444  { \
445  \"id\": 1, \
446  \"name\": \"A green door\", \
447  \"price\": 12.50, \
448  \"tags\": [\"home\", \"green\"] \
449  }";
450  ASSERT_TRUE(check(parseJson(productSchema), parseJson(product)));
451 }
static ObjectMaker object()
Definition: dynamic-inl.h:240
auto product
Definition: BaseTest.cpp:73
dynamic parseJson(StringPiece range)
Definition: json.cpp:900
std::shared_ptr< Validator > makeSchemaValidator()
STL namespace.
double val
Definition: String.cpp:273
TestEnum
Definition: HashTest.cpp:228
const dynamic & schema
Definition: JSONSchema.cpp:103
std::unique_ptr< Validator > makeValidator(const dynamic &schema)
#define ASSERT_THROW(statement, expected_exception)
Definition: gtest.h:1849
static void array(EmptyArrayTag)
Definition: dynamic-inl.h:233
uint64_t value(const typename LockFreeRingBuffer< T, Atom >::Cursor &rbcursor)
#define ASSERT_FALSE(condition)
Definition: gtest.h:1868
#define ASSERT_TRUE(condition)
Definition: gtest.h:1865
TEST(SequencedExecutor, CPUThreadPoolExecutor)
bool check(const dynamic &schema, const dynamic &value, bool check=true)