JSONSchemaTest.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  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. #include <folly/experimental/JSONSchema.h>
  18. #include <folly/json.h>
  19. #include <folly/portability/GTest.h>
  20. using folly::dynamic;
  21. using folly::parseJson;
  22. using namespace folly::jsonschema;
  23. using namespace std;
  24. bool check(const dynamic& schema, const dynamic& value, bool check = true) {
  25. if (check) {
  26. auto schemavalidator = makeSchemaValidator();
  27. auto ew = schemavalidator->try_validate(schema);
  28. if (ew) {
  29. return false;
  30. }
  31. }
  32. auto validator = makeValidator(schema);
  33. auto ew = validator->try_validate(value);
  34. if (validator->try_validate(value)) {
  35. return false;
  36. }
  37. return true;
  38. }
  39. TEST(JSONSchemaTest, TestMultipleOfInt) {
  40. dynamic schema = dynamic::object("multipleOf", 2);
  41. ASSERT_TRUE(check(schema, "invalid"));
  42. ASSERT_TRUE(check(schema, 30));
  43. ASSERT_TRUE(check(schema, 24.0));
  44. ASSERT_FALSE(check(schema, 5));
  45. ASSERT_FALSE(check(schema, 2.01));
  46. }
  47. TEST(JSONSchemaTest, TestMultipleOfDouble) {
  48. dynamic schema = dynamic::object("multipleOf", 1.5);
  49. ASSERT_TRUE(check(schema, "invalid"));
  50. ASSERT_TRUE(check(schema, 30));
  51. ASSERT_TRUE(check(schema, 24.0));
  52. ASSERT_FALSE(check(schema, 5));
  53. ASSERT_FALSE(check(schema, 2.01));
  54. schema = dynamic::object("multipleOf", 0.0001);
  55. ASSERT_TRUE(check(schema, 0.0075));
  56. }
  57. TEST(JSONSchemaTest, TestMinimumIntInclusive) {
  58. dynamic schema = dynamic::object("minimum", 2);
  59. ASSERT_TRUE(check(schema, "invalid"));
  60. ASSERT_TRUE(check(schema, 30));
  61. ASSERT_TRUE(check(schema, 24.0));
  62. ASSERT_TRUE(check(schema, 2));
  63. ASSERT_FALSE(check(schema, 1));
  64. ASSERT_FALSE(check(schema, 1.9999));
  65. }
  66. TEST(JSONSchemaTest, TestMinimumIntExclusive) {
  67. dynamic schema = dynamic::object("minimum", 2)("exclusiveMinimum", true);
  68. ASSERT_FALSE(check(schema, 2));
  69. }
  70. TEST(JSONSchemaTest, TestMaximumIntInclusive) {
  71. dynamic schema = dynamic::object("maximum", 12);
  72. ASSERT_TRUE(check(schema, "invalid"));
  73. ASSERT_TRUE(check(schema, 3));
  74. ASSERT_TRUE(check(schema, 3.1));
  75. ASSERT_TRUE(check(schema, 12));
  76. ASSERT_FALSE(check(schema, 13));
  77. ASSERT_FALSE(check(schema, 12.0001));
  78. }
  79. TEST(JSONSchemaTest, TestMaximumIntExclusive) {
  80. dynamic schema = dynamic::object("maximum", 2)("exclusiveMaximum", true);
  81. ASSERT_FALSE(check(schema, 2));
  82. }
  83. TEST(JSONSchemaTest, TestMinimumDoubleInclusive) {
  84. dynamic schema = dynamic::object("minimum", 1.75);
  85. ASSERT_TRUE(check(schema, "invalid"));
  86. ASSERT_TRUE(check(schema, 30));
  87. ASSERT_TRUE(check(schema, 24.0));
  88. ASSERT_TRUE(check(schema, 1.75));
  89. ASSERT_FALSE(check(schema, 1));
  90. ASSERT_FALSE(check(schema, 1.74));
  91. }
  92. TEST(JSONSchemaTest, TestMinimumDoubleExclusive) {
  93. dynamic schema = dynamic::object("minimum", 1.75)("exclusiveMinimum", true);
  94. ASSERT_FALSE(check(schema, 1.75));
  95. }
  96. TEST(JSONSchemaTest, TestMaximumDoubleInclusive) {
  97. dynamic schema = dynamic::object("maximum", 12.75);
  98. ASSERT_TRUE(check(schema, "invalid"));
  99. ASSERT_TRUE(check(schema, 3));
  100. ASSERT_TRUE(check(schema, 3.1));
  101. ASSERT_TRUE(check(schema, 12.75));
  102. ASSERT_FALSE(check(schema, 13));
  103. ASSERT_FALSE(check(schema, 12.76));
  104. }
  105. TEST(JSONSchemaTest, TestMaximumDoubleExclusive) {
  106. dynamic schema = dynamic::object("maximum", 12.75)("exclusiveMaximum", true);
  107. ASSERT_FALSE(check(schema, 12.75));
  108. }
  109. TEST(JSONSchemaTest, TestInvalidSchema) {
  110. dynamic schema = dynamic::object("multipleOf", "invalid");
  111. // don't check the schema since it's meant to be invalid
  112. ASSERT_TRUE(check(schema, 30, false));
  113. schema = dynamic::object("minimum", "invalid")("maximum", "invalid");
  114. ASSERT_TRUE(check(schema, 2, false));
  115. schema = dynamic::object("minLength", "invalid")("maxLength", "invalid");
  116. ASSERT_TRUE(check(schema, 2, false));
  117. ASSERT_TRUE(check(schema, "foo", false));
  118. }
  119. TEST(JSONSchemaTest, TestMinimumStringLength) {
  120. dynamic schema = dynamic::object("minLength", 3);
  121. ASSERT_TRUE(check(schema, "abcde"));
  122. ASSERT_TRUE(check(schema, "abc"));
  123. ASSERT_FALSE(check(schema, "a"));
  124. }
  125. TEST(JSONSchemaTest, TestMaximumStringLength) {
  126. dynamic schema = dynamic::object("maxLength", 3);
  127. ASSERT_FALSE(check(schema, "abcde"));
  128. ASSERT_TRUE(check(schema, "abc"));
  129. ASSERT_TRUE(check(schema, "a"));
  130. }
  131. TEST(JSONSchemaTest, TestStringPattern) {
  132. dynamic schema = dynamic::object("pattern", "[1-9]+");
  133. ASSERT_TRUE(check(schema, "123"));
  134. ASSERT_FALSE(check(schema, "abc"));
  135. }
  136. TEST(JSONSchemaTest, TestMinimumArrayItems) {
  137. dynamic schema = dynamic::object("minItems", 3);
  138. ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3, 4, 5)));
  139. ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3)));
  140. ASSERT_FALSE(check(schema, dynamic::array(1)));
  141. }
  142. TEST(JSONSchemaTest, TestMaximumArrayItems) {
  143. dynamic schema = dynamic::object("maxItems", 3);
  144. ASSERT_FALSE(check(schema, dynamic::array(1, 2, 3, 4, 5)));
  145. ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3)));
  146. ASSERT_TRUE(check(schema, dynamic::array(1)));
  147. ASSERT_TRUE(check(schema, "foobar"));
  148. }
  149. TEST(JSONSchemaTest, TestArrayUniqueItems) {
  150. dynamic schema = dynamic::object("uniqueItems", true);
  151. ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3)));
  152. ASSERT_FALSE(check(schema, dynamic::array(1, 2, 3, 1)));
  153. ASSERT_FALSE(check(schema, dynamic::array("cat", "dog", 1, 2, "cat")));
  154. ASSERT_TRUE(check(
  155. schema,
  156. dynamic::array(
  157. dynamic::object("foo", "bar"), dynamic::object("foo", "baz"))));
  158. schema = dynamic::object("uniqueItems", false);
  159. ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3, 1)));
  160. }
  161. TEST(JSONSchemaTest, TestArrayItems) {
  162. dynamic schema = dynamic::object("items", dynamic::object("minimum", 2));
  163. ASSERT_TRUE(check(schema, dynamic::array(2, 3, 4)));
  164. ASSERT_FALSE(check(schema, dynamic::array(3, 4, 1)));
  165. }
  166. TEST(JSONSchemaTest, TestArrayAdditionalItems) {
  167. dynamic schema = dynamic::object(
  168. "items",
  169. dynamic::array(
  170. dynamic::object("minimum", 2), dynamic::object("minimum", 1)))(
  171. "additionalItems", dynamic::object("minimum", 3));
  172. ASSERT_TRUE(check(schema, dynamic::array(2, 1, 3, 3, 3, 3, 4)));
  173. ASSERT_FALSE(check(schema, dynamic::array(2, 1, 3, 3, 3, 3, 1)));
  174. }
  175. TEST(JSONSchemaTest, TestArrayNoAdditionalItems) {
  176. dynamic schema =
  177. dynamic::object("items", dynamic::array(dynamic::object("minimum", 2)))(
  178. "additionalItems", false);
  179. ASSERT_FALSE(check(schema, dynamic::array(3, 3, 3)));
  180. }
  181. TEST(JSONSchemaTest, TestArrayItemsNotPresent) {
  182. dynamic schema = dynamic::object("additionalItems", false);
  183. ASSERT_TRUE(check(schema, dynamic::array(3, 3, 3)));
  184. }
  185. TEST(JSONSchemaTest, TestRef) {
  186. dynamic schema = dynamic::object(
  187. "definitions",
  188. dynamic::object(
  189. "positiveInteger", dynamic::object("minimum", 1)("type", "integer")))(
  190. "items", dynamic::object("$ref", "#/definitions/positiveInteger"));
  191. ASSERT_TRUE(check(schema, dynamic::array(1, 2, 3, 4)));
  192. ASSERT_FALSE(check(schema, dynamic::array(4, -5)));
  193. }
  194. TEST(JSONSchemaTest, TestRecursiveRef) {
  195. dynamic schema = dynamic::object(
  196. "properties", dynamic::object("more", dynamic::object("$ref", "#")));
  197. dynamic d = dynamic::object;
  198. ASSERT_TRUE(check(schema, d));
  199. d["more"] = dynamic::object;
  200. ASSERT_TRUE(check(schema, d));
  201. d["more"]["more"] = dynamic::object;
  202. ASSERT_TRUE(check(schema, d));
  203. d["more"]["more"]["more"] = dynamic::object;
  204. ASSERT_TRUE(check(schema, d));
  205. }
  206. TEST(JSONSchemaTest, TestDoubleRecursiveRef) {
  207. dynamic schema = dynamic::object(
  208. "properties",
  209. dynamic::object("more", dynamic::object("$ref", "#"))(
  210. "less", dynamic::object("$ref", "#")));
  211. dynamic d = dynamic::object;
  212. ASSERT_TRUE(check(schema, d));
  213. d["more"] = dynamic::object;
  214. d["less"] = dynamic::object;
  215. ASSERT_TRUE(check(schema, d));
  216. d["more"]["less"] = dynamic::object;
  217. d["less"]["mode"] = dynamic::object;
  218. ASSERT_TRUE(check(schema, d));
  219. }
  220. TEST(JSONSchemaTest, TestInfinitelyRecursiveRef) {
  221. dynamic schema = dynamic::object("not", dynamic::object("$ref", "#"));
  222. auto validator = makeValidator(schema);
  223. ASSERT_THROW(validator->validate(dynamic::array(1, 2)), std::runtime_error);
  224. }
  225. TEST(JSONSchemaTest, TestRequired) {
  226. dynamic schema = dynamic::object("required", dynamic::array("foo", "bar"));
  227. ASSERT_FALSE(check(schema, dynamic::object("foo", 123)));
  228. ASSERT_FALSE(check(schema, dynamic::object("bar", 123)));
  229. ASSERT_TRUE(check(schema, dynamic::object("bar", 123)("foo", 456)));
  230. }
  231. TEST(JSONSchemaTest, TestMinMaxProperties) {
  232. dynamic schema = dynamic::object("minProperties", 1)("maxProperties", 3);
  233. dynamic d = dynamic::object;
  234. ASSERT_FALSE(check(schema, d));
  235. d["a"] = 1;
  236. ASSERT_TRUE(check(schema, d));
  237. d["b"] = 2;
  238. ASSERT_TRUE(check(schema, d));
  239. d["c"] = 3;
  240. ASSERT_TRUE(check(schema, d));
  241. d["d"] = 4;
  242. ASSERT_FALSE(check(schema, d));
  243. }
  244. TEST(JSONSchemaTest, TestProperties) {
  245. dynamic schema = dynamic::object(
  246. "properties", dynamic::object("p1", dynamic::object("minimum", 1)))(
  247. "patternProperties", dynamic::object("[0-9]+", dynamic::object))(
  248. "additionalProperties", dynamic::object("maximum", 5));
  249. ASSERT_TRUE(check(schema, dynamic::object("p1", 1)));
  250. ASSERT_FALSE(check(schema, dynamic::object("p1", 0)));
  251. ASSERT_TRUE(check(schema, dynamic::object("123", "anything")));
  252. ASSERT_TRUE(check(schema, dynamic::object("123", 500)));
  253. ASSERT_TRUE(check(schema, dynamic::object("other_property", 4)));
  254. ASSERT_FALSE(check(schema, dynamic::object("other_property", 6)));
  255. }
  256. TEST(JSONSchemaTest, TestPropertyAndPattern) {
  257. dynamic schema = dynamic::object(
  258. "properties", dynamic::object("p1", dynamic::object("minimum", 1)))(
  259. "patternProperties",
  260. dynamic::object("p.", dynamic::object("maximum", 5)));
  261. ASSERT_TRUE(check(schema, dynamic::object("p1", 3)));
  262. ASSERT_FALSE(check(schema, dynamic::object("p1", 0)));
  263. ASSERT_FALSE(check(schema, dynamic::object("p1", 6)));
  264. }
  265. TEST(JSONSchemaTest, TestPropertyDependency) {
  266. dynamic schema = dynamic::object(
  267. "dependencies", dynamic::object("p1", dynamic::array("p2")));
  268. ASSERT_TRUE(check(schema, dynamic::object));
  269. ASSERT_TRUE(check(schema, dynamic::object("p1", 1)("p2", 1)));
  270. ASSERT_FALSE(check(schema, dynamic::object("p1", 1)));
  271. }
  272. TEST(JSONSchemaTest, TestSchemaDependency) {
  273. dynamic schema = dynamic::object(
  274. "dependencies",
  275. dynamic::object("p1", dynamic::object("required", dynamic::array("p2"))));
  276. ASSERT_TRUE(check(schema, dynamic::object));
  277. ASSERT_TRUE(check(schema, dynamic::object("p1", 1)("p2", 1)));
  278. ASSERT_FALSE(check(schema, dynamic::object("p1", 1)));
  279. }
  280. TEST(JSONSchemaTest, TestEnum) {
  281. dynamic schema = dynamic::object("enum", dynamic::array("a", 1));
  282. ASSERT_TRUE(check(schema, "a"));
  283. ASSERT_TRUE(check(schema, 1));
  284. ASSERT_FALSE(check(schema, "b"));
  285. }
  286. TEST(JSONSchemaTest, TestType) {
  287. dynamic schema = dynamic::object("type", "object");
  288. ASSERT_TRUE(check(schema, dynamic::object));
  289. ASSERT_FALSE(check(schema, dynamic(5)));
  290. }
  291. TEST(JSONSchemaTest, TestTypeArray) {
  292. dynamic schema = dynamic::object("type", dynamic::array("array", "number"));
  293. ASSERT_TRUE(check(schema, dynamic(5)));
  294. ASSERT_TRUE(check(schema, dynamic(1.1)));
  295. ASSERT_FALSE(check(schema, dynamic::object));
  296. }
  297. TEST(JSONSchemaTest, TestAllOf) {
  298. dynamic schema = dynamic::object(
  299. "allOf",
  300. dynamic::array(
  301. dynamic::object("minimum", 1), dynamic::object("type", "integer")));
  302. ASSERT_TRUE(check(schema, 2));
  303. ASSERT_FALSE(check(schema, 0));
  304. ASSERT_FALSE(check(schema, 1.1));
  305. }
  306. TEST(JSONSchemaTest, TestAnyOf) {
  307. dynamic schema = dynamic::object(
  308. "anyOf",
  309. dynamic::array(
  310. dynamic::object("minimum", 1), dynamic::object("type", "integer")));
  311. ASSERT_TRUE(check(schema, 2)); // matches both
  312. ASSERT_FALSE(check(schema, 0.1)); // matches neither
  313. ASSERT_TRUE(check(schema, 1.1)); // matches first one
  314. ASSERT_TRUE(check(schema, 0)); // matches second one
  315. }
  316. TEST(JSONSchemaTest, TestOneOf) {
  317. dynamic schema = dynamic::object(
  318. "oneOf",
  319. dynamic::array(
  320. dynamic::object("minimum", 1), dynamic::object("type", "integer")));
  321. ASSERT_FALSE(check(schema, 2)); // matches both
  322. ASSERT_FALSE(check(schema, 0.1)); // matches neither
  323. ASSERT_TRUE(check(schema, 1.1)); // matches first one
  324. ASSERT_TRUE(check(schema, 0)); // matches second one
  325. }
  326. TEST(JSONSchemaTest, TestNot) {
  327. dynamic schema =
  328. dynamic::object("not", dynamic::object("minimum", 5)("maximum", 10));
  329. ASSERT_TRUE(check(schema, 4));
  330. ASSERT_FALSE(check(schema, 7));
  331. ASSERT_TRUE(check(schema, 11));
  332. }
  333. // The tests below use some sample schema from json-schema.org
  334. TEST(JSONSchemaTest, TestMetaSchema) {
  335. const char* example1 =
  336. "\
  337. { \
  338. \"title\": \"Example Schema\", \
  339. \"type\": \"object\", \
  340. \"properties\": { \
  341. \"firstName\": { \
  342. \"type\": \"string\" \
  343. }, \
  344. \"lastName\": { \
  345. \"type\": \"string\" \
  346. }, \
  347. \"age\": { \
  348. \"description\": \"Age in years\", \
  349. \"type\": \"integer\", \
  350. \"minimum\": 0 \
  351. } \
  352. }, \
  353. \"required\": [\"firstName\", \"lastName\"] \
  354. }";
  355. auto val = makeSchemaValidator();
  356. val->validate(parseJson(example1)); // doesn't throw
  357. ASSERT_THROW(val->validate("123"), std::runtime_error);
  358. }
  359. TEST(JSONSchemaTest, TestProductSchema) {
  360. const char* productSchema =
  361. "\
  362. { \
  363. \"$schema\": \"http://json-schema.org/draft-04/schema#\", \
  364. \"title\": \"Product\", \
  365. \"description\": \"A product from Acme's catalog\", \
  366. \"type\": \"object\", \
  367. \"properties\": { \
  368. \"id\": { \
  369. \"description\": \"The unique identifier for a product\", \
  370. \"type\": \"integer\" \
  371. }, \
  372. \"name\": { \
  373. \"description\": \"Name of the product\", \
  374. \"type\": \"string\" \
  375. }, \
  376. \"price\": { \
  377. \"type\": \"number\", \
  378. \"minimum\": 0, \
  379. \"exclusiveMinimum\": true \
  380. }, \
  381. \"tags\": { \
  382. \"type\": \"array\", \
  383. \"items\": { \
  384. \"type\": \"string\" \
  385. }, \
  386. \"minItems\": 1, \
  387. \"uniqueItems\": true \
  388. } \
  389. }, \
  390. \"required\": [\"id\", \"name\", \"price\"] \
  391. }";
  392. const char* product =
  393. "\
  394. { \
  395. \"id\": 1, \
  396. \"name\": \"A green door\", \
  397. \"price\": 12.50, \
  398. \"tags\": [\"home\", \"green\"] \
  399. }";
  400. ASSERT_TRUE(check(parseJson(productSchema), parseJson(product)));
  401. }