char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
acecd5603
char/aliceai-t5-moe
1#include "json-schema.h" 2#include "json.h" 3#include "testing.h" 4 5#include <cstdlib> 6#include <iostream> 7#include <stdexcept> 8#include <string> 9#include <utility> 10 11static common_chat_schema_document parse (const std::string & schema ) { 12return common_chat_schema_from_json (common_json::parse (schema )); 13} 14 15// the node as T, aborting the current test when it is some other kind 16template < typename T > 17static const T & as (testing & t ,const common_chat_schema * node ,const char * what ) { 18const T * typed = dynamic_cast < const T *> (node ); 19if (!t .assert_true (std::string (what )+ " has the expected kind" ,typed != nullptr )) { 20throw std::runtime_error (std::string (what )+ " has the wrong kind" ); 21 } 22return * typed ; 23} 24 25template < typename T > 26static const T & root (testing & t ,const common_chat_schema_document & doc ) { 27return as < T > (t ,doc .root .get (),"root" ); 28} 29 30static void assert_error (testing & t ,const std::string & schema ,const std::string & needle ) { 31try { 32parse (schema ); 33t .assert_true (schema + " is rejected" , false); 34 }catch (const std::runtime_error & e ) { 35 std::string what = e .what (); 36t .assert_true (schema + " -> " + what ,what .find (needle )!= std::string::npos ); 37 } 38} 39 40static void test_any (testing & t ) { 41t .test ("empty schema" , [](testing & t ) { 42auto doc = parse ("{}" ); 43root < common_chat_schema_any > (t ,doc ); 44t .assert_true ("no refs" ,doc .refs .empty ()); 45 }); 46 47t .test ("keywords that do not imply a type" , [](testing & t ) { 48auto doc = parse (R"({"description": "x", "format": "email", "additionalProperties": true})" ); 49root < common_chat_schema_any > (t ,doc ); 50 }); 51} 52 53static void test_primitives (testing & t ) { 54t .test ("null, boolean, number" , [](testing & t ) { 55auto doc_null = parse (R"({"type": "null"})" ); 56root < common_chat_schema_null > (t ,doc_null ); 57auto doc_bool = parse (R"({"type": "boolean"})" ); 58root < common_chat_schema_boolean > (t ,doc_bool ); 59auto doc_num = parse (R"({"type": "number", "minimum": 1, "maximum": 2})" ); 60root < common_chat_schema_number > (t ,doc_num ); 61 }); 62} 63 64static void test_integer (testing & t ) { 65t .test ("unbounded" , [](testing & t ) { 66auto doc = parse (R"({"type": "integer"})" ); 67const auto & i = root < common_chat_schema_integer > (t ,doc ); 68t .assert_equal ("minimum" ,INT64_MIN ,i .minimum ); 69t .assert_equal ("maximum" ,INT64_MAX ,i .maximum ); 70 }); 71 72t .test ("inclusive bounds" , [](testing & t ) { 73auto doc = parse (R"({"type": "integer", "minimum": -5, "maximum": 10})" ); 74const auto & i = root < common_chat_schema_integer > (t ,doc ); 75t .assert_equal ("minimum" ,-5 ,i .minimum ); 76t .assert_equal ("maximum" ,10 ,i .maximum ); 77 }); 78 79t .test ("exclusive bounds are folded" , [](testing & t ) { 80auto doc = parse (R"({"type": "integer", "exclusiveMinimum": 0, "exclusiveMaximum": 10})" ); 81const auto & i = root < common_chat_schema_integer > (t ,doc ); 82t .assert_equal ("minimum" ,1 ,i .minimum ); 83t .assert_equal ("maximum" ,9 ,i .maximum ); 84 }); 85 86t .test ("fractional bounds round inwards" , [](testing & t ) { 87auto doc = parse (R"({"type": "integer", "minimum": 1.5, "exclusiveMaximum": 9.5})" ); 88const auto & i = root < common_chat_schema_integer > (t ,doc ); 89t .assert_equal ("minimum" ,2 ,i .minimum ); 90t .assert_equal ("maximum" ,9 ,i .maximum ); 91 }); 92} 93 94static void test_string (testing & t ) { 95t .test ("defaults" , [](testing & t ) { 96auto doc = parse (R"({"type": "string"})" ); 97const auto & s = root < common_chat_schema_string > (t ,doc ); 98t .assert_equal ("pattern" ,"" ,s .pattern ); 99t .assert_equal ("format" , common_chat_schema::FORMAT_NONE ,s .format ); 100t .assert_equal ("min_length" ,0 ,s .min_length ); 101t .assert_equal ("max_length" ,-1 ,s .max_length ); 102 }); 103 104t .test ("all keywords are kept" , [](testing & t ) { 105auto doc = parse (R"({"type": "string", "pattern": "^[a-z]+$", "format": "date", "minLength": 2, "maxLength": 8})" ); 106const auto & s = root < common_chat_schema_string > (t ,doc ); 107t .assert_equal ("pattern" ,"^[a-z]+$" ,s .pattern ); 108t .assert_equal ("format" , common_chat_schema::FORMAT_DATE ,s .format ); 109t .assert_equal ("min_length" ,2 ,s .min_length ); 110t .assert_equal ("max_length" ,8 ,s .max_length ); 111 }); 112 113t .test ("formats" , [](testing & t ) { 114auto expect = [& ](const char * format , common_chat_schema::string_format expected ) { 115auto doc = parse (std::string (R"({"type": "string", "format": ")" )+ format + "\"}" ); 116t .assert_equal (format ,expected ,root < common_chat_schema_string > (t ,doc ).format ); 117 }; 118expect ("time" , common_chat_schema::FORMAT_TIME ); 119expect ("date-time" , common_chat_schema::FORMAT_DATE_TIME ); 120expect ("uuid" , common_chat_schema::FORMAT_UUID ); 121expect ("uuid5" , common_chat_schema::FORMAT_UUID ); 122expect ("email" , common_chat_schema::FORMAT_NONE ); 123 }); 124 125t .test ("pattern, length and known format imply a string" , [](testing & t ) { 126auto doc_pattern = parse (R"({"pattern": "^a$"})" ); 127t .assert_equal ("pattern" ,"^a$" ,root < common_chat_schema_string > (t ,doc_pattern ).pattern ); 128auto doc_length = parse (R"({"minLength": 1, "maxLength": 3})" ); 129t .assert_equal ("min_length" ,1 ,root < common_chat_schema_string > (t ,doc_length ).min_length ); 130t .assert_equal ("max_length" ,3 ,root < common_chat_schema_string > (t ,doc_length ).max_length ); 131auto doc_format = parse (R"({"format": "uuid"})" ); 132t .assert_equal ("format" , common_chat_schema::FORMAT_UUID ,root < common_chat_schema_string > (t ,doc_format ).format ); 133 }); 134} 135 136static void test_array (testing & t ) { 137t .test ("items with bounds" , [](testing & t ) { 138auto doc = parse (R"({"type": "array", "items": {"type": "integer"}, "minItems": 1, "maxItems": 3})" ); 139const auto & a = root < common_chat_schema_array > (t ,doc ); 140as < common_chat_schema_integer > (t ,a .items .get (),"items" ); 141t .assert_equal ("min_items" ,1 ,a .min_items ); 142t .assert_equal ("max_items" ,3 ,a .max_items ); 143 }); 144 145t .test ("no items" , [](testing & t ) { 146auto doc = parse (R"({"type": "array"})" ); 147const auto & a = root < common_chat_schema_array > (t ,doc ); 148as < common_chat_schema_any > (t ,a .items .get (),"items" ); 149t .assert_equal ("min_items" ,0 ,a .min_items ); 150t .assert_equal ("max_items" ,-1 ,a .max_items ); 151 }); 152 153t .test ("items imply an array" , [](testing & t ) { 154auto doc = parse (R"({"items": {"type": "string"}})" ); 155const auto & a = root < common_chat_schema_array > (t ,doc ); 156as < common_chat_schema_string > (t ,a .items .get (),"items" ); 157 }); 158} 159 160static void test_tuple (testing & t ) { 161t .test ("prefixItems" , [](testing & t ) { 162auto doc = parse (R"({"prefixItems": [{"type": "string"}, {"type": "number"}]})" ); 163const auto & tup = root < common_chat_schema_tuple > (t ,doc ); 164t .assert_equal ("size" , (size_t )2 ,tup .items .size ()); 165as < common_chat_schema_string > (t ,tup .items [0 ].get (),"items[0]" ); 166as < common_chat_schema_number > (t ,tup .items [1 ].get (),"items[1]" ); 167 }); 168 169t .test ("items as an array" , [](testing & t ) { 170auto doc = parse (R"({"type": "array", "items": [{"type": "boolean"}]})" ); 171const auto & tup = root < common_chat_schema_tuple > (t ,doc ); 172t .assert_equal ("size" , (size_t )1 ,tup .items .size ()); 173as < common_chat_schema_boolean > (t ,tup .items [0 ].get (),"items[0]" ); 174 }); 175} 176 177static void test_object (testing & t ) { 178t .test ("type alone accepts any object" , [](testing & t ) { 179auto doc = parse (R"({"type": "object"})" ); 180const auto & o = root < common_chat_schema_object > (t ,doc ); 181t .assert_true ("no properties" ,o .properties .empty ()); 182as < common_chat_schema_any > (t ,o .additional_properties .get (),"additional_properties" ); 183 }); 184 185t .test ("properties" , [](testing & t ) { 186auto doc = parse (R"({ 187"type": "object", 188"properties": { 189"b": {"type": "string"}, 190"a": {"type": "integer"}, 191"c": {"type": "boolean"} 192}, 193"required": ["a", "c"] 194})" ); 195const auto & o = root < common_chat_schema_object > (t ,doc ); 196t .assert_equal ("size" , (size_t )3 ,o .properties .size ()); 197t .assert_equal ("order" ,"b" ,o .properties [0 ].name ); 198t .assert_equal ("order" ,"a" ,o .properties [1 ].name ); 199t .assert_equal ("order" ,"c" ,o .properties [2 ].name ); 200t .assert_true ("b optional" , !o .properties [0 ].required ); 201t .assert_true ("a required" ,o .properties [1 ].required ); 202t .assert_true ("c required" ,o .properties [2 ].required ); 203as < common_chat_schema_string > (t ,o .properties [0 ].schema .get (),"b" ); 204as < common_chat_schema_integer > (t ,o .properties [1 ].schema .get (),"a" ); 205as < common_chat_schema_boolean > (t ,o .properties [2 ].schema .get (),"c" ); 206t .assert_true ("closed" ,o .additional_properties == nullptr ); 207 }); 208 209t .test ("unknown required entries are ignored" , [](testing & t ) { 210auto doc = parse (R"({"properties": {"a": {}}, "required": ["a", "zzz", 1]})" ); 211const auto & o = root < common_chat_schema_object > (t ,doc ); 212t .assert_equal ("size" , (size_t )1 ,o .properties .size ()); 213t .assert_true ("a required" ,o .properties [0 ].required ); 214 }); 215 216t .test ("additionalProperties false implies an object" , [](testing & t ) { 217auto doc = parse (R"({"additionalProperties": false})" ); 218const auto & o = root < common_chat_schema_object > (t ,doc ); 219t .assert_true ("no properties" ,o .properties .empty ()); 220t .assert_true ("closed" ,o .additional_properties == nullptr ); 221 }); 222 223t .test ("additionalProperties schema" , [](testing & t ) { 224auto doc = parse (R"({"properties": {"a": {}}, "additionalProperties": {"type": "integer", "minimum": 0}})" ); 225const auto & o = root < common_chat_schema_object > (t ,doc ); 226t .assert_equal ("size" , (size_t )1 ,o .properties .size ()); 227const auto & v = as < common_chat_schema_integer > (t ,o .additional_properties .get (),"additional_properties" ); 228t .assert_equal ("minimum" ,0 ,v .minimum ); 229 }); 230 231t .test ("nested" , [](testing & t ) { 232auto doc = parse (R"({"properties": {"inner": {"properties": {"leaf": {"type": "null"}}, "required": ["leaf"]}}})" ); 233const auto & o = root < common_chat_schema_object > (t ,doc ); 234const auto & inner = as < common_chat_schema_object > (t ,o .properties [0 ].schema .get (),"inner" ); 235t .assert_equal ("leaf name" ,"leaf" ,inner .properties [0 ].name ); 236t .assert_true ("leaf required" ,inner .properties [0 ].required ); 237as < common_chat_schema_null > (t ,inner .properties [0 ].schema .get (),"leaf" ); 238 }); 239} 240 241static void test_const_enum (testing & t ) { 242t .test ("const" , [](testing & t ) { 243auto doc = parse (R"({"const": {"a": [1, null]}})" ); 244t .assert_equal ("value" ,R"({"a":[1,null]})" ,root < common_chat_schema_const > (t ,doc ).value .dump ()); 245 }); 246 247t .test ("enum" , [](testing & t ) { 248auto doc = parse (R"({"enum": ["a", 1, null, true]})" ); 249const auto & e = root < common_chat_schema_enum > (t ,doc ); 250t .assert_equal ("size" , (size_t )4 ,e .values .size ()); 251t .assert_equal ("values[0]" ,"\"a\"" ,e .values [0 ].dump ()); 252t .assert_equal ("values[1]" ,"1" ,e .values [1 ].dump ()); 253t .assert_equal ("values[2]" ,"null" ,e .values [2 ].dump ()); 254t .assert_equal ("values[3]" ,"true" ,e .values [3 ].dump ()); 255 }); 256 257t .test ("const wins over enum, enum wins over type" , [](testing & t ) { 258auto doc_enum = parse (R"({"type": "integer", "enum": [1, 2]})" ); 259root < common_chat_schema_enum > (t ,doc_enum ); 260auto doc_const = parse (R"({"type": "string", "const": "x", "enum": ["y"]})" ); 261t .assert_equal ("value" ,"\"x\"" ,root < common_chat_schema_const > (t ,doc_const ).value .dump ()); 262 }); 263} 264 265static void test_any_of (testing & t ) { 266t .test ("anyOf and oneOf" , [](testing & t ) { 267auto doc_any = parse (R"({"anyOf": [{"type": "string"}, {"type": "number"}]})" ); 268const auto & u = root < common_chat_schema_any_of > (t ,doc_any ); 269t .assert_equal ("size" , (size_t )2 ,u .children .size ()); 270as < common_chat_schema_string > (t ,u .children [0 ].get (),"children[0]" ); 271as < common_chat_schema_number > (t ,u .children [1 ].get (),"children[1]" ); 272 273auto doc_one = parse (R"({"oneOf": [{"type": "null"}]})" ); 274const auto & o = root < common_chat_schema_any_of > (t ,doc_one ); 275t .assert_equal ("size" , (size_t )1 ,o .children .size ()); 276as < common_chat_schema_null > (t ,o .children [0 ].get (),"children[0]" ); 277 }); 278 279t .test ("oneOf wins over anyOf and type" , [](testing & t ) { 280auto doc = parse (R"({"type": "string", "oneOf": [{"type": "null"}], "anyOf": [{"type": "number"}, {"type": "boolean"}]})" ); 281const auto & u = root < common_chat_schema_any_of > (t ,doc ); 282t .assert_equal ("size" , (size_t )1 ,u .children .size ()); 283as < common_chat_schema_null > (t ,u .children [0 ].get (),"children[0]" ); 284 }); 285 286t .test ("type array expands with sibling keywords" , [](testing & t ) { 287auto doc = parse (R"({"type": ["string", "null", "integer"], "minLength": 2, "minimum": 5})" ); 288const auto & u = root < common_chat_schema_any_of > (t ,doc ); 289t .assert_equal ("size" , (size_t )3 ,u .children .size ()); 290t .assert_equal ("min_length" ,2 ,as < common_chat_schema_string > (t ,u .children [0 ].get (),"children[0]" ).min_length ); 291as < common_chat_schema_null > (t ,u .children [1 ].get (),"children[1]" ); 292t .assert_equal ("minimum" ,5 ,as < common_chat_schema_integer > (t ,u .children [2 ].get (),"children[2]" ).minimum ); 293 }); 294} 295 296static void test_all_of (testing & t ) { 297t .test ("components" , [](testing & t ) { 298auto doc = parse (R"({"allOf": [{"properties": {"a": {}}}, {"anyOf": [{"properties": {"b": {}}}, {"type": "null"}]}]})" ); 299const auto & all = root < common_chat_schema_all_of > (t ,doc ); 300t .assert_equal ("size" , (size_t )2 ,all .children .size ()); 301as < common_chat_schema_object > (t ,all .children [0 ].get (),"children[0]" ); 302as < common_chat_schema_any_of > (t ,all .children [1 ].get (),"children[1]" ); 303 304auto doc_typed = parse (R"({"type": "object", "allOf": [{"properties": {"a": {}}}]})" ); 305root < common_chat_schema_all_of > (t ,doc_typed ); 306 }); 307 308t .test ("properties win over allOf" , [](testing & t ) { 309auto doc = parse (R"({"type": "object", "properties": {"a": {}}, "allOf": [{"properties": {"b": {}}}]})" ); 310t .assert_equal ("size" , (size_t )1 ,root < common_chat_schema_object > (t ,doc ).properties .size ()); 311 }); 312 313t .test ("other types ignore allOf" , [](testing & t ) { 314auto doc = parse (R"({"type": "integer", "allOf": [{"minimum": 1}]})" ); 315root < common_chat_schema_integer > (t ,doc ); 316 }); 317} 318 319static void test_ref (testing & t ) { 320t .test ("target is owned by the document" , [](testing & t ) { 321auto doc = parse (R"({"$ref": "#/$defs/t", "type": "string", "$defs": {"t": {"type": "boolean"}}})" ); 322const auto & r = root < common_chat_schema_ref > (t ,doc ); 323t .assert_equal ("ref" ,"#/$defs/t" ,r .ref ); 324t .assert_equal ("refs" , (size_t )1 ,doc .refs .size ()); 325t .assert_true ("target" ,r .target != nullptr && r .target == doc .refs .at ("#/$defs/t" ).get ()); 326as < common_chat_schema_boolean > (t ,r .target ,"target" ); 327 }); 328 329t .test ("definitions" , [](testing & t ) { 330auto doc = parse (R"({"properties": {"a": {"$ref": "#/definitions/t"}}, "definitions": {"t": {"type": "number"}}})" ); 331const auto & o = root < common_chat_schema_object > (t ,doc ); 332const auto & r = as < common_chat_schema_ref > (t ,o .properties [0 ].schema .get (),"a" ); 333as < common_chat_schema_number > (t ,r .target ,"target" ); 334 }); 335 336t .test ("recursive" , [](testing & t ) { 337auto doc = parse (R"({ 338"$ref": "#/$defs/node", 339"$defs": { 340"node": { 341"type": "object", 342"properties": { 343"value": {"type": "number"}, 344"next": {"$ref": "#/$defs/node"} 345}, 346"required": ["value"] 347} 348} 349})" ); 350const auto & r = root < common_chat_schema_ref > (t ,doc ); 351const auto & node = as < common_chat_schema_object > (t ,r .target ,"node" ); 352t .assert_equal ("properties" , (size_t )2 ,node .properties .size ()); 353const auto & next = as < common_chat_schema_ref > (t ,node .properties [1 ].schema .get (),"next" ); 354t .assert_true ("cycle" ,next .target == r .target ); 355t .assert_equal ("refs" , (size_t )1 ,doc .refs .size ()); 356 }); 357 358t .test ("pointer through an array" , [](testing & t ) { 359auto doc = parse (R"({"oneOf": [{"type": "null"}, {"$ref": "#/oneOf/0"}]})" ); 360const auto & u = root < common_chat_schema_any_of > (t ,doc ); 361const auto & r = as < common_chat_schema_ref > (t ,u .children [1 ].get (),"children[1]" ); 362as < common_chat_schema_null > (t ,r .target ,"target" ); 363 }); 364 365t .test ("targets survive moving the document" , [](testing & t ) { 366auto parsed = parse (R"({"items": {"$ref": "#/$defs/t"}, "$defs": {"t": {"type": "null"}}})" ); 367common_chat_schema_document doc = std::move (parsed ); 368const auto & a = root < common_chat_schema_array > (t ,doc ); 369const auto & r = as < common_chat_schema_ref > (t ,a .items .get (),"items" ); 370t .assert_true ("target" ,r .target == doc .refs .at ("#/$defs/t" ).get ()); 371as < common_chat_schema_null > (t ,r .target ,"target" ); 372 }); 373} 374 375static void test_may_be_string (testing & t ) { 376auto check = [](testing & t ,const std::string & schema ,bool expected ) { 377t .assert_equal (schema ,expected ,parse (schema ).root -> may_be_string ()); 378 }; 379 380t .test ("leaves" , [& ](testing & t ) { 381check (t ,R"({"type": "string"})" , true); 382check (t ,R"({"type": "integer"})" , false); 383check (t ,R"({"minLength": 1})" , true); 384check (t ,R"({"pattern": "^[a-z]+$"})" , true); 385check (t ,R"({"const": "hello"})" , true); 386check (t ,R"({"const": 123})" , false); 387check (t ,R"({"enum": [1, "a", null]})" , true); 388check (t ,R"({"enum": [1, 2, 3]})" , false); 389 }); 390 391t .test ("composites" , [& ](testing & t ) { 392check (t ,R"({"type": ["integer", "string"]})" , true); 393check (t ,R"({"anyOf": [{"type": "integer"}, {"type": "boolean"}]})" , false); 394check (t ,R"({"allOf": [{"type": "string"}, {"minLength": 1}]})" , true); 395check (t ,R"({"allOf": [{"type": "string"}, {"type": "integer"}]})" , false); 396check (t ,R"({"allOf": [{"minLength": 1}, {"maxLength": 2}]})" , true); 397 }); 398 399t .test ("ref" , [& ](testing & t ) { 400check (t ,R"({"$ref": "#/$defs/n", "$defs": {"n": {"anyOf": [{"$ref": "#/$defs/n"}, {"type": "string"}]}}})" , true); 401check (t ,R"({"$ref": "#/$defs/n", "$defs": {"n": {"$ref": "#/$defs/n"}}})" , false); 402check (t ,R"({"anyOf": [{"$ref": "#/$defs/a"}, {"$ref": "#/$defs/b"}], "$defs": {"a": {"allOf": [{"$ref": "#/$defs/b"}, {"type": "integer"}]}, "b": {"type": "string"}}})" , true); 403 }); 404} 405 406// e.g. {number, integer}, in type order 407static std::string dump (const common_chat_schema::type_set & types ) { 408static const common_chat_schema::value_type order []= { common_chat_schema::TYPE_NULL , common_chat_schema::TYPE_BOOLEAN , common_chat_schema::TYPE_NUMBER , 409 common_chat_schema::TYPE_INTEGER , common_chat_schema::TYPE_STRING , common_chat_schema::TYPE_ARRAY , 410 common_chat_schema::TYPE_OBJECT }; 411 std::string out ; 412for (auto type :order ) { 413if (types .has (type )) { 414out += (out .empty () ?"" :", " )+ std::string (common_chat_schema::type_name (type )); 415 } 416 } 417return "{" + out + "}" ; 418} 419 420static void test_value_types (testing & t ) { 421auto check = [](testing & t ,const std::string & schema ,const common_chat_schema::type_set & expected ) { 422t .assert_equal (schema ,dump (expected ),dump (parse (schema ).root -> value_types ())); 423 }; 424 425t .test ("leaves" , [& ](testing & t ) { 426check (t ,R"({"type": "string"})" , { common_chat_schema::TYPE_STRING }); 427check (t ,R"({"type": "number"})" , { common_chat_schema::TYPE_NUMBER , common_chat_schema::TYPE_INTEGER }); 428check (t ,R"({"description": "x"})" , common_chat_schema::type_set::all ()); 429check (t ,R"({"properties": {"a": {"type": "string"}}})" , { common_chat_schema::TYPE_OBJECT }); 430check (t ,R"({"items": {"type": "string"}})" , { common_chat_schema::TYPE_ARRAY }); 431check (t ,R"({"const": 1.5})" , { common_chat_schema::TYPE_NUMBER }); 432check (t ,R"({"enum": [1, "a", null]})" , { common_chat_schema::TYPE_INTEGER , common_chat_schema::TYPE_STRING , common_chat_schema::TYPE_NULL }); 433 }); 434 435t .test ("any_of is the union, all_of is the intersection" , [& ](testing & t ) { 436check (t ,R"({"type": ["string", "null"]})" , { common_chat_schema::TYPE_STRING , common_chat_schema::TYPE_NULL }); 437check (t ,R"({"allOf": [{"type": ["string", "number"]}, {"type": ["number", "object"]}]})" , { common_chat_schema::TYPE_NUMBER , common_chat_schema::TYPE_INTEGER }); 438check (t ,R"({"allOf": [{"type": "string"}, {"type": "integer"}]})" , {}); 439 }); 440 441t .test ("ref" , [& ](testing & t ) { 442check (t ,R"({"$ref": "#/$defs/n", "$defs": {"n": {"anyOf": [{"$ref": "#/$defs/n"}, {"type": "string"}]}}})" , 443 { common_chat_schema::TYPE_STRING }); 444 }); 445} 446 447static void test_errors (testing & t ) { 448t .test ("not a schema" , [](testing & t ) { 449assert_error (t ,R"([])" ,"#: schema must be an object" ); 450 }); 451 452t .test ("type" , [](testing & t ) { 453assert_error (t ,R"({"type": 5})" ,"#: type must be a string or an array of strings" ); 454assert_error (t ,R"({"type": []})" ,"#: type must not be empty" ); 455assert_error (t ,R"({"type": ["string", "bad"]})" ,"#/type/1: unrecognized type bad" ); 456 }); 457 458t .test ("ref" , [](testing & t ) { 459assert_error (t ,R"({"$ref": 5})" ,"#: $ref must be a string" ); 460assert_error (t ,R"({"$ref": "https://example.com/x.json"})" ,"#: unsupported $ref https://example.com/x.json" ); 461assert_error (t ,R"({"$ref": ""})" ,"#: unsupported $ref ," ); 462assert_error (t ,R"({"$ref": "#"})" ,"#: unsupported $ref #," ); 463assert_error (t ,R"({"$defs": {}, "$ref": "#/$defs/missing"})" ,"#: cannot resolve $ref #/$defs/missing, missing not found" ); 464assert_error (t ,R"({"oneOf": [{}], "$ref": "#/oneOf/1"})" ,"#: cannot resolve $ref #/oneOf/1, 1 is out of range" ); 465assert_error (t ,R"({"$defs": {"a": {"$ref": "#/$defs/a/nope"}}, "$ref": "#/$defs/a"})" ,"#/$defs/a: cannot resolve $ref #/$defs/a/nope, nope not found" ); 466 }); 467 468t .test ("alternatives" , [](testing & t ) { 469assert_error (t ,R"({"oneOf": []})" ,"#/oneOf: must not be empty" ); 470assert_error (t ,R"({"anyOf": {}})" ,"#/anyOf: must be an array of schemas" ); 471assert_error (t ,R"({"anyOf": [{"type": "string"}, {"items": {"type": "x"}}]})" ,"#/anyOf/1/items: unrecognized type x" ); 472 }); 473 474t .test ("keywords" , [](testing & t ) { 475assert_error (t ,R"({"enum": []})" ,"#: enum must be a non-empty array" ); 476assert_error (t ,R"({"type": "string", "pattern": 5})" ,"#: pattern must be a string" ); 477assert_error (t ,R"({"type": "string", "minLength": -1})" ,"#: minLength must be a non-negative integer" ); 478assert_error (t ,R"({"type": "integer", "minimum": "1"})" ,"#: minimum must be a number" ); 479assert_error (t ,R"({"type": "array", "maxItems": 1.5})" ,"#: maxItems must be a non-negative integer" ); 480assert_error (t ,R"({"properties": []})" ,"#: properties must be an object" ); 481assert_error (t ,R"({"properties": {"a": {"type": "nope"}}})" ,"#/properties/a: unrecognized type nope" ); 482assert_error (t ,R"({"additionalProperties": null})" ,"#: additionalProperties must be a boolean or a schema" ); 483 }); 484} 485 486int main (int argc ,char * argv []) { 487testing t (std::cout ); 488if (argc >=2 ) { 489t .set_filter (argv [1 ]); 490 } 491 492const char * verbose = getenv ("LLAMA_TEST_VERBOSE" ); 493if (verbose ) { 494t .verbose = std::string (verbose )== "1" ; 495 } 496 497t .test ("any" ,test_any ); 498t .test ("primitives" ,test_primitives ); 499t .test ("integer" ,test_integer ); 500t .test ("string" ,test_string ); 501t .test ("array" ,test_array ); 502t .test ("tuple" ,test_tuple ); 503t .test ("object" ,test_object ); 504t .test ("const and enum" ,test_const_enum ); 505t .test ("any_of" ,test_any_of ); 506t .test ("all_of" ,test_all_of ); 507t .test ("ref" ,test_ref ); 508t .test ("may_be_string" ,test_may_be_string ); 509t .test ("value_types" ,test_value_types ); 510t .test ("errors" ,test_errors ); 511 512return t .summary (); 513}