diff --git a/common/json-schema-to-grammar.cpp b/common/json-schema-to-grammar.cpp index 389b09d45..cca69cf5b 100644 --- a/common/json-schema-to-grammar.cpp +++ b/common/json-schema-to-grammar.cpp @@ -177,8 +177,8 @@ private: string _visit_pattern(const string& pattern, const string& name) { if (!(pattern.front() == '^' && pattern.back() == '$')) { - _errors.push_back("Pattern must start with '^' and end with '$'"); - return ""; + _errors.push_back("Pattern must start with '^' and end with '$'"); + return ""; } string sub_pattern = pattern.substr(1, pattern.length() - 2); unordered_map sub_rule_ids; @@ -212,21 +212,21 @@ private: string literal; auto flush_literal = [&]() { - if (literal.empty()) { - return false; - } - ret.push_back(make_pair(literal, true)); - literal.clear(); - return true; + if (literal.empty()) { + return false; + } + ret.push_back(make_pair(literal, true)); + literal.clear(); + return true; }; for (const auto& item : seq) { auto is_literal = item.second; if (is_literal) { - literal += item.first; + literal += item.first; } else { - flush_literal(); - ret.push_back(item); + flush_literal(); + ret.push_back(item); } } flush_literal(); @@ -254,7 +254,7 @@ private: } else if (c == ')') { i++; if (start > 0 && sub_pattern[start - 1] != '(') { - _errors.push_back("Unbalanced parentheses"); + _errors.push_back("Unbalanced parentheses"); } return join_seq(); } else if (c == '[') { @@ -270,7 +270,7 @@ private: } } if (i >= length) { - _errors.push_back("Unbalanced square brackets"); + _errors.push_back("Unbalanced square brackets"); } square_brackets += ']'; i++; @@ -289,7 +289,7 @@ private: i++; } if (i >= length) { - _errors.push_back("Unbalanced curly brackets"); + _errors.push_back("Unbalanced curly brackets"); } curly_brackets += '}'; i++; @@ -547,8 +547,8 @@ public: for (size_t i = 1; i < tokens.size(); ++i) { string sel = tokens[i]; if (target.is_null() || !target.contains(sel)) { - _errors.push_back("Error resolving ref " + ref + ": " + sel + " not in " + target.dump()); - return; + _errors.push_back("Error resolving ref " + ref + ": " + sel + " not in " + target.dump()); + return; } target = target[sel]; } @@ -698,8 +698,8 @@ public: return _add_rule(rule_name, "object"); } else { if (!schema_type.is_string() || PRIMITIVE_RULES.find(schema_type.get()) == PRIMITIVE_RULES.end()) { - _errors.push_back("Unrecognized schema: " + schema.dump()); - return ""; + _errors.push_back("Unrecognized schema: " + schema.dump()); + return ""; } // TODO: support minimum, maximum, exclusiveMinimum, exclusiveMaximum at least for zero return _add_rule(rule_name == "root" ? "root" : schema_type.get(), PRIMITIVE_RULES.at(schema_type.get())); @@ -707,12 +707,12 @@ public: } void check_errors() { - if (!_errors.empty()) { - throw std::runtime_error("JSON schema conversion failed:\n" + join(_errors.begin(), _errors.end(), "\n")); - } - if (!_warnings.empty()) { - std::cerr << "WARNING: JSON schema conversion was incomplete: " + join(_warnings.begin(), _warnings.end(), "; ") << std::endl; - } + if (!_errors.empty()) { + throw std::runtime_error("JSON schema conversion failed:\n" + join(_errors.begin(), _errors.end(), "\n")); + } + if (!_warnings.empty()) { + std::cerr << "WARNING: JSON schema conversion was incomplete: " + join(_warnings.begin(), _warnings.end(), "; ") << std::endl; + } } string format_grammar() { @@ -725,10 +725,10 @@ public: }; string json_schema_to_grammar(const json& schema) { - SchemaConverter converter([](const string&) { return json::object(); }, /* dotall= */ false); - auto copy = schema; - converter.resolve_refs(copy, "input"); - converter.visit(copy, ""); - converter.check_errors(); - return converter.format_grammar(); + SchemaConverter converter([](const string&) { return json::object(); }, /* dotall= */ false); + auto copy = schema; + converter.resolve_refs(copy, "input"); + converter.visit(copy, ""); + converter.check_errors(); + return converter.format_grammar(); } diff --git a/examples/json-schema-pydantic-example.py b/examples/json-schema-pydantic-example.py index e506968d6..69ebfd409 100644 --- a/examples/json-schema-pydantic-example.py +++ b/examples/json-schema-pydantic-example.py @@ -4,71 +4,71 @@ #! python json-schema-pydantic-example.py from pydantic import BaseModel, TypeAdapter -from annotated_types import MaxLen, MinLen -from typing import Annotated, Iterable, List, Optional -import enum, json, requests +from annotated_types import MinLen +from typing import Annotated, List, Optional +import json, requests if True: - def create_completion(*, response_model=None, endpoint="http://localhost:8080/v1/chat/completions", messages, **kwargs): - ''' - Creates a chat completion using an OpenAI-compatible endpoint w/ JSON schema support - (llama.cpp server, llama-cpp-python, Anyscale / Together...) + def create_completion(*, response_model=None, endpoint="http://localhost:8080/v1/chat/completions", messages, **kwargs): + ''' + Creates a chat completion using an OpenAI-compatible endpoint w/ JSON schema support + (llama.cpp server, llama-cpp-python, Anyscale / Together...) - The response_model param takes a type (+ supports Pydantic) and behaves just as w/ Instructor (see below) - ''' - if response_model: - type_adapter = TypeAdapter(response_model) - schema = type_adapter.json_schema() - messages = [{ - "role": "system", - "content": f"You respond in JSON format with the following schema: {json.dumps(schema, indent=2)}" - }] + messages - response_format={"type": "json_object", "schema": schema} + The response_model param takes a type (+ supports Pydantic) and behaves just as w/ Instructor (see below) + ''' + if response_model: + type_adapter = TypeAdapter(response_model) + schema = type_adapter.json_schema() + messages = [{ + "role": "system", + "content": f"You respond in JSON format with the following schema: {json.dumps(schema, indent=2)}" + }] + messages + response_format={"type": "json_object", "schema": schema} - data = requests.post(endpoint, headers={"Content-Type": "application/json"}, - json=dict(messages=messages, response_format=response_format, **kwargs)).json() - if 'error' in data: - raise Exception(data['error']['message']) + data = requests.post(endpoint, headers={"Content-Type": "application/json"}, + json=dict(messages=messages, response_format=response_format, **kwargs)).json() + if 'error' in data: + raise Exception(data['error']['message']) - content = data["choices"][0]["message"]["content"] - return type_adapter.validate_json(content) if type_adapter else content + content = data["choices"][0]["message"]["content"] + return type_adapter.validate_json(content) if type_adapter else content else: - # This alternative branch uses Instructor + OpenAI client lib. - # Instructor support streamed iterable responses, retry & more. - # (see https://python.useinstructor.com/) - #! pip install instructor openai - import instructor, openai - client = instructor.patch( - openai.OpenAI(api_key="123", base_url="http://localhost:8080"), - mode=instructor.Mode.JSON_SCHEMA) - create_completion = client.chat.completions.create + # This alternative branch uses Instructor + OpenAI client lib. + # Instructor support streamed iterable responses, retry & more. + # (see https://python.useinstructor.com/) + #! pip install instructor openai + import instructor, openai + client = instructor.patch( + openai.OpenAI(api_key="123", base_url="http://localhost:8080"), + mode=instructor.Mode.JSON_SCHEMA) + create_completion = client.chat.completions.create if __name__ == '__main__': - class QAPair(BaseModel): - question: str - concise_answer: str - justification: str + class QAPair(BaseModel): + question: str + concise_answer: str + justification: str - class PyramidalSummary(BaseModel): - title: str - summary: str - question_answers: Annotated[List[QAPair], MinLen(2)] - sub_sections: Optional[Annotated[List['PyramidalSummary'], MinLen(2)]] + class PyramidalSummary(BaseModel): + title: str + summary: str + question_answers: Annotated[List[QAPair], MinLen(2)] + sub_sections: Optional[Annotated[List['PyramidalSummary'], MinLen(2)]] - print("# Summary\n", create_completion( - model="...", - response_model=PyramidalSummary, - messages=[{ - "role": "user", - "content": f""" - You are a highly efficient corporate document summarizer. - Create a pyramidal summary of an imaginary internal document about our company processes - (starting high-level, going down to each sub sections). - Keep questions short, and answers even shorter (trivia / quizz style). - """ - }])) + print("# Summary\n", create_completion( + model="...", + response_model=PyramidalSummary, + messages=[{ + "role": "user", + "content": f""" + You are a highly efficient corporate document summarizer. + Create a pyramidal summary of an imaginary internal document about our company processes + (starting high-level, going down to each sub sections). + Keep questions short, and answers even shorter (trivia / quizz style). + """ + }])) diff --git a/examples/regex-to-grammar.py b/examples/regex-to-grammar.py index 7fe856651..c98118e95 100644 --- a/examples/regex-to-grammar.py +++ b/examples/regex-to-grammar.py @@ -4,17 +4,17 @@ assert len(sys.argv) >= 2 [_, pattern, *rest] = sys.argv print(subprocess.check_output( - [ - "python", - os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "json-schema-to-grammar.py"), - *rest, - "-", - "--raw-pattern", - ], - text=True, - input=json.dumps({ - "type": "string", - "pattern": pattern, - }, indent=2))) + [ + "python", + os.path.join( + os.path.dirname(os.path.realpath(__file__)), + "json-schema-to-grammar.py"), + *rest, + "-", + "--raw-pattern", + ], + text=True, + input=json.dumps({ + "type": "string", + "pattern": pattern, + }, indent=2))) diff --git a/examples/server/utils.hpp b/examples/server/utils.hpp index 926f1fd85..9d6ec1a8a 100644 --- a/examples/server/utils.hpp +++ b/examples/server/utils.hpp @@ -377,14 +377,14 @@ static json oaicompat_completion_params_parse( } if (body.contains("response_format")) { - auto response_format = json_value(body, "response_format", json::object()); - if (response_format.contains("type")) { - if (response_format["type"] == "json_object") { - llama_params["json_schema"] = json_value(response_format, "schema", json::object()); - } else { - throw std::runtime_error("response_format type not supported: " + response_format["type"].dump()); + auto response_format = json_value(body, "response_format", json::object()); + if (response_format.contains("type")) { + if (response_format["type"] == "json_object") { + llama_params["json_schema"] = json_value(response_format, "schema", json::object()); + } else { + throw std::runtime_error("response_format type not supported: " + response_format["type"].dump()); + } } - } } // Handle 'stop' field diff --git a/tests/test-json-schema-to-grammar.cpp b/tests/test-json-schema-to-grammar.cpp index 82a209f5d..2637bf8d2 100755 --- a/tests/test-json-schema-to-grammar.cpp +++ b/tests/test-json-schema-to-grammar.cpp @@ -20,813 +20,812 @@ static std::string trim(const std::string & source) { } enum TestCaseStatus { - SUCCESS, FAILURE + SUCCESS, + FAILURE }; struct TestCase { - TestCaseStatus expected_status; - string name; - string schema; - string expected_grammar; + TestCaseStatus expected_status; + string name; + string schema; + string expected_grammar; - void _print_failure_header() const { - cerr << "#" << endl; - cerr << "# Test '" << name.c_str() << "' failed." << endl; - cerr << "#" << endl; - cerr << schema.c_str() << endl; - } - void verify(const string& actual_grammar) const { - if (trim(actual_grammar) != trim(expected_grammar)) { - _print_failure_header(); - cerr << "# EXPECTED:\n" << expected_grammar.c_str() << endl; - cerr << "# ACTUAL:\n" << actual_grammar.c_str() << endl; - assert(false); + void _print_failure_header() const { + cerr << "#" << endl; + cerr << "# Test '" << name.c_str() << "' failed." << endl; + cerr << "#" << endl; + cerr << schema.c_str() << endl; } - } - void verify_expectation_parseable() const { - try { - auto state = grammar_parser::parse(expected_grammar.c_str()); - if (state.symbol_ids.find("root") == state.symbol_ids.end()) { - throw runtime_error("Grammar failed to parse:\n" + expected_grammar); - } - } catch (const runtime_error& ex) { - _print_failure_header(); - cerr << "# GRAMMAR ERROR: " << ex.what() << endl; - assert(false); + void verify(const string& actual_grammar) const { + if (trim(actual_grammar) != trim(expected_grammar)) { + _print_failure_header(); + cerr << "# EXPECTED:\n" << expected_grammar.c_str() << endl; + cerr << "# ACTUAL:\n" << actual_grammar.c_str() << endl; + assert(false); + } } - } - void verify_status(TestCaseStatus status) const { - if (status != expected_status) { - _print_failure_header(); - cerr << "# EXPECTED STATUS: " << (expected_status == SUCCESS ? "SUCCESS" : "FAILURE") << endl; - cerr << "# ACTUAL STATUS: " << (status == SUCCESS ? "SUCCESS" : "FAILURE") << endl; - assert(false); + void verify_expectation_parseable() const { + try { + auto state = grammar_parser::parse(expected_grammar.c_str()); + if (state.symbol_ids.find("root") == state.symbol_ids.end()) { + throw runtime_error("Grammar failed to parse:\n" + expected_grammar); + } + } catch (const runtime_error& ex) { + _print_failure_header(); + cerr << "# GRAMMAR ERROR: " << ex.what() << endl; + assert(false); + } + } + void verify_status(TestCaseStatus status) const { + if (status != expected_status) { + _print_failure_header(); + cerr << "# EXPECTED STATUS: " << (expected_status == SUCCESS ? "SUCCESS" : "FAILURE") << endl; + cerr << "# ACTUAL STATUS: " << (status == SUCCESS ? "SUCCESS" : "FAILURE") << endl; + assert(false); + } } - } }; static void write(const string& file, const string& content) { - ofstream f; - f.open(file.c_str()); - f << content.c_str(); - f.close(); + ofstream f; + f.open(file.c_str()); + f << content.c_str(); + f.close(); } static string read(const string& file) { - ostringstream actuals; - actuals << ifstream(file.c_str()).rdbuf(); - return actuals.str(); + ostringstream actuals; + actuals << ifstream(file.c_str()).rdbuf(); + return actuals.str(); } static void test_all(const string& lang, std::function runner) { - cerr << "#\n# Testing JSON schema conversion (" << lang.c_str() << ")\n#" << endl; - auto test = [&](const TestCase& tc) { - cerr << "- " << tc.name.c_str() << (tc.expected_status == FAILURE ? " (failure expected)" : "") << endl; - runner(tc); - }; + cerr << "#\n# Testing JSON schema conversion (" << lang.c_str() << ")\n#" << endl; + auto test = [&](const TestCase& tc) { + cerr << "- " << tc.name.c_str() << (tc.expected_status == FAILURE ? " (failure expected)" : "") << endl; + runner(tc); + }; - test({ - FAILURE, - "unknown type", - R"""({ - "type": "kaboom" - })""", - "" - }); + test({ + FAILURE, + "unknown type", + R"""({ + "type": "kaboom" + })""", + "" + }); - test({ - FAILURE, - "invalid type type", - R"""({ - "type": 123 - })""", - "" - }); + test({ + FAILURE, + "invalid type type", + R"""({ + "type": 123 + })""", + "" + }); - test({ - SUCCESS, - "empty schema (object)", - "{}", - R"""( - array ::= "[" space ( value ("," space value)* )? "]" space - boolean ::= ("true" | "false") space - null ::= "null" space - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space - root ::= object - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - value ::= object | array | string | number | boolean - )""" - }); + test({ + SUCCESS, + "empty schema (object)", + "{}", + R"""( + array ::= "[" space ( value ("," space value)* )? "]" space + boolean ::= ("true" | "false") space + null ::= "null" space + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space + root ::= object + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + value ::= object | array | string | number | boolean + )""" + }); - test({ - SUCCESS, - "exotic formats", - R"""({ - "items": [ - { "format": "date" }, - { "format": "uuid" }, - { "format": "time" }, - { "format": "date-time" } - ] - })""", - R"""( - 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] ) - date-string ::= "\"" date "\"" space - date-time ::= date "T" time - date-time-string ::= "\"" date-time "\"" space - root ::= "[" space date-string "," space uuid "," space time-string "," space date-time-string "]" space - space ::= " "? - 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] ) - time-string ::= "\"" time "\"" space - 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 - )""" - }); + test({ + SUCCESS, + "exotic formats", + R"""({ + "items": [ + { "format": "date" }, + { "format": "uuid" }, + { "format": "time" }, + { "format": "date-time" } + ] + })""", + R"""( + 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] ) + date-string ::= "\"" date "\"" space + date-time ::= date "T" time + date-time-string ::= "\"" date-time "\"" space + root ::= "[" space date-string "," space uuid "," space time-string "," space date-time-string "]" space + space ::= " "? + 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] ) + time-string ::= "\"" time "\"" space + 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 + )""" + }); - test({ - SUCCESS, - "string", - R"""({ - "type": "string" - })""", - R"""( - root ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "string", + R"""({ + "type": "string" + })""", + R"""( + root ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "boolean", - R"""({ - "type": "boolean" - })""", - R"""( - root ::= ("true" | "false") space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "boolean", + R"""({ + "type": "boolean" + })""", + R"""( + root ::= ("true" | "false") space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "integer", - R"""({ - "type": "integer" - })""", - R"""( - root ::= ("-"? ([0-9] | [1-9] [0-9]*)) space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "integer", + R"""({ + "type": "integer" + })""", + R"""( + root ::= ("-"? ([0-9] | [1-9] [0-9]*)) space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "string const", - R"""({ - "const": "foo" - })""", - R"""( - root ::= "\"foo\"" - space ::= " "? - )""" - }); + test({ + SUCCESS, + "string const", + R"""({ + "const": "foo" + })""", + R"""( + root ::= "\"foo\"" + space ::= " "? + )""" + }); - test({ - FAILURE, - "non-string const", - R"""({ - "const": 123 - })""", - "" - }); + test({ + FAILURE, + "non-string const", + R"""({ + "const": 123 + })""", + "" + }); - test({ - FAILURE, - "non-string enum", - R"""({ - "enum": [123] - })""", - "" - }); + test({ + FAILURE, + "non-string enum", + R"""({ + "enum": [123] + })""", + "" + }); - test({ - SUCCESS, - "tuple1", - R"""({ - "prefixItems": [{ "type": "string" }] - })""", - R"""( - root ::= "[" space string "]" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); + test({ + SUCCESS, + "tuple1", + R"""({ + "prefixItems": [{ "type": "string" }] + })""", + R"""( + root ::= "[" space string "]" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); - test({ - SUCCESS, - "tuple2", - R"""({ - "prefixItems": [{ "type": "string" }, { "type": "number" }] - })""", - R"""( - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= "[" space string "," space number "]" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); + test({ + SUCCESS, + "tuple2", + R"""({ + "prefixItems": [{ "type": "string" }, { "type": "number" }] + })""", + R"""( + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= "[" space string "," space number "]" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); - test({ - SUCCESS, - "number", - R"""({ - "type": "number" - })""", - R"""( - root ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "number", + R"""({ + "type": "number" + })""", + R"""( + root ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "minItems", - R"""({ - "items": { - "type": "boolean" - }, - "minItems": 2 - })""", - R"""( - boolean ::= ("true" | "false") space - root ::= "[" space boolean ( "," space boolean )( "," space boolean )* "]" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "minItems", + R"""({ + "items": { + "type": "boolean" + }, + "minItems": 2 + })""", + R"""( + boolean ::= ("true" | "false") space + root ::= "[" space boolean ( "," space boolean )( "," space boolean )* "]" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "maxItems 1", - R"""({ - "items": { - "type": "boolean" - }, - "maxItems": 1 - })""", - R"""( - boolean ::= ("true" | "false") space - root ::= "[" space ( boolean )? "]" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "maxItems 1", + R"""({ + "items": { + "type": "boolean" + }, + "maxItems": 1 + })""", + R"""( + boolean ::= ("true" | "false") space + root ::= "[" space ( boolean )? "]" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "maxItems 2", - R"""({ - "items": { - "type": "boolean" - }, - "maxItems": 2 - })""", - R"""( - boolean ::= ("true" | "false") space - root ::= "[" space ( boolean ( "," space boolean )? )? "]" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "maxItems 2", + R"""({ + "items": { + "type": "boolean" + }, + "maxItems": 2 + })""", + R"""( + boolean ::= ("true" | "false") space + root ::= "[" space ( boolean ( "," space boolean )? )? "]" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "min + maxItems", - R"""({ - "items": { - "type": ["number", "integer"] - }, - "minItems": 3, - "maxItems": 5 - })""", - R"""( - integer ::= ("-"? ([0-9] | [1-9] [0-9]*)) space - item ::= number | integer - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= "[" space item ( "," space item )( "," space item )( "," space item )?( "," space item )? "]" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "min + maxItems", + R"""({ + "items": { + "type": ["number", "integer"] + }, + "minItems": 3, + "maxItems": 5 + })""", + R"""( + integer ::= ("-"? ([0-9] | [1-9] [0-9]*)) space + item ::= number | integer + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= "[" space item ( "," space item )( "," space item )( "," space item )?( "," space item )? "]" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "simple regexp", - R"""({ - "type": "string", - "pattern": "^abc?d*efg+(hij)?kl$" - })""", - R"""( - root ::= "\"" "ab" "c"? "d"* "ef" "g"+ ("hij")? "kl" "\"" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "simple regexp", + R"""({ + "type": "string", + "pattern": "^abc?d*efg+(hij)?kl$" + })""", + R"""( + root ::= "\"" "ab" "c"? "d"* "ef" "g"+ ("hij")? "kl" "\"" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "regexp escapes", - R"""({ - "type": "string", - "pattern": "^\\[\\]\\{\\}\\(\\)\\|\\+\\*\\?$" - })""", - R"""( - root ::= "\"" "[]{}()|+*?" "\"" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "regexp escapes", + R"""({ + "type": "string", + "pattern": "^\\[\\]\\{\\}\\(\\)\\|\\+\\*\\?$" + })""", + R"""( + root ::= "\"" "[]{}()|+*?" "\"" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "regexp quote", - R"""({ - "type": "string", - "pattern": "^\"$" - })""", - R"""( - root ::= "\"" "\"" "\"" space - space ::= " "? - )""" - }); + test({ + SUCCESS, + "regexp quote", + R"""({ + "type": "string", + "pattern": "^\"$" + })""", + R"""( + root ::= "\"" "\"" "\"" space + space ::= " "? + )""" + }); - test({ - SUCCESS, - "regexp", - R"""({ - "type": "string", - "pattern": "^(\\([0-9]{1,3}\\))?[0-9]{3}-[0-9]{4} and...$" - })""", - R"""( - dot ::= [\U00000000-\x09\x0B\x0C\x0E-\U0010FFFF] - 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 - root-1 ::= [0-9] - space ::= " "? - )""" - }); + test({ + SUCCESS, + "regexp", + R"""({ + "type": "string", + "pattern": "^(\\([0-9]{1,3}\\))?[0-9]{3}-[0-9]{4} and...$" + })""", + R"""( + dot ::= [\U00000000-\x09\x0B\x0C\x0E-\U0010FFFF] + 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 + root-1 ::= [0-9] + space ::= " "? + )""" + }); - test({ - SUCCESS, - "required props", - R"""({ - "type": "object", - "properties": { - "a": { - "type": "string" - }, - "b": { - "type": "string" - } - }, - "required": [ - "a", - "b" - ], - "additionalProperties": false, - "definitions": {} - })""", - R"""( - a-kv ::= "\"a\"" space ":" space string - b-kv ::= "\"b\"" space ":" space string - root ::= "{" space a-kv "," space b-kv "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "1 optional prop", - R"""({ - "properties": { - "a": { - "type": "string" - } - }, - "additionalProperties": false - })""", - R"""( - a-kv ::= "\"a\"" space ":" space string - root ::= "{" space (a-kv )? "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "N optional props", - R"""({ - "properties": { - "a": {"type": "string"}, - "b": {"type": "string"}, - "c": {"type": "string"} - }, - "additionalProperties": false - })""", - R"""( - a-kv ::= "\"a\"" space ":" space string - a-rest ::= ( "," space b-kv )? b-rest - b-kv ::= "\"b\"" space ":" space string - b-rest ::= ( "," space c-kv )? - c-kv ::= "\"c\"" space ":" space string - root ::= "{" space (a-kv a-rest | b-kv b-rest | c-kv )? "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "required + optional props", - R"""({ - "properties": { - "a": {"type": "string"}, - "b": {"type": "string"}, - "c": {"type": "string"}, - "d": {"type": "string"} - }, - "required": ["a", "b"], - "additionalProperties": false - })""", - R"""( - a-kv ::= "\"a\"" space ":" space string - b-kv ::= "\"b\"" space ":" space string - c-kv ::= "\"c\"" space ":" space string - c-rest ::= ( "," space d-kv )? - d-kv ::= "\"d\"" space ":" space string - root ::= "{" space a-kv "," space b-kv ( "," space ( c-kv c-rest | d-kv ) )? "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "additional props", - R"""({ - "type": "object", - "additionalProperties": {"type": "array", "items": {"type": "number"}} - })""", - R"""( - additional-kv ::= string ":" space additional-value - additional-kvs ::= additional-kv ( "," space additional-kv )* - additional-value ::= "[" space ( number ( "," space number )* )? "]" space - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= "{" space (additional-kvs )? "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "additional props (true)", - R"""({ - "type": "object", - "additionalProperties": true - })""", - R"""( - array ::= "[" space ( value ("," space value)* )? "]" space - boolean ::= ("true" | "false") space - null ::= "null" space - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space - root ::= object - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - value ::= object | array | string | number | boolean - )""" - }); - - test({ - SUCCESS, - "additional props (implicit)", - R"""({ - "type": "object" - })""", - R"""( - array ::= "[" space ( value ("," space value)* )? "]" space - boolean ::= ("true" | "false") space - null ::= "null" space - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space - root ::= object - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - value ::= object | array | string | number | boolean - )""" - }); - - test({ - SUCCESS, - "empty w/o additional props", - R"""({ - "type": "object", - "additionalProperties": false - })""", - R"""( - root ::= "{" space "}" space - space ::= " "? - )""" - }); - - test({ - SUCCESS, - "required + additional props", - R"""({ - "type": "object", - "properties": { - "a": {"type": "number"} - }, - "required": ["a"], - "additionalProperties": {"type": "string"} - })""", - R"""( - a-kv ::= "\"a\"" space ":" space number - additional-kv ::= string ":" space string - additional-kvs ::= additional-kv ( "," space additional-kv )* - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= "{" space a-kv ( "," space ( additional-kvs ) )? "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "optional + additional props", - R"""({ - "type": "object", - "properties": { - "a": {"type": "number"} - }, - "additionalProperties": {"type": "number"} - })""", - R"""( - a-kv ::= "\"a\"" space ":" space number - a-rest ::= additional-kvs - additional-kv ::= string ":" space number - additional-kvs ::= additional-kv ( "," space additional-kv )* - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= "{" space (a-kv a-rest | additional-kvs )? "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "required + optional + additional props", - R"""({ - "type": "object", - "properties": { - "a": {"type": "number"}, - "b": {"type": "number"} - }, - "required": ["a"], - "additionalProperties": {"type": "number"} - })""", - R"""( - a-kv ::= "\"a\"" space ":" space number - additional-kv ::= string ":" space number - additional-kvs ::= additional-kv ( "," space additional-kv )* - b-kv ::= "\"b\"" space ":" space number - b-rest ::= additional-kvs - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= "{" space a-kv ( "," space ( b-kv b-rest | additional-kvs ) )? "}" space - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "top-level $ref", - R"""({ - "$ref": "#/definitions/MyType", - "definitions": { - "MyType": { - "type": "object", - "properties": { - "a": { - "type": "string" - } - }, - "required": [ - "a" - ], - "additionalProperties": false - } - } - })""", - R"""( - MyType ::= "{" space MyType-a-kv "}" space - MyType-a-kv ::= "\"a\"" space ":" space string - root ::= MyType - space ::= " "? - string ::= "\"" ( - [^"\\] | - "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) - )* "\"" space - )""" - }); - - test({ - SUCCESS, - "anyOf", - R"""({ - "anyOf": [ - {"$ref": "#/definitions/foo"}, - {"$ref": "#/definitions/bar"} - ], - "definitions": { - "foo": { - "properties": {"a": {"type": "number"}} - }, - "bar": { - "properties": {"b": {"type": "number"}} - } - }, - "type": "object" - })""", - R"""( - alternative-0 ::= foo - alternative-1 ::= bar - bar ::= "{" space (bar-b-kv )? "}" space - bar-b-kv ::= "\"b\"" space ":" space number - foo ::= "{" space (foo-a-kv )? "}" space - foo-a-kv ::= "\"a\"" space ":" space number - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= alternative-0 | alternative-1 - space ::= " "? - )""" - }); - - test({ - SUCCESS, - "mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)", - R"""({ - "allOf": [ - {"$ref": "#/definitions/foo"}, - {"$ref": "#/definitions/bar"}, - { - "anyOf": [ - {"$ref": "#/definitions/baz"}, - {"$ref": "#/definitions/bam"} - ] - } - ], - "definitions": { - "foo": { - "properties": {"a": {"type": "number"}} - }, - "bar": { - "properties": {"b": {"type": "number"}} - }, - "bam": { - "properties": {"c": {"type": "number"}} - }, - "baz": { - "properties": {"d": {"type": "number"}} - } - }, - "type": "object" - })""", - R"""( - a-kv ::= "\"a\"" space ":" space number - b-kv ::= "\"b\"" space ":" space number - c-kv ::= "\"c\"" space ":" space number - d-kv ::= "\"d\"" space ":" space number - d-rest ::= ( "," space c-kv )? - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - root ::= "{" space a-kv "," space b-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space - space ::= " "? - )""" - }); - - test({ - SUCCESS, - "conflicting names", - R"""({ - "type": "object", - "properties": { - "number": { - "type": "object", - "properties": { - "number": { - "type": "object", - "properties": { - "root": { - "type": "number" + test({ + SUCCESS, + "required props", + R"""({ + "type": "object", + "properties": { + "a": { + "type": "string" + }, + "b": { + "type": "string" + } + }, + "required": [ + "a", + "b" + ], + "additionalProperties": false, + "definitions": {} + })""", + R"""( + a-kv ::= "\"a\"" space ":" space string + b-kv ::= "\"b\"" space ":" space string + root ::= "{" space a-kv "," space b-kv "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "1 optional prop", + R"""({ + "properties": { + "a": { + "type": "string" + } + }, + "additionalProperties": false + })""", + R"""( + a-kv ::= "\"a\"" space ":" space string + root ::= "{" space (a-kv )? "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "N optional props", + R"""({ + "properties": { + "a": {"type": "string"}, + "b": {"type": "string"}, + "c": {"type": "string"} + }, + "additionalProperties": false + })""", + R"""( + a-kv ::= "\"a\"" space ":" space string + a-rest ::= ( "," space b-kv )? b-rest + b-kv ::= "\"b\"" space ":" space string + b-rest ::= ( "," space c-kv )? + c-kv ::= "\"c\"" space ":" space string + root ::= "{" space (a-kv a-rest | b-kv b-rest | c-kv )? "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "required + optional props", + R"""({ + "properties": { + "a": {"type": "string"}, + "b": {"type": "string"}, + "c": {"type": "string"}, + "d": {"type": "string"} + }, + "required": ["a", "b"], + "additionalProperties": false + })""", + R"""( + a-kv ::= "\"a\"" space ":" space string + b-kv ::= "\"b\"" space ":" space string + c-kv ::= "\"c\"" space ":" space string + c-rest ::= ( "," space d-kv )? + d-kv ::= "\"d\"" space ":" space string + root ::= "{" space a-kv "," space b-kv ( "," space ( c-kv c-rest | d-kv ) )? "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "additional props", + R"""({ + "type": "object", + "additionalProperties": {"type": "array", "items": {"type": "number"}} + })""", + R"""( + additional-kv ::= string ":" space additional-value + additional-kvs ::= additional-kv ( "," space additional-kv )* + additional-value ::= "[" space ( number ( "," space number )* )? "]" space + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= "{" space (additional-kvs )? "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "additional props (true)", + R"""({ + "type": "object", + "additionalProperties": true + })""", + R"""( + array ::= "[" space ( value ("," space value)* )? "]" space + boolean ::= ("true" | "false") space + null ::= "null" space + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space + root ::= object + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + value ::= object | array | string | number | boolean + )""" + }); + + test({ + SUCCESS, + "additional props (implicit)", + R"""({ + "type": "object" + })""", + R"""( + array ::= "[" space ( value ("," space value)* )? "]" space + boolean ::= ("true" | "false") space + null ::= "null" space + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + object ::= "{" space ( string ":" space value ("," space string ":" space value)* )? "}" space + root ::= object + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + value ::= object | array | string | number | boolean + )""" + }); + + test({ + SUCCESS, + "empty w/o additional props", + R"""({ + "type": "object", + "additionalProperties": false + })""", + R"""( + root ::= "{" space "}" space + space ::= " "? + )""" + }); + + test({ + SUCCESS, + "required + additional props", + R"""({ + "type": "object", + "properties": { + "a": {"type": "number"} + }, + "required": ["a"], + "additionalProperties": {"type": "string"} + })""", + R"""( + a-kv ::= "\"a\"" space ":" space number + additional-kv ::= string ":" space string + additional-kvs ::= additional-kv ( "," space additional-kv )* + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= "{" space a-kv ( "," space ( additional-kvs ) )? "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "optional + additional props", + R"""({ + "type": "object", + "properties": { + "a": {"type": "number"} + }, + "additionalProperties": {"type": "number"} + })""", + R"""( + a-kv ::= "\"a\"" space ":" space number + a-rest ::= additional-kvs + additional-kv ::= string ":" space number + additional-kvs ::= additional-kv ( "," space additional-kv )* + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= "{" space (a-kv a-rest | additional-kvs )? "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "required + optional + additional props", + R"""({ + "type": "object", + "properties": { + "a": {"type": "number"}, + "b": {"type": "number"} + }, + "required": ["a"], + "additionalProperties": {"type": "number"} + })""", + R"""( + a-kv ::= "\"a\"" space ":" space number + additional-kv ::= string ":" space number + additional-kvs ::= additional-kv ( "," space additional-kv )* + b-kv ::= "\"b\"" space ":" space number + b-rest ::= additional-kvs + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= "{" space a-kv ( "," space ( b-kv b-rest | additional-kvs ) )? "}" space + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "top-level $ref", + R"""({ + "$ref": "#/definitions/MyType", + "definitions": { + "MyType": { + "type": "object", + "properties": { + "a": { + "type": "string" + } + }, + "required": [ + "a" + ], + "additionalProperties": false } - }, - "required": [ - "root" - ], - "additionalProperties": false } - }, - "required": [ - "number" - ], - "additionalProperties": false - } - }, - "required": [ - "number" - ], - "additionalProperties": false, - "definitions": {} - })""", - R"""( - number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space - number- ::= "{" space number-number-kv "}" space - number-kv ::= "\"number\"" space ":" space number- - number-number ::= "{" space number-number-root-kv "}" space - number-number-kv ::= "\"number\"" space ":" space number-number - number-number-root-kv ::= "\"root\"" space ":" space number - root ::= "{" space number-kv "}" space - space ::= " "? - )""" - }); + })""", + R"""( + MyType ::= "{" space MyType-a-kv "}" space + MyType-a-kv ::= "\"a\"" space ":" space string + root ::= MyType + space ::= " "? + string ::= "\"" ( + [^"\\] | + "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F]) + )* "\"" space + )""" + }); + + test({ + SUCCESS, + "anyOf", + R"""({ + "anyOf": [ + {"$ref": "#/definitions/foo"}, + {"$ref": "#/definitions/bar"} + ], + "definitions": { + "foo": { + "properties": {"a": {"type": "number"}} + }, + "bar": { + "properties": {"b": {"type": "number"}} + } + }, + "type": "object" + })""", + R"""( + alternative-0 ::= foo + alternative-1 ::= bar + bar ::= "{" space (bar-b-kv )? "}" space + bar-b-kv ::= "\"b\"" space ":" space number + foo ::= "{" space (foo-a-kv )? "}" space + foo-a-kv ::= "\"a\"" space ":" space number + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= alternative-0 | alternative-1 + space ::= " "? + )""" + }); + + test({ + SUCCESS, + "mix of allOf, anyOf and $ref (similar to https://json.schemastore.org/tsconfig.json)", + R"""({ + "allOf": [ + {"$ref": "#/definitions/foo"}, + {"$ref": "#/definitions/bar"}, + { + "anyOf": [ + {"$ref": "#/definitions/baz"}, + {"$ref": "#/definitions/bam"} + ] + } + ], + "definitions": { + "foo": { + "properties": {"a": {"type": "number"}} + }, + "bar": { + "properties": {"b": {"type": "number"}} + }, + "bam": { + "properties": {"c": {"type": "number"}} + }, + "baz": { + "properties": {"d": {"type": "number"}} + } + }, + "type": "object" + })""", + R"""( + a-kv ::= "\"a\"" space ":" space number + b-kv ::= "\"b\"" space ":" space number + c-kv ::= "\"c\"" space ":" space number + d-kv ::= "\"d\"" space ":" space number + d-rest ::= ( "," space c-kv )? + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + root ::= "{" space a-kv "," space b-kv ( "," space ( d-kv d-rest | c-kv ) )? "}" space + space ::= " "? + )""" + }); + + test({ + SUCCESS, + "conflicting names", + R"""({ + "type": "object", + "properties": { + "number": { + "type": "object", + "properties": { + "number": { + "type": "object", + "properties": { + "root": { + "type": "number" + } + }, + "required": [ + "root" + ], + "additionalProperties": false + } + }, + "required": [ + "number" + ], + "additionalProperties": false + } + }, + "required": [ + "number" + ], + "additionalProperties": false, + "definitions": {} + })""", + R"""( + number ::= ("-"? ([0-9] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? space + number- ::= "{" space number-number-kv "}" space + number-kv ::= "\"number\"" space ":" space number- + number-number ::= "{" space number-number-root-kv "}" space + number-number-kv ::= "\"number\"" space ":" space number-number + number-number-root-kv ::= "\"root\"" space ":" space number + root ::= "{" space number-kv "}" space + space ::= " "? + )""" + }); } int main() { - test_all("C++", [](const TestCase& tc) { - try { - tc.verify(json_schema_to_grammar(nlohmann::json::parse(tc.schema))); - tc.verify_status(SUCCESS); - } catch (const runtime_error& ex) { - cerr << "Error: " << ex.what() << endl; - tc.verify_status(FAILURE); - } - }); - test_all("Python", [](const TestCase& tc) { + test_all("C++", [](const TestCase& tc) { + try { + tc.verify(json_schema_to_grammar(nlohmann::json::parse(tc.schema))); + tc.verify_status(SUCCESS); + } catch (const runtime_error& ex) { + cerr << "Error: " << ex.what() << endl; + tc.verify_status(FAILURE); + } + }); + test_all("Python", [](const TestCase& tc) { + write("test-json-schema-input.tmp", tc.schema); + tc.verify_status(std::system( + "python ./examples/json-schema-to-grammar.py test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE); + tc.verify(read("test-grammar-output.tmp")); + }); + test_all("JavaScript", [](const TestCase& tc) { + write("test-json-schema-input.tmp", tc.schema); + tc.verify_status(std::system( + "node ./tests/run-json-schema-to-grammar.mjs test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE); + tc.verify(read("test-grammar-output.tmp")); + }); - write("test-json-schema-input.tmp", tc.schema); - tc.verify_status(std::system( - "python ./examples/json-schema-to-grammar.py test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE); - tc.verify(read("test-grammar-output.tmp")); - }); - test_all("JavaScript", [](const TestCase& tc) { - - write("test-json-schema-input.tmp", tc.schema); - tc.verify_status(std::system( - "node ./tests/run-json-schema-to-grammar.mjs test-json-schema-input.tmp > test-grammar-output.tmp") == 0 ? SUCCESS : FAILURE); - tc.verify(read("test-grammar-output.tmp")); - }); - - test_all("Check Expectations Validity", [](const TestCase& tc) { - if (tc.expected_status == SUCCESS) { - tc.verify_expectation_parseable(); - } - }); + test_all("Check Expectations Validity", [](const TestCase& tc) { + if (tc.expected_status == SUCCESS) { + tc.verify_expectation_parseable(); + } + }); }