2 * Copyright 2015 Facebook, Inc.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 // Copyright 2004-present Facebook. All Rights Reserved.
18 #include <folly/experimental/JSONSchema.h>
19 #include <folly/json.h>
20 #include <gtest/gtest.h>
23 using folly::parseJson;
24 using namespace folly::jsonschema;
27 bool check(const dynamic& schema, const dynamic& value, bool check = true) {
29 auto schemavalidator = makeSchemaValidator();
30 auto ew = schemavalidator->try_validate(schema);
36 auto validator = makeValidator(schema);
37 auto ew = validator->try_validate(value);
38 if (validator->try_validate(value)) {
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));
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));
61 schema = dynamic::object("multipleOf", 0.0001);
62 ASSERT_TRUE(check(schema, 0.0075));
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));
75 TEST(JSONSchemaTest, TestMinimumIntExclusive) {
76 dynamic schema = dynamic::object("minimum", 2)("exclusiveMinimum", true);
77 ASSERT_FALSE(check(schema, 2));
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));
90 TEST(JSONSchemaTest, TestMaximumIntExclusive) {
91 dynamic schema = dynamic::object("maximum", 2)("exclusiveMaximum", true);
92 ASSERT_FALSE(check(schema, 2));
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));
105 TEST(JSONSchemaTest, TestMinimumDoubleExclusive) {
106 dynamic schema = dynamic::object("minimum", 1.75)("exclusiveMinimum", true);
107 ASSERT_FALSE(check(schema, 1.75));
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));
120 TEST(JSONSchemaTest, TestMaximumDoubleExclusive) {
121 dynamic schema = dynamic::object("maximum", 12.75)("exclusiveMaximum", true);
122 ASSERT_FALSE(check(schema, 12.75));
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));
130 schema = dynamic::object("minimum", "invalid")("maximum", "invalid");
131 ASSERT_TRUE(check(schema, 2, false));
133 schema = dynamic::object("minLength", "invalid")("maxLength", "invalid");
134 ASSERT_TRUE(check(schema, 2, false));
135 ASSERT_TRUE(check(schema, "foo", false));
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"));
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"));
152 TEST(JSONSchemaTest, TestStringPattern) {
153 dynamic schema = dynamic::object("pattern", "[1-9]+");
154 ASSERT_TRUE(check(schema, "123"));
155 ASSERT_FALSE(check(schema, "abc"));
158 TEST(JSONSchemaTest, TestMinimumArrayItems) {
159 dynamic schema = dynamic::object("minItems", 3);
160 ASSERT_TRUE(check(schema, {1, 2, 3, 4, 5}));
161 ASSERT_TRUE(check(schema, {1, 2, 3}));
162 ASSERT_FALSE(check(schema, {1}));
165 TEST(JSONSchemaTest, TestMaximumArrayItems) {
166 dynamic schema = dynamic::object("maxItems", 3);
167 ASSERT_FALSE(check(schema, {1, 2, 3, 4, 5}));
168 ASSERT_TRUE(check(schema, {1, 2, 3}));
169 ASSERT_TRUE(check(schema, {1}));
170 ASSERT_TRUE(check(schema, "foobar"));
173 TEST(JSONSchemaTest, TestArrayUniqueItems) {
174 dynamic schema = dynamic::object("uniqueItems", true);
175 ASSERT_TRUE(check(schema, {1, 2, 3}));
176 ASSERT_FALSE(check(schema, {1, 2, 3, 1}));
177 ASSERT_FALSE(check(schema, {"cat", "dog", 1, 2, "cat"}));
178 ASSERT_TRUE(check(schema, {
179 dynamic::object("foo", "bar"),
180 dynamic::object("foo", "baz")
183 schema = dynamic::object("uniqueItems", false);
184 ASSERT_TRUE(check(schema, {1, 2, 3, 1}));
187 TEST(JSONSchemaTest, TestArrayItems) {
188 dynamic schema = dynamic::object("items", dynamic::object("minimum", 2));
189 ASSERT_TRUE(check(schema, {2, 3, 4}));
190 ASSERT_FALSE(check(schema, {3, 4, 1}));
193 TEST(JSONSchemaTest, TestArrayAdditionalItems) {
194 dynamic schema = dynamic::object(
195 "items", {dynamic::object("minimum", 2), dynamic::object("minimum", 1)})(
196 "additionalItems", dynamic::object("minimum", 3));
197 ASSERT_TRUE(check(schema, {2, 1, 3, 3, 3, 3, 4}));
198 ASSERT_FALSE(check(schema, {2, 1, 3, 3, 3, 3, 1}));
201 TEST(JSONSchemaTest, TestArrayNoAdditionalItems) {
202 dynamic schema = dynamic::object("items", {dynamic::object("minimum", 2)})(
203 "additionalItems", false);
204 ASSERT_FALSE(check(schema, {3, 3, 3}));
207 TEST(JSONSchemaTest, TestArrayItemsNotPresent) {
208 dynamic schema = dynamic::object("additionalItems", false);
209 ASSERT_TRUE(check(schema, {3, 3, 3}));
212 TEST(JSONSchemaTest, TestRef) {
213 dynamic schema = dynamic::object(
215 dynamic::object("positiveInteger",
216 dynamic::object("minimum", 1)("type", "integer")))(
217 "items", dynamic::object("$ref", "#/definitions/positiveInteger"));
218 ASSERT_TRUE(check(schema, {1, 2, 3, 4}));
219 ASSERT_FALSE(check(schema, {4, -5}));
222 TEST(JSONSchemaTest, TestRecursiveRef) {
223 dynamic schema = dynamic::object(
224 "properties", dynamic::object("more", dynamic::object("$ref", "#")));
225 dynamic d = dynamic::object;
226 ASSERT_TRUE(check(schema, d));
227 d["more"] = dynamic::object;
228 ASSERT_TRUE(check(schema, d));
229 d["more"]["more"] = dynamic::object;
230 ASSERT_TRUE(check(schema, d));
231 d["more"]["more"]["more"] = dynamic::object;
232 ASSERT_TRUE(check(schema, d));
235 TEST(JSONSchemaTest, TestDoubleRecursiveRef) {
237 dynamic::object("properties",
238 dynamic::object("more", dynamic::object("$ref", "#"))(
239 "less", dynamic::object("$ref", "#")));
240 dynamic d = dynamic::object;
241 ASSERT_TRUE(check(schema, d));
242 d["more"] = dynamic::object;
243 d["less"] = dynamic::object;
244 ASSERT_TRUE(check(schema, d));
245 d["more"]["less"] = dynamic::object;
246 d["less"]["mode"] = dynamic::object;
247 ASSERT_TRUE(check(schema, d));
250 TEST(JSONSchemaTest, TestInfinitelyRecursiveRef) {
251 dynamic schema = dynamic::object("not", dynamic::object("$ref", "#"));
252 auto validator = makeValidator(schema);
253 ASSERT_THROW(validator->validate({1, 2}), std::runtime_error);
256 TEST(JSONSchemaTest, TestRequired) {
257 dynamic schema = dynamic::object("required", {"foo", "bar"});
258 ASSERT_FALSE(check(schema, dynamic::object("foo", 123)));
259 ASSERT_FALSE(check(schema, dynamic::object("bar", 123)));
260 ASSERT_TRUE(check(schema, dynamic::object("bar", 123)("foo", 456)));
263 TEST(JSONSchemaTest, TestMinMaxProperties) {
264 dynamic schema = dynamic::object("minProperties", 1)("maxProperties", 3);
265 dynamic d = dynamic::object;
266 ASSERT_FALSE(check(schema, d));
268 ASSERT_TRUE(check(schema, d));
270 ASSERT_TRUE(check(schema, d));
272 ASSERT_TRUE(check(schema, d));
274 ASSERT_FALSE(check(schema, d));
277 TEST(JSONSchemaTest, TestProperties) {
278 dynamic schema = dynamic::object(
279 "properties", dynamic::object("p1", dynamic::object("minimum", 1)))(
280 "patternProperties", dynamic::object("[0-9]+", dynamic::object))(
281 "additionalProperties", dynamic::object("maximum", 5));
282 ASSERT_TRUE(check(schema, dynamic::object("p1", 1)));
283 ASSERT_FALSE(check(schema, dynamic::object("p1", 0)));
284 ASSERT_TRUE(check(schema, dynamic::object("123", "anything")));
285 ASSERT_TRUE(check(schema, dynamic::object("123", 500)));
286 ASSERT_TRUE(check(schema, dynamic::object("other_property", 4)));
287 ASSERT_FALSE(check(schema, dynamic::object("other_property", 6)));
289 TEST(JSONSchemaTest, TestPropertyAndPattern) {
290 dynamic schema = dynamic::object
291 ("properties", dynamic::object("p1", dynamic::object("minimum", 1)))
292 ("patternProperties", dynamic::object("p.", dynamic::object("maximum", 5)));
293 ASSERT_TRUE(check(schema, dynamic::object("p1", 3)));
294 ASSERT_FALSE(check(schema, dynamic::object("p1", 0)));
295 ASSERT_FALSE(check(schema, dynamic::object("p1", 6)));
298 TEST(JSONSchemaTest, TestPropertyDependency) {
300 dynamic::object("dependencies", dynamic::object("p1", {"p2"}));
301 ASSERT_TRUE(check(schema, dynamic::object));
302 ASSERT_TRUE(check(schema, dynamic::object("p1", 1)("p2", 1)));
303 ASSERT_FALSE(check(schema, dynamic::object("p1", 1)));
306 TEST(JSONSchemaTest, TestSchemaDependency) {
307 dynamic schema = dynamic::object(
309 dynamic::object("p1", dynamic::object("required", {"p2"})));
310 ASSERT_TRUE(check(schema, dynamic::object));
311 ASSERT_TRUE(check(schema, dynamic::object("p1", 1)("p2", 1)));
312 ASSERT_FALSE(check(schema, dynamic::object("p1", 1)));
315 TEST(JSONSchemaTest, TestEnum) {
316 dynamic schema = dynamic::object("enum", {"a", 1});
317 ASSERT_TRUE(check(schema, "a"));
318 ASSERT_TRUE(check(schema, 1));
319 ASSERT_FALSE(check(schema, "b"));
322 TEST(JSONSchemaTest, TestType) {
323 dynamic schema = dynamic::object("type", "object");
324 ASSERT_TRUE(check(schema, dynamic::object));
325 ASSERT_FALSE(check(schema, dynamic(5)));
328 TEST(JSONSchemaTest, TestTypeArray) {
329 dynamic schema = dynamic::object("type", {"array", "number"});
330 ASSERT_TRUE(check(schema, dynamic(5)));
331 ASSERT_TRUE(check(schema, dynamic(1.1)));
332 ASSERT_FALSE(check(schema, dynamic::object));
335 TEST(JSONSchemaTest, TestAllOf) {
336 dynamic schema = dynamic::object(
338 {dynamic::object("minimum", 1), dynamic::object("type", "integer")});
339 ASSERT_TRUE(check(schema, 2));
340 ASSERT_FALSE(check(schema, 0));
341 ASSERT_FALSE(check(schema, 1.1));
344 TEST(JSONSchemaTest, TestAnyOf) {
345 dynamic schema = dynamic::object(
347 {dynamic::object("minimum", 1), dynamic::object("type", "integer")});
348 ASSERT_TRUE(check(schema, 2)); // matches both
349 ASSERT_FALSE(check(schema, 0.1)); // matches neither
350 ASSERT_TRUE(check(schema, 1.1)); // matches first one
351 ASSERT_TRUE(check(schema, 0)); // matches second one
354 TEST(JSONSchemaTest, TestOneOf) {
355 dynamic schema = dynamic::object(
357 {dynamic::object("minimum", 1), dynamic::object("type", "integer")});
358 ASSERT_FALSE(check(schema, 2)); // matches both
359 ASSERT_FALSE(check(schema, 0.1)); // matches neither
360 ASSERT_TRUE(check(schema, 1.1)); // matches first one
361 ASSERT_TRUE(check(schema, 0)); // matches second one
364 TEST(JSONSchemaTest, TestNot) {
366 dynamic::object("not", dynamic::object("minimum", 5)("maximum", 10));
367 ASSERT_TRUE(check(schema, 4));
368 ASSERT_FALSE(check(schema, 7));
369 ASSERT_TRUE(check(schema, 11));
372 // The tests below use some sample schema from json-schema.org
374 TEST(JSONSchemaTest, TestMetaSchema) {
375 const char* example1 =
378 \"title\": \"Example Schema\", \
379 \"type\": \"object\", \
382 \"type\": \"string\" \
385 \"type\": \"string\" \
388 \"description\": \"Age in years\", \
389 \"type\": \"integer\", \
393 \"required\": [\"firstName\", \"lastName\"] \
396 auto val = makeSchemaValidator();
397 val->validate(parseJson(example1)); // doesn't throw
399 ASSERT_THROW(val->validate("123"), std::runtime_error);
402 TEST(JSONSchemaTest, TestProductSchema) {
403 const char* productSchema =
406 \"$schema\": \"http://json-schema.org/draft-04/schema#\", \
407 \"title\": \"Product\", \
408 \"description\": \"A product from Acme's catalog\", \
409 \"type\": \"object\", \
412 \"description\": \"The unique identifier for a product\", \
413 \"type\": \"integer\" \
416 \"description\": \"Name of the product\", \
417 \"type\": \"string\" \
420 \"type\": \"number\", \
422 \"exclusiveMinimum\": true \
425 \"type\": \"array\", \
427 \"type\": \"string\" \
430 \"uniqueItems\": true \
433 \"required\": [\"id\", \"name\", \"price\"] \
435 const char* product =
439 \"name\": \"A green door\", \
441 \"tags\": [\"home\", \"green\"] \
443 ASSERT_TRUE(check(parseJson(productSchema), parseJson(product)));