test-json-schema-to-grammar.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. #ifdef NDEBUG
  2. #undef NDEBUG
  3. #endif
  4. #include <fstream>
  5. #include <sstream>
  6. #include <regex>
  7. #include "json-schema-to-grammar.h"
  8. #include "grammar-parser.h"
  9. static std::string trim(const std::string & source) {
  10. std::string s(source);
  11. s.erase(0,s.find_first_not_of(" \n\r\t"));
  12. s.erase(s.find_last_not_of(" \n\r\t")+1);
  13. return std::regex_replace(s, std::regex("(^|\n)[ \t]+"), "$1");
  14. }
  15. enum TestCaseStatus {
  16. SUCCESS,
  17. FAILURE
  18. };
  19. struct TestCase {
  20. TestCaseStatus expected_status;
  21. std::string name;
  22. std::string schema;
  23. std::string expected_grammar;
  24. void _print_failure_header() const {
  25. fprintf(stderr, "#\n# Test '%s' failed.\n#\n%s\n", name.c_str(), schema.c_str());
  26. }
  27. void verify(const std::string & actual_grammar) const {
  28. if (trim(actual_grammar) != trim(expected_grammar)) {
  29. _print_failure_header();
  30. fprintf(stderr, "# EXPECTED:\n%s\n# ACTUAL:\n%s\n", expected_grammar.c_str(), actual_grammar.c_str());
  31. assert(false);
  32. }
  33. }
  34. void verify_expectation_parseable() const {
  35. try {
  36. auto state = grammar_parser::parse(expected_grammar.c_str());
  37. if (state.symbol_ids.find("root") == state.symbol_ids.end()) {
  38. throw std::runtime_error("Grammar failed to parse:\n" + expected_grammar);
  39. }
  40. } catch (const std::runtime_error & ex) {
  41. _print_failure_header();
  42. fprintf(stderr, "# GRAMMAR ERROR: %s\n", ex.what());
  43. assert(false);
  44. }
  45. }
  46. void verify_status(TestCaseStatus status) const {
  47. if (status != expected_status) {
  48. _print_failure_header();
  49. fprintf(stderr, "# EXPECTED STATUS: %s\n", expected_status == SUCCESS ? "SUCCESS" : "FAILURE");
  50. fprintf(stderr, "# ACTUAL STATUS: %s\n", status == SUCCESS ? "SUCCESS" : "FAILURE");
  51. assert(false);
  52. }
  53. }
  54. };
  55. static void write(const std::string & file, const std::string & content) {
  56. std::ofstream f;
  57. f.open(file.c_str());
  58. f << content.c_str();
  59. f.close();
  60. }
  61. static std::string read(const std::string & file) {
  62. std::ostringstream actuals;
  63. actuals << std::ifstream(file.c_str()).rdbuf();
  64. return actuals.str();
  65. }
  66. static void test_all(const std::string & lang, std::function<void(const TestCase &)> runner) {
  67. fprintf(stderr, "#\n# Testing JSON schema conversion (%s)\n#\n", lang.c_str());
  68. auto test = [&](const TestCase & tc) {
  69. fprintf(stderr, "- %s%s\n", tc.name.c_str(), tc.expected_status == FAILURE ? " (failure expected)" : "");
  70. runner(tc);
  71. };
  72. test({
  73. FAILURE,
  74. "unknown type",
  75. R"""({
  76. "type": "kaboom"
  77. })""",
  78. ""
  79. });
  80. test({
  81. FAILURE,
  82. "invalid type type",
  83. R"""({
  84. "type": 123
  85. })""",
  86. ""
  87. });
  88. test({
  89. SUCCESS,
  90. "empty schema (object)",
  91. "{}",
  92. R"""(
  93. array ::= "[" space ( value ("," space value)* )? "]" space
  94. boolean ::= ("true" | "false") space
  95. null ::= "null" space
  96. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  97. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  98. root ::= object
  99. space ::= " "?
  100. string ::= "\"" (
  101. [^"\\] |
  102. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  103. )* "\"" space
  104. value ::= object | array | string | number | boolean
  105. )"""
  106. });
  107. test({
  108. SUCCESS,
  109. "exotic formats",
  110. R"""({
  111. "items": [
  112. { "format": "date" },
  113. { "format": "uuid" },
  114. { "format": "time" },
  115. { "format": "date-time" }
  116. ]
  117. })""",
  118. R"""(
  119. date ::= [0-9] [0-9] [0-9] [0-9] "-" ( "0" [1-9] | "1" [0-2] ) "-" ( "0" [1-9] | [1-2] [0-9] | "3" [0-1] )
  120. date-string ::= "\"" date "\"" space
  121. date-time ::= date "T" time
  122. date-time-string ::= "\"" date-time "\"" space
  123. root ::= "[" space date-string "," space uuid "," space time-string "," space date-time-string "]" space
  124. space ::= " "?
  125. time ::= ([01] [0-9] | "2" [0-3]) ":" [0-5] [0-9] ":" [0-5] [0-9] ( "." [0-9] [0-9] [0-9] )? ( "Z" | ( "+" | "-" ) ( [01] [0-9] | "2" [0-3] ) ":" [0-5] [0-9] )
  126. time-string ::= "\"" time "\"" space
  127. uuid ::= "\"" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "-" [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F] "\"" space
  128. )"""
  129. });
  130. test({
  131. SUCCESS,
  132. "string",
  133. R"""({
  134. "type": "string"
  135. })""",
  136. R"""(
  137. root ::= "\"" (
  138. [^"\\] |
  139. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  140. )* "\"" space
  141. space ::= " "?
  142. )"""
  143. });
  144. test({
  145. SUCCESS,
  146. "boolean",
  147. R"""({
  148. "type": "boolean"
  149. })""",
  150. R"""(
  151. root ::= ("true" | "false") space
  152. space ::= " "?
  153. )"""
  154. });
  155. test({
  156. SUCCESS,
  157. "integer",
  158. R"""({
  159. "type": "integer"
  160. })""",
  161. R"""(
  162. root ::= ("-"? ([0-9] | [1-9] [0-9]*)) space
  163. space ::= " "?
  164. )"""
  165. });
  166. test({
  167. SUCCESS,
  168. "string const",
  169. R"""({
  170. "const": "foo"
  171. })""",
  172. R"""(
  173. root ::= "\"foo\""
  174. space ::= " "?
  175. )"""
  176. });
  177. test({
  178. FAILURE,
  179. "non-string const",
  180. R"""({
  181. "const": 123
  182. })""",
  183. ""
  184. });
  185. test({
  186. FAILURE,
  187. "non-string enum",
  188. R"""({
  189. "enum": [123]
  190. })""",
  191. ""
  192. });
  193. test({
  194. SUCCESS,
  195. "tuple1",
  196. R"""({
  197. "prefixItems": [{ "type": "string" }]
  198. })""",
  199. R"""(
  200. root ::= "[" space string "]" space
  201. space ::= " "?
  202. string ::= "\"" (
  203. [^"\\] |
  204. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  205. )* "\"" space
  206. )"""
  207. });
  208. test({
  209. SUCCESS,
  210. "tuple2",
  211. R"""({
  212. "prefixItems": [{ "type": "string" }, { "type": "number" }]
  213. })""",
  214. R"""(
  215. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  216. root ::= "[" space string "," space number "]" space
  217. space ::= " "?
  218. string ::= "\"" (
  219. [^"\\] |
  220. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  221. )* "\"" space
  222. )"""
  223. });
  224. test({
  225. SUCCESS,
  226. "number",
  227. R"""({
  228. "type": "number"
  229. })""",
  230. R"""(
  231. root ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  232. space ::= " "?
  233. )"""
  234. });
  235. test({
  236. SUCCESS,
  237. "minItems",
  238. R"""({
  239. "items": {
  240. "type": "boolean"
  241. },
  242. "minItems": 2
  243. })""",
  244. R"""(
  245. boolean ::= ("true" | "false") space
  246. root ::= "[" space boolean ( "," space boolean )( "," space boolean )* "]" space
  247. space ::= " "?
  248. )"""
  249. });
  250. test({
  251. SUCCESS,
  252. "maxItems 1",
  253. R"""({
  254. "items": {
  255. "type": "boolean"
  256. },
  257. "maxItems": 1
  258. })""",
  259. R"""(
  260. boolean ::= ("true" | "false") space
  261. root ::= "[" space ( boolean )? "]" space
  262. space ::= " "?
  263. )"""
  264. });
  265. test({
  266. SUCCESS,
  267. "maxItems 2",
  268. R"""({
  269. "items": {
  270. "type": "boolean"
  271. },
  272. "maxItems": 2
  273. })""",
  274. R"""(
  275. boolean ::= ("true" | "false") space
  276. root ::= "[" space ( boolean ( "," space boolean )? )? "]" space
  277. space ::= " "?
  278. )"""
  279. });
  280. test({
  281. SUCCESS,
  282. "min + maxItems",
  283. R"""({
  284. "items": {
  285. "type": ["number", "integer"]
  286. },
  287. "minItems": 3,
  288. "maxItems": 5
  289. })""",
  290. R"""(
  291. integer ::= ("-"? ([0-9] | [1-9] [0-9]*)) space
  292. item ::= number | integer
  293. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  294. root ::= "[" space item ( "," space item )( "," space item )( "," space item )?( "," space item )? "]" space
  295. space ::= " "?
  296. )"""
  297. });
  298. test({
  299. SUCCESS,
  300. "simple regexp",
  301. R"""({
  302. "type": "string",
  303. "pattern": "^abc?d*efg+(hij)?kl$"
  304. })""",
  305. R"""(
  306. root ::= "\"" "ab" "c"? "d"* "ef" "g"+ ("hij")? "kl" "\"" space
  307. space ::= " "?
  308. )"""
  309. });
  310. test({
  311. SUCCESS,
  312. "regexp escapes",
  313. R"""({
  314. "type": "string",
  315. "pattern": "^\\[\\]\\{\\}\\(\\)\\|\\+\\*\\?$"
  316. })""",
  317. R"""(
  318. root ::= "\"" "[]{}()|+*?" "\"" space
  319. space ::= " "?
  320. )"""
  321. });
  322. test({
  323. SUCCESS,
  324. "regexp quote",
  325. R"""({
  326. "type": "string",
  327. "pattern": "^\"$"
  328. })""",
  329. R"""(
  330. root ::= "\"" "\"" "\"" space
  331. space ::= " "?
  332. )"""
  333. });
  334. test({
  335. SUCCESS,
  336. "regexp",
  337. R"""({
  338. "type": "string",
  339. "pattern": "^(\\([0-9]{1,3}\\))?[0-9]{3}-[0-9]{4} and...$"
  340. })""",
  341. R"""(
  342. dot ::= [\U00000000-\x09\x0B\x0C\x0E-\U0010FFFF]
  343. root ::= "\"" ("(" root-1 root-1? root-1? ")")? root-1 root-1 root-1 "-" root-1 root-1 root-1 root-1 " and" dot dot dot "\"" space
  344. root-1 ::= [0-9]
  345. space ::= " "?
  346. )"""
  347. });
  348. test({
  349. SUCCESS,
  350. "required props",
  351. R"""({
  352. "type": "object",
  353. "properties": {
  354. "a": {
  355. "type": "string"
  356. },
  357. "b": {
  358. "type": "string"
  359. }
  360. },
  361. "required": [
  362. "a",
  363. "b"
  364. ],
  365. "additionalProperties": false,
  366. "definitions": {}
  367. })""",
  368. R"""(
  369. a-kv ::= "\"a\"" space ":" space string
  370. b-kv ::= "\"b\"" space ":" space string
  371. root ::= "{" space a-kv "," space b-kv "}" space
  372. space ::= " "?
  373. string ::= "\"" (
  374. [^"\\] |
  375. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  376. )* "\"" space
  377. )"""
  378. });
  379. test({
  380. SUCCESS,
  381. "1 optional prop",
  382. R"""({
  383. "properties": {
  384. "a": {
  385. "type": "string"
  386. }
  387. },
  388. "additionalProperties": false
  389. })""",
  390. R"""(
  391. a-kv ::= "\"a\"" space ":" space string
  392. root ::= "{" space (a-kv )? "}" space
  393. space ::= " "?
  394. string ::= "\"" (
  395. [^"\\] |
  396. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  397. )* "\"" space
  398. )"""
  399. });
  400. test({
  401. SUCCESS,
  402. "N optional props",
  403. R"""({
  404. "properties": {
  405. "a": {"type": "string"},
  406. "b": {"type": "string"},
  407. "c": {"type": "string"}
  408. },
  409. "additionalProperties": false
  410. })""",
  411. R"""(
  412. a-kv ::= "\"a\"" space ":" space string
  413. a-rest ::= ( "," space b-kv )? b-rest
  414. b-kv ::= "\"b\"" space ":" space string
  415. b-rest ::= ( "," space c-kv )?
  416. c-kv ::= "\"c\"" space ":" space string
  417. root ::= "{" space (a-kv a-rest | b-kv b-rest | c-kv )? "}" space
  418. space ::= " "?
  419. string ::= "\"" (
  420. [^"\\] |
  421. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  422. )* "\"" space
  423. )"""
  424. });
  425. test({
  426. SUCCESS,
  427. "required + optional props",
  428. R"""({
  429. "properties": {
  430. "a": {"type": "string"},
  431. "b": {"type": "string"},
  432. "c": {"type": "string"},
  433. "d": {"type": "string"}
  434. },
  435. "required": ["a", "b"],
  436. "additionalProperties": false
  437. })""",
  438. R"""(
  439. a-kv ::= "\"a\"" space ":" space string
  440. b-kv ::= "\"b\"" space ":" space string
  441. c-kv ::= "\"c\"" space ":" space string
  442. c-rest ::= ( "," space d-kv )?
  443. d-kv ::= "\"d\"" space ":" space string
  444. root ::= "{" space a-kv "," space b-kv ( "," space ( c-kv c-rest | d-kv ) )? "}" space
  445. space ::= " "?
  446. string ::= "\"" (
  447. [^"\\] |
  448. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  449. )* "\"" space
  450. )"""
  451. });
  452. test({
  453. SUCCESS,
  454. "additional props",
  455. R"""({
  456. "type": "object",
  457. "additionalProperties": {"type": "array", "items": {"type": "number"}}
  458. })""",
  459. R"""(
  460. additional-kv ::= string ":" space additional-value
  461. additional-kvs ::= additional-kv ( "," space additional-kv )*
  462. additional-value ::= "[" space ( number ( "," space number )* )? "]" space
  463. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  464. root ::= "{" space (additional-kvs )? "}" space
  465. space ::= " "?
  466. string ::= "\"" (
  467. [^"\\] |
  468. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  469. )* "\"" space
  470. )"""
  471. });
  472. test({
  473. SUCCESS,
  474. "additional props (true)",
  475. R"""({
  476. "type": "object",
  477. "additionalProperties": true
  478. })""",
  479. R"""(
  480. array ::= "[" space ( value ("," space value)* )? "]" space
  481. boolean ::= ("true" | "false") space
  482. null ::= "null" space
  483. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  484. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  485. root ::= object
  486. space ::= " "?
  487. string ::= "\"" (
  488. [^"\\] |
  489. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  490. )* "\"" space
  491. value ::= object | array | string | number | boolean
  492. )"""
  493. });
  494. test({
  495. SUCCESS,
  496. "additional props (implicit)",
  497. R"""({
  498. "type": "object"
  499. })""",
  500. R"""(
  501. array ::= "[" space ( value ("," space value)* )? "]" space
  502. boolean ::= ("true" | "false") space
  503. null ::= "null" space
  504. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  505. object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space
  506. root ::= object
  507. space ::= " "?
  508. string ::= "\"" (
  509. [^"\\] |
  510. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  511. )* "\"" space
  512. value ::= object | array | string | number | boolean
  513. )"""
  514. });
  515. test({
  516. SUCCESS,
  517. "empty w/o additional props",
  518. R"""({
  519. "type": "object",
  520. "additionalProperties": false
  521. })""",
  522. R"""(
  523. root ::= "{" space "}" space
  524. space ::= " "?
  525. )"""
  526. });
  527. test({
  528. SUCCESS,
  529. "required + additional props",
  530. R"""({
  531. "type": "object",
  532. "properties": {
  533. "a": {"type": "number"}
  534. },
  535. "required": ["a"],
  536. "additionalProperties": {"type": "string"}
  537. })""",
  538. R"""(
  539. a-kv ::= "\"a\"" space ":" space number
  540. additional-kv ::= string ":" space string
  541. additional-kvs ::= additional-kv ( "," space additional-kv )*
  542. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  543. root ::= "{" space a-kv ( "," space ( additional-kvs ) )? "}" space
  544. space ::= " "?
  545. string ::= "\"" (
  546. [^"\\] |
  547. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  548. )* "\"" space
  549. )"""
  550. });
  551. test({
  552. SUCCESS,
  553. "optional + additional props",
  554. R"""({
  555. "type": "object",
  556. "properties": {
  557. "a": {"type": "number"}
  558. },
  559. "additionalProperties": {"type": "number"}
  560. })""",
  561. R"""(
  562. a-kv ::= "\"a\"" space ":" space number
  563. a-rest ::= additional-kvs
  564. additional-kv ::= string ":" space number
  565. additional-kvs ::= additional-kv ( "," space additional-kv )*
  566. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  567. root ::= "{" space (a-kv a-rest | additional-kvs )? "}" space
  568. space ::= " "?
  569. string ::= "\"" (
  570. [^"\\] |
  571. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  572. )* "\"" space
  573. )"""
  574. });
  575. test({
  576. SUCCESS,
  577. "required + optional + additional props",
  578. R"""({
  579. "type": "object",
  580. "properties": {
  581. "a": {"type": "number"},
  582. "b": {"type": "number"}
  583. },
  584. "required": ["a"],
  585. "additionalProperties": {"type": "number"}
  586. })""",
  587. R"""(
  588. a-kv ::= "\"a\"" space ":" space number
  589. additional-kv ::= string ":" space number
  590. additional-kvs ::= additional-kv ( "," space additional-kv )*
  591. b-kv ::= "\"b\"" space ":" space number
  592. b-rest ::= additional-kvs
  593. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  594. root ::= "{" space a-kv ( "," space ( b-kv b-rest | additional-kvs ) )? "}" space
  595. space ::= " "?
  596. string ::= "\"" (
  597. [^"\\] |
  598. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  599. )* "\"" space
  600. )"""
  601. });
  602. test({
  603. SUCCESS,
  604. "top-level $ref",
  605. R"""({
  606. "$ref": "#/definitions/MyType",
  607. "definitions": {
  608. "MyType": {
  609. "type": "object",
  610. "properties": {
  611. "a": {
  612. "type": "string"
  613. }
  614. },
  615. "required": [
  616. "a"
  617. ],
  618. "additionalProperties": false
  619. }
  620. }
  621. })""",
  622. R"""(
  623. MyType ::= "{" space MyType-a-kv "}" space
  624. MyType-a-kv ::= "\"a\"" space ":" space string
  625. root ::= MyType
  626. space ::= " "?
  627. string ::= "\"" (
  628. [^"\\] |
  629. "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
  630. )* "\"" space
  631. )"""
  632. });
  633. test({
  634. SUCCESS,
  635. "anyOf",
  636. R"""({
  637. "anyOf": [
  638. {"$ref": "#/definitions/foo"},
  639. {"$ref": "#/definitions/bar"}
  640. ],
  641. "definitions": {
  642. "foo": {
  643. "properties": {"a": {"type": "number"}}
  644. },
  645. "bar": {
  646. "properties": {"b": {"type": "number"}}
  647. }
  648. },
  649. "type": "object"
  650. })""",
  651. R"""(
  652. alternative-0 ::= foo
  653. alternative-1 ::= bar
  654. bar ::= "{" space (bar-b-kv )? "}" space
  655. bar-b-kv ::= "\"b\"" space ":" space number
  656. foo ::= "{" space (foo-a-kv )? "}" space
  657. foo-a-kv ::= "\"a\"" space ":" space number
  658. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  659. root ::= alternative-0 | alternative-1
  660. space ::= " "?
  661. )"""
  662. });
  663. test({
  664. SUCCESS,
  665. "mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)",
  666. R"""({
  667. "allOf": [
  668. {"$ref": "#/definitions/foo"},
  669. {"$ref": "#/definitions/bar"},
  670. {
  671. "anyOf": [
  672. {"$ref": "#/definitions/baz"},
  673. {"$ref": "#/definitions/bam"}
  674. ]
  675. }
  676. ],
  677. "definitions": {
  678. "foo": {
  679. "properties": {"a": {"type": "number"}}
  680. },
  681. "bar": {
  682. "properties": {"b": {"type": "number"}}
  683. },
  684. "bam": {
  685. "properties": {"c": {"type": "number"}}
  686. },
  687. "baz": {
  688. "properties": {"d": {"type": "number"}}
  689. }
  690. },
  691. "type": "object"
  692. })""",
  693. R"""(
  694. a-kv ::= "\"a\"" space ":" space number
  695. b-kv ::= "\"b\"" space ":" space number
  696. c-kv ::= "\"c\"" space ":" space number
  697. d-kv ::= "\"d\"" space ":" space number
  698. d-rest ::= ( "," space c-kv )?
  699. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  700. root ::= "{" space a-kv "," space b-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space
  701. space ::= " "?
  702. )"""
  703. });
  704. test({
  705. SUCCESS,
  706. "conflicting names",
  707. R"""({
  708. "type": "object",
  709. "properties": {
  710. "number": {
  711. "type": "object",
  712. "properties": {
  713. "number": {
  714. "type": "object",
  715. "properties": {
  716. "root": {
  717. "type": "number"
  718. }
  719. },
  720. "required": [
  721. "root"
  722. ],
  723. "additionalProperties": false
  724. }
  725. },
  726. "required": [
  727. "number"
  728. ],
  729. "additionalProperties": false
  730. }
  731. },
  732. "required": [
  733. "number"
  734. ],
  735. "additionalProperties": false,
  736. "definitions": {}
  737. })""",
  738. R"""(
  739. number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space
  740. number- ::= "{" space number-number-kv "}" space
  741. number-kv ::= "\"number\"" space ":" space number-
  742. number-number ::= "{" space number-number-root-kv "}" space
  743. number-number-kv ::= "\"number\"" space ":" space number-number
  744. number-number-root-kv ::= "\"root\"" space ":" space number
  745. root ::= "{" space number-kv "}" space
  746. space ::= " "?
  747. )"""
  748. });
  749. }
  750. int main() {
  751. test_all("C++", [](const TestCase & tc) {
  752. try {
  753. tc.verify(json_schema_to_grammar(nlohmann::json::parse(tc.schema)));
  754. tc.verify_status(SUCCESS);
  755. } catch (const std::runtime_error & ex) {
  756. fprintf(stderr, "Error: %s\n", ex.what());
  757. tc.verify_status(FAILURE);
  758. }
  759. });
  760. test_all("Python", [](const TestCase & tc) {
  761. write("test-json-schema-input.tmp", tc.schema);
  762. tc.verify_status(std::system(
  763. "python ./examples/json-schema-to-grammar.py test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  764. tc.verify(read("test-grammar-output.tmp"));
  765. });
  766. test_all("JavaScript", [](const TestCase & tc) {
  767. write("test-json-schema-input.tmp", tc.schema);
  768. tc.verify_status(std::system(
  769. "node ./tests/run-json-schema-to-grammar.mjs test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE);
  770. tc.verify(read("test-grammar-output.tmp"));
  771. });
  772. test_all("Check Expectations Validity", [](const TestCase & tc) {
  773. if (tc.expected_status == SUCCESS) {
  774. tc.verify_expectation_parseable();
  775. }
  776. });
  777. }