Felipe97/llama-cpp-compiled
01.1k
1#ifdef NDEBUG2#undef NDEBUG3#endif4 5#include "json-schema-to-grammar.h"6 7#include "../src/unicode.h"8#include "../src/llama-grammar.h"9 10#include "json.h"11 12#include <cassert>13#include <string>14#include <vector>15 16using json = common_json;17 18static llama_grammar * build_grammar_with_root(const std::string & grammar_str, const char * grammar_root) {19 return llama_grammar_init_impl(nullptr, grammar_str.c_str(), grammar_root, false, nullptr, 0, nullptr, 0);20}21 22static llama_grammar * build_grammar(const std::string & grammar_str) {23 return build_grammar_with_root(grammar_str, "root");24}25 26static bool test_build_grammar_fails(const std::string & grammar_str) {27 fprintf(stderr, "⚫ Testing failure for grammar: %s\n", grammar_str.c_str());28 bool grammar_fails = false;29 llama_grammar * grammar = build_grammar(grammar_str);30 if (grammar != nullptr) {31 fprintf(stderr, " ❌ Expected build failure, but succeeded\n");32 } else {33 grammar_fails = true;34 fprintf(stdout, " ✅︎\n");35 }36 return grammar_fails;37}38 39struct token_and_piece {40 llama_token token;41 std::string piece;42};43 44// token() encodes a 32-bit ID as 5 bytes: a 0xff marker followed by the ID in big-endian order.45static std::string token(llama_token id) {46 return std::string{47 static_cast<char>(0xff),48 static_cast<char>((id >> 24) & 0xff),49 static_cast<char>((id >> 16) & 0xff),50 static_cast<char>((id >> 8) & 0xff),51 static_cast<char>(id & 0xff)52 };53}54 55// parse_tokens() parses the token encodes above and UTF-8 text.56static std::vector<token_and_piece> parse_tokens(const std::string & input) {57 std::vector<token_and_piece> result;58 result.reserve(input.size());59 size_t offset = 0;60 while (offset < input.size()) {61 try {62 if (static_cast<unsigned char>(input[offset]) == 0xff) {63 if (offset + 5 > input.size()) {64 throw std::runtime_error("not enough bytes for token id");65 }66 uint32_t val =67 (static_cast<unsigned char>(input[offset + 1]) << 24) |68 (static_cast<unsigned char>(input[offset + 2]) << 16) |69 (static_cast<unsigned char>(input[offset + 3]) << 8) |70 (static_cast<unsigned char>(input[offset + 4]));71 auto piece = "<[" + std::to_string(val) + "]>";72 result.push_back({static_cast<llama_token>(val), piece});73 offset += 5;74 } else {75 uint32_t cpt = unicode_cpt_from_utf8(input, offset);76 result.push_back({0, unicode_cpt_to_utf8(cpt)});77 }78 } catch (const std::invalid_argument & /*ex*/) {79 // Silently ignore invalid UTF-8 input to avoid leaking the exception beyond llama_tokenize80 ++offset;81 result.push_back({0, unicode_cpt_to_utf8(0xFFFD)}); // replacement character82 }83 }84 return result;85}86 87static bool match_string(const std::string & input, llama_grammar * grammar) {88 const auto parsed = parse_tokens(input);89 90 auto & stacks_cur = llama_grammar_get_stacks(grammar);91 92 for (const auto & in : parsed) {93 try {94 llama_grammar_accept_token(*grammar, in.token, in.piece);95 } catch (const std::runtime_error & /*e*/) {96 // normally this shouldn't get hit because of llama_grammar_apply97 return false;98 }99 100 if (stacks_cur.empty()) {101 // no stacks means that the grammar failed to match at this point102 return false;103 }104 }105 106 for (const auto & stack : stacks_cur) {107 if (stack.empty()) {108 // An empty stack means that the grammar has been completed109 return true;110 }111 }112 113 return false;114}115 116static void test(const std::string & test_desc, const std::string & grammar_str, const std::vector<std::string> & passing_strings, const std::vector<std::string> & failing_strings) {117 fprintf(stderr, "⚫ Testing %s\n%s\n", test_desc.c_str(), grammar_str.c_str());118 fflush(stderr);119 120 auto * grammar = build_grammar(grammar_str);121 122 // Save the original grammar stacks so that we can reset after every new string we want to test123 const llama_grammar_stacks stacks_org = llama_grammar_get_stacks(grammar); // copy124 125 llama_grammar_stacks & stacks_cur = llama_grammar_get_stacks(grammar);126 127 fprintf(stderr, " 🔵 Valid strings:\n");128 129 // Passing strings130 for (const auto & test_string : passing_strings) {131 fprintf(stderr, " \"%s\" ", test_string.c_str());132 fflush(stderr);133 134 bool matched = match_string(test_string, grammar);135 136 if (!matched) {137 fprintf(stderr, "❌ (failed to match)\n");138 139 // DEBUG: Write strings to files so that we can analyze more easily with gbnf-validator program to see exactly where things failed.140 // DEBUG: Write the grammar_str to test-grammar-integration.grammar.gbnf141 FILE* grammar_file = fopen("test-grammar-integration.grammar.gbnf", "w");142 if (grammar_file) {143 fprintf(grammar_file, "%s", grammar_str.c_str());144 fclose(grammar_file);145 }146 147 // DEBUG: Write the test string to test-grammar-integration.string.txt148 FILE* string_file = fopen("test-grammar-integration.string.txt", "w");149 if (string_file) {150 fprintf(string_file, "%s", test_string.c_str());151 fclose(string_file);152 }153 154 fprintf(stderr, "\n NOTE: Debug grammar file generated. To analyze this failure in detail, run the following command: ./llama-gbnf-validator test-grammar-integration.grammar.gbnf test-grammar-integration.string.txt\n\n");155 } else {156 fprintf(stdout, "✅︎\n");157 }158 159 assert(matched);160 161 // Reset the grammar stacks162 stacks_cur = stacks_org;163 }164 165 fprintf(stderr, " 🟠 Invalid strings:\n");166 167 // Failing strings168 for (const auto & test_string : failing_strings) {169 fprintf(stderr, " \"%s\" ", test_string.c_str());170 fflush(stderr);171 172 bool matched = match_string(test_string, grammar);173 174 if (matched) {175 fprintf(stderr, "❌ (incorrectly matched)\n");176 } else {177 fprintf(stdout, "✅︎\n");178 }179 assert(!matched);180 181 // Reset the grammar stacks182 stacks_cur = stacks_org;183 }184 185 // Clean up allocated memory186 llama_grammar_free_impl(grammar);187}188static void test_grammar(const std::string & test_desc, const std::string & grammar_str, const std::vector<std::string> & passing_strings, const std::vector<std::string> & failing_strings) {189 test(test_desc + ". Grammar: " + grammar_str, grammar_str, passing_strings, failing_strings);190}191static void test_schema(const std::string & test_desc, const std::string & schema_str, const std::vector<std::string> & passing_strings, const std::vector<std::string> & failing_strings) {192 test(test_desc + ". Schema: " + schema_str, json_schema_to_grammar(json::parse(schema_str), true), passing_strings, failing_strings);193}194 195static void test_simple_grammar() {196 test_schema(197 "min 0",198 R"""({199 "type": "integer",200 "minimum": 0201 })""",202 // Passing strings203 {204 "0",205 "10",206 "12",207 "10000",208 },209 // Failing strings210 {211 "-1",212 "-10",213 "-10000",214 "-100000000000000000000000000000000",215 "100000000000000000000000000000000",216 "00",217 "01",218 "-0",219 }220 );221 test_schema(222 "min 2",223 // Schema224 R"""({225 "type": "integer",226 "minimum": 2227 })""",228 // Passing strings229 {230 "2",231 "3",232 "4",233 "10",234 "20",235 "1234567890000000",236 },237 // Failing strings238 {239 "0",240 "1",241 "-1",242 "-100",243 "0",244 "1",245 "01",246 "02",247 "12345678900000000",248 }249 );250 test_schema(251 "min 456",252 R"""({253 "type": "integer",254 "minimum": 456255 })""",256 // Passing strings257 {258 "456",259 "4560",260 "457",261 "460",262 "500",263 },264 // Failing strings265 {266 "455",267 "356",268 "50",269 "050",270 "-1",271 "-456",272 }273 );274 test_schema(275 "min -123",276 R"""({277 "type": "integer",278 "minimum": -123279 })""",280 // Passing strings281 {282 "-123",283 "-122",284 "-11",285 "-1",286 "0",287 "1",288 "123",289 "1234",290 "2345",291 },292 // Failing strings293 {294 "-1234",295 "-124",296 }297 );298 299 test_schema(300 "max 9999",301 // Schema302 R"""({303 "type": "integer",304 "maximum": 9999305 })""",306 // Passing strings307 {308 "-99999",309 "0",310 "9999",311 },312 // Failing strings313 {314 "10000",315 "99991",316 }317 );318 test_schema(319 "max -9999",320 // Schema321 R"""({322 "type": "integer",323 "maximum": -9999324 })""",325 // Passing strings326 {327 "-10000",328 "-9999",329 },330 // Failing strings331 {332 "-9998",333 "0",334 "9999",335 }336 );337 test_schema(338 "min 5 max 30",339 // Schema340 R"""({341 "type": "integer",342 "minimum": 5,343 "maximum": 30344 })""",345 // Passing strings346 {347 "5",348 "10",349 "30",350 },351 // Failing strings352 {353 "05",354 "4",355 "-1",356 "31",357 "123",358 "0123",359 }360 );361 test_schema(362 "min 1 max 900719925474091",363 // Schema364 R"""({365 "type": "integer",366 "exclusiveMinimum": 0,367 "maximum": 900719925474091368 })""",369 // Passing strings370 {371 "1",372 "2",373 "10",374 "900719925474090",375 "900719925474091",376 },377 // Failing strings378 {379 "0",380 "01",381 "900719925474092",382 "9007199254740910",383 }384 );385 test_schema(386 "min -1 max 1",387 R"""({388 "type": "integer",389 "minimum": -1,390 "maximum": 1391 })""",392 // Passing strings393 {394 "-1",395 "0",396 "1",397 },398 // Failing strings399 {400 "-11",401 "-10",402 "-2",403 "2",404 "10",405 "11",406 }407 );408 test_schema(409 "min -123 max 42",410 R"""({411 "type": "integer",412 "minimum": -123,413 "maximum": 42414 })""",415 // Passing strings416 {417 "-123",418 "-122",419 "-13",420 "-11",421 "-2",422 "-1",423 "0",424 "1",425 "5",426 "10",427 "39",428 "40",429 "42",430 },431 // Failing strings432 {433 "-0123",434 "-124",435 "-1123",436 "-200",437 "43",438 "123",439 "0123",440 }441 );442 test_schema(443 "exclusive min / max",444 // Schema445 R"""({446 "type": "integer",447 "exclusiveMinimum": 0,448 "exclusiveMaximum": 10000449 })""",450 // Passing strings451 {452 "1",453 "9999",454 },455 // Failing strings456 {457 "0",458 "01",459 "10000",460 "99999",461 }462 );463 464 // Test case for a simple grammar465 test_grammar(466 "simple grammar",467 R"""(468 root ::= expr469 expr ::= term ("+" term)*470 term ::= number471 number ::= [0-9]+)""",472 // Passing strings473 {474 "42",475 "1+2+3+4+5",476 "123+456",477 },478 // Failing strings479 {480 "+",481 "/ 3",482 "1+2+3+4+5+",483 "12a45",484 }485 );486 487 // Test case for a simple grammar with tokens488 test_grammar(489 "simple grammar with tokens",490 R"""(491 root ::= <[10]> content <[11]>492 content ::= (!<[11]>)*)""",493 // Passing strings494 {495 token(10) + "hello world" + token(11),496 token(10) + "text with " + token(12) + " other tokens " + token(13) + " mixed in" + token(11),497 token(10) + token(11),498 token(10) + token(12) + token(13) + token(14) + token(15) + token(11),499 token(10) + "a" + token(11),500 },501 // Failing strings502 {503 token(10) + "missing end token",504 token(10),505 "missing start token" + token(11),506 token(10) + token(11) + token(11), // double end token507 token(11) + "wrong order" + token(10),508 }509 );510}511 512static void test_complex_grammar() {513 // Test case for a more complex grammar, with both failure strings and success strings514 test_grammar(515 "medium complexity grammar",516 // Grammar517 R"""(518 root ::= expression519 expression ::= term ws (("+"|"-") ws term)*520 term ::= factor ws (("*"|"/") ws factor)*521 factor ::= number | variable | "(" expression ")" | function-call522 number ::= [0-9]+523 variable ::= [a-zA-Z_][a-zA-Z0-9_]*524 function-call ::= variable ws "(" (expression ("," ws expression)*)? ")"525 ws ::= [ \t\n\r]?)""",526 // Passing strings527 {528 "42",529 "1*2*3*4*5",530 "x",531 "x+10",532 "x1+y2",533 "(a+b)*(c-d)",534 "func()",535 "func(x,y+2)",536 "a*(b+c)-d/e",537 "f(g(x),h(y,z))",538 "x + 10",539 "x1 + y2",540 "(a + b) * (c - d)",541 "func()",542 "func(x, y + 2)",543 "a * (b + c) - d / e",544 "f(g(x), h(y, z))",545 "123+456",546 "123*456*789-123/456+789*123",547 "123+456*789-123/456+789*123-456/789+123*456-789/123+456*789-123/456+789*123-456"548 },549 // Failing strings550 {551 "+",552 "/ 3x",553 "x + + y",554 "a * / b",555 "func(,)",556 "func(x y)",557 "(a + b",558 "x + y)",559 "a + b * (c - d",560 "42 +",561 "x +",562 "x + 10 +",563 "(a + b) * (c - d",564 "func(",565 "func(x, y + 2",566 "a * (b + c) - d /",567 "f(g(x), h(y, z)",568 "123+456*789-123/456+789*123-456/789+123*456-789/123+456*789-123/456+789*123-456/",569 }570 );571 572 // Test case for a more complex grammar with tokens573 test_grammar(574 "complex grammar with tokens",575 R"""(576 root ::= reasoning+ content tool-call*577 reasoning ::= <[10]> (!<[11]>)* <[11]>578 content ::= <[20]> (!<[21]>)* <[21]>579 tool-call ::= <[12]> name <[13]> args <[14]>580 name ::= (!<[13]>)+581 args ::= (!<[14]>)*)""",582 // Passing strings583 {584 token(10) + "I am thinking" + token(11) + token(20) + "hello world!" + token(21) + token(12) + "search" + token(13) + "query=test" + token(14),585 token(10) + "reasoning 1" + token(11) + token(10) + "reasoning 2" + token(11) + token(20) + token(21) + token(12) + "tool" + token(13) + token(14),586 token(10) + token(11) + token(20) + "content" + token(21),587 token(10) + "think" + token(12) + " nested" + token(11) + token(20) + token(10) + "more content" + token(21) + token(12) + "fn" + token(13) + "x=1,y=2" + token(14) + token(12) + "fn2" + token(13) + token(14),588 token(10) + "reasoning" + token(11) + token(10) + "more" + token(11) + token(10) + "even more" + token(11) + token(20) + "text" + token(21) + token(12) + "a" + token(13) + "b" + token(14) + token(12) + "c" + token(13) + "d" + token(14),589 },590 // Failing strings591 {592 token(20) + "content only" + token(21),593 token(10) + "no closing reasoning",594 token(10) + token(11) + token(20) + "no closing content",595 token(10) + token(11) + token(20) + token(21) + token(12) + "incomplete tool",596 token(10) + token(11) + token(11) + token(20) + token(21),597 }598 );599}600 601static void test_special_chars() {602 // A collection of tests to exercise special characters such as "."603 test_grammar(604 "special characters",605 // Grammar606 R"""(607 root ::= ... "abc" ...608 )""",609 // Passing strings610 {611 "abcabcabc",612 "aaaabcccc",613 // NOTE: Also ensures that multi-byte characters still count as a single character614 "🔵🟠✅abc❌🟠🔵"615 },616 // Failing strings617 {618 "aaabcccc",619 "aaaaabcccc",620 "aaaabccc",621 "aaaabccccc",622 "🔵🟠✅❌abc❌✅🟠🔵",623 "🔵🟠abc🟠🔵"624 }625 );626}627 628static void test_quantifiers() {629 // A collection of tests to exercise * + and ? quantifiers630 631 test_grammar(632 "* quantifier",633 // Grammar634 R"""(root ::= "a"*)""",635 // Passing strings636 {637 "",638 "a",639 "aaaaa",640 "aaaaaaaaaaaaaaaaaa",641 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"642 },643 // Failing strings644 {645 "b",646 "ab",647 "aab",648 "ba",649 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"650 }651 );652 test_grammar(653 "+ quantifier",654 // Grammar655 R"""(root ::= "a"+)""",656 // Passing strings657 {658 "a",659 "aaaaa",660 "aaaaaaaaaaaaaaaaaa",661 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"662 },663 // Failing strings664 {665 "",666 "b",667 "ab",668 "aab",669 "ba",670 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"671 }672 );673 test_grammar(674 "? quantifier",675 // Grammar676 R"""(root ::= "a"?)""",677 // Passing strings678 {679 "",680 "a"681 },682 // Failing strings683 {684 "b",685 "ab",686 "aa",687 "ba",688 }689 );690 test_grammar(691 "mixed quantifiers",692 // Grammar693 R"""(694 root ::= cons+ vowel* cons? (vowel cons)*695 vowel ::= [aeiouy]696 cons ::= [bcdfghjklmnpqrstvwxyz]697 )""",698 // Passing strings699 {700 "yes",701 "no",702 "noyes",703 "crwth",704 "four",705 "bryyyy",706 },707 // Failing strings708 {709 "yess",710 "yesno",711 "forty",712 "catyyy",713 }714 );715 test_grammar(716 "simple exact repetition",717 // Grammar718 R"""(719 root ::= [ab]{4}720 )""",721 // Passing strings722 {723 "aaaa",724 "bbbb",725 "abab",726 },727 // Failing strings728 {729 "a",730 "b",731 "aaaaa",732 }733 );734 test_grammar(735 "simple min repetition",736 // Grammar737 R"""(738 root ::= [ab]{4,}739 )""",740 // Passing strings741 {742 "aaaa",743 "aaaaab",744 "bbbb",745 "ababab",746 },747 // Failing strings748 {749 "",750 "aba",751 }752 );753 test_grammar(754 "simple max repetition",755 // Grammar756 R"""(757 root ::= [ab]{0,4}758 )""",759 // Passing strings760 {761 "",762 "a",763 "aa",764 "aaa",765 "aaab",766 },767 // Failing strings768 {769 "aaaaa",770 }771 );772 test_grammar(773 "min / max repetition",774 // Grammar775 R"""(776 root ::= ("0x" [A-F0-9]{2} " "?){3,5}777 )""",778 // Passing strings779 {780 "0xFF 0x12 0xAB",781 "0xFF 0x12 0xAB 0x00 0x00",782 },783 // Failing strings784 {785 "",786 "0xFF",787 "0xFF 0x12",788 "0xFF 0x12 0xAB 0x00 0x00 0x00",789 }790 );791 test_grammar(792 "segfault",793 // Grammar794 R"""(795 root ::= ( [x]* )*796 )""",797 // Passing strings798 {799 "",800 "x",801 "xx"802 },803 // Failing strings804 {805 "y",806 "yy"807 }808 );809}810 811static void test_failure_missing_root() {812 fprintf(stderr, "⚫ Testing missing root node:\n");813 // Test case for a grammar that is missing a root rule814 const std::string grammar_str = R"""(815 rot ::= expr816 expr ::= term ("+" term)*817 term ::= number818 number ::= [0-9]+)""";819 820 llama_grammar_parser parsed_grammar;821 parsed_grammar.parse(grammar_str.c_str());822 823 // Ensure we parsed correctly824 assert(!parsed_grammar.rules.empty());825 826 // Ensure we do NOT have a root node827 assert(parsed_grammar.symbol_ids.find("root") == parsed_grammar.symbol_ids.end());828 fprintf(stderr, " ✅︎ Passed\n");829}830 831static void test_failure_missing_reference() {832 fprintf(stderr, "⚫ Testing missing reference node:\n");833 834 // Test case for a grammar that is missing a referenced rule835 const std::string grammar_str =836 R"""(root ::= expr837 expr ::= term ("+" term)*838 term ::= numero839 number ::= [0-9]+)""";840 841 fprintf(stderr, " Expected error: ");842 843 llama_grammar_parser parsed_grammar;844 parsed_grammar.parse(grammar_str.c_str());845 846 // Ensure we did NOT parsed correctly847 assert(parsed_grammar.rules.empty());848 849 fprintf(stderr, " End of expected error.\n");850 fprintf(stderr, " ✅︎ Passed\n");851}852 853static void test_failure_left_recursion() {854 fprintf(stderr, "⚫ Testing left recursion detection:\n");855 856 // Test simple left recursion detection857 const std::string simple_str = R"""(root ::= "a" | root "a")""";858 assert(test_build_grammar_fails(simple_str));859 860 // Test more complicated left recursion detection861 const std::string medium_str = R"""(862 root ::= asdf863 asdf ::= "a" | asdf "a"864 )""";865 assert(test_build_grammar_fails(medium_str));866 867 // Test even more complicated left recursion detection868 const std::string hard_str = R"""(869 root ::= asdf870 asdf ::= "a" | foo "b"871 foo ::= "c" | asdf "d" | "e")""";872 assert(test_build_grammar_fails(hard_str));873 874 // Test yet even more complicated left recursion detection875 const std::string hardest_str = R"""(876 root ::= asdf877 asdf ::= "a" | foo "b"878 foo ::= "c" | empty asdf "d" | "e"879 empty ::= "blah" | )""";880 assert(test_build_grammar_fails(hardest_str));881 882 fprintf(stderr, " ✅︎ Passed\n");883}884 885static void test_failure_missing_root_symbol() {886 fprintf(stderr, "⚫ Testing missing root symbol:\n");887 888 const std::string grammar_str = R"""(889 root ::= "foobar"890 )""";891 892 llama_grammar * failure_result = build_grammar_with_root(grammar_str, "nonexistent");893 assert(failure_result == nullptr);894 895 fprintf(stderr, " ✅︎ Passed\n");896}897 898static void test_custom_root_symbol_check() {899 fprintf(stderr, "⚫ Testing custom root symbol check:\n");900 901 const std::string custom_root_grammar_str = R"""(902 foobar ::= "foobar"903 )""";904 905 llama_grammar * failure_result = build_grammar_with_root(custom_root_grammar_str, "root");906 assert(failure_result == nullptr);907 908 llama_grammar * success_result = build_grammar_with_root(custom_root_grammar_str, "foobar");909 assert(success_result != nullptr);910 llama_grammar_free_impl(success_result);911 912 fprintf(stderr, " ✅︎ Passed\n");913}914 915static void test_json_schema() {916 // Note that this is similar to the regular grammar tests,917 // but we convert each json schema to a grammar before parsing.918 // Otherwise, this test structure is the same.919 920 test_schema(921 "empty schema (any value)",922 // Schema923 R"""(924 {}925 )""",926 // Passing strings927 {928 R"""({})""",929 R"""({"foo": "bar"})""",930 "[]",931 "null",932 R"""("")""",933 "true",934 },935 // Failing strings936 {937 "",938 R"""({"foo"})""",939 "foo",940 }941 );942 943 test_schema(944 "exotic formats (list)",945 // Schema946 R"""({947 "items": [948 { "format": "date" },949 { "format": "uuid" },950 { "format": "time" },951 { "format": "date-time" }952 ]953 })""",954 // Passing strings955 {956 // "{}", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it?957 // "[]", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it?958 R"""(["2012-04-23", "12345678-1234-1234-1234-1234567890ab", "18:25:43.511Z", "2012-04-23T18:25:43.511Z"])""",959 //R"""(["2012-04-23","12345678-1234-1234-1234-1234567890ab"])""", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it?960 //R"""({"foo": "bar"})""", // NOTE: This string passes for this schema on https://www.jsonschemavalidator.net/ -- should it?961 },962 // Failing strings963 {964 R"""(["foo", "bar"])""",965 R"""(["12345678-1234-1234-1234-1234567890ab"])""",966 }967 );968 969 test_schema(970 "string",971 // Schema972 R"""({973 "type": "string"974 })""",975 // Passing strings976 {977 R"""("foo")""",978 R"""("bar")""",979 R"""("")""",980 },981 // Failing strings982 {983 R"""({})""",984 R"""("foo": "bar")""",985 }986 );987 988 test_schema(989 "string w/ min length 1",990 // Schema991 R"""({992 "type": "string",993 "minLength": 1994 })""",995 // Passing strings996 {997 R"""("foo")""",998 R"""("bar")""",999 },1000 // Failing strings1001 {1002 R"""("")""",1003 R"""({})""",1004 R"""("foo": "bar")""",1005 }1006 );1007 1008 test_schema(1009 "string w/ min length 3",1010 // Schema1011 R"""({1012 "type": "string",1013 "minLength": 31014 })""",1015 // Passing strings1016 {1017 R"""("foo")""",1018 R"""("bar")""",1019 R"""("foobar")""",1020 },1021 // Failing strings1022 {1023 R"""("")""",1024 R"""("f")""",1025 R"""("fo")""",1026 }1027 );1028 1029 test_schema(1030 "string w/ max length",1031 // Schema1032 R"""({1033 "type": "string",1034 "maxLength": 31035 })""",1036 // Passing strings1037 {1038 R"""("foo")""",1039 R"""("bar")""",1040 R"""("")""",1041 R"""("f")""",1042 R"""("fo")""",1043 },1044 // Failing strings1045 {1046 R"""("foobar")""",1047 }1048 );1049 1050 test_schema(1051 "string w/ min & max length",1052 // Schema1053 R"""({1054 "type": "string",1055 "minLength": 1,1056 "maxLength": 41057 })""",1058 // Passing strings1059 {1060 R"""("foo")""",1061 R"""("bar")""",1062 R"""("f")""",1063 R"""("barf")""",1064 },1065 // Failing strings1066 {1067 R"""("")""",1068 R"""("barfo")""",1069 R"""("foobar")""",1070 }1071 );1072 1073 test_schema(1074 "boolean",1075 // Schema1076 R"""({1077 "type": "boolean"1078 })""",1079 // Passing strings1080 {1081 "true",1082 "false",1083 },1084 // Failing strings1085 {1086 R"""("")""",1087 R"""("true")""",1088 R"""(True)""",1089 R"""(FALSE)""",1090 }1091 );1092 1093 test_schema(1094 "integer",1095 // Schema1096 R"""({1097 "type": "integer"1098 })""",1099 // Passing strings1100 {1101 R"""(0)""",1102 R"""(12345)""",1103 R"""(1234567890123456)""",1104 },1105 // Failing strings1106 {1107 R"""()""",1108 R"""(01)""",1109 R"""(007)""",1110 R"""(12345678901234567 )""",1111 }1112 );1113 1114 test_schema(1115 "string const",1116 // Schema1117 R"""({1118 "const": "foo"1119 })""",1120 // Passing strings1121 {1122 R"""("foo")""",1123 },1124 // Failing strings1125 {1126 R"""(foo)""",1127 R"""("bar")""",1128 }1129 );1130 1131 test_schema(1132 "non-string const",1133 // Schema1134 R"""({1135 "const": true1136 })""",1137 // Passing strings1138 {1139 R"""(true)""",1140 },1141 // Failing strings1142 {1143 R"""()""",1144 R"""(foo)""",1145 R"""("true")""",1146 }1147 );1148 1149 test_schema(1150 "non-string const",1151 // Schema1152 R"""({1153 "enum": ["red", "amber", "green", null, 42, ["foo"]]1154 })""",1155 // Passing strings1156 {1157 R"""("red")""",1158 R"""(null)""",1159 R"""(42)""",1160 R"""(["foo"])""",1161 },1162 // Failing strings1163 {1164 R"""()""",1165 R"""(420)""",1166 R"""(true)""",1167 R"""(foo)""",1168 }1169 );1170 1171 test_schema(1172 "simple pattern",1173 // Schema1174 R"""({1175 "pattern": "^[a-zA-Z0-9_-]*$"1176 })""",1177 // Passing strings1178 {1179 R"""("")""",1180 R"""("He_llo-12")""",1181 },1182 // Failing strings1183 {1184 R"""("!")""",1185 R"""("Hello World")""",1186 }1187 );1188 1189 test_schema(1190 "pattern with escapes",1191 // Schema1192 R"""({1193 "pattern": "^a\\^\\$\\.\\[\\]\\(\\)\\|\\{\\}\\*\\+\\?b$"1194 })""",1195 // Passing strings1196 {1197 R"""("a^$.[]()|{}*+?b")""",1198 },1199 // Failing strings1200 {