ucl_schema.c revision 263646
1/* 2 * Copyright (c) 2014, Vsevolod Stakhov 3 * 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY AUTHOR ''AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 * DISCLAIMED. IN NO EVENT SHALL AUTHOR BE LIABLE FOR ANY 18 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 19 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 21 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 23 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26#include "ucl.h" 27#include "ucl_internal.h" 28#include "tree.h" 29#include "utlist.h" 30#ifdef HAVE_STDARG_H 31#include <stdarg.h> 32#endif 33#ifdef HAVE_STDIO_H 34#include <stdio.h> 35#endif 36#ifdef HAVE_REGEX_H 37#include <regex.h> 38#endif 39#ifdef HAVE_MATH_H 40#include <math.h> 41#endif 42 43static bool ucl_schema_validate (ucl_object_t *schema, 44 ucl_object_t *obj, bool try_array, 45 struct ucl_schema_error *err, 46 ucl_object_t *root); 47 48static bool 49ucl_string_to_type (const char *input, ucl_type_t *res) 50{ 51 if (strcasecmp (input, "object") == 0) { 52 *res = UCL_OBJECT; 53 } 54 else if (strcasecmp (input, "array") == 0) { 55 *res = UCL_ARRAY; 56 } 57 else if (strcasecmp (input, "integer") == 0) { 58 *res = UCL_INT; 59 } 60 else if (strcasecmp (input, "number") == 0) { 61 *res = UCL_FLOAT; 62 } 63 else if (strcasecmp (input, "string") == 0) { 64 *res = UCL_STRING; 65 } 66 else if (strcasecmp (input, "boolean") == 0) { 67 *res = UCL_BOOLEAN; 68 } 69 else if (strcasecmp (input, "null") == 0) { 70 *res = UCL_NULL; 71 } 72 else { 73 return false; 74 } 75 76 return true; 77} 78 79static const char * 80ucl_object_type_to_string (ucl_type_t type) 81{ 82 const char *res = "unknown"; 83 84 switch (type) { 85 case UCL_OBJECT: 86 res = "object"; 87 break; 88 case UCL_ARRAY: 89 res = "array"; 90 break; 91 case UCL_INT: 92 res = "integer"; 93 break; 94 case UCL_FLOAT: 95 case UCL_TIME: 96 res = "number"; 97 break; 98 case UCL_STRING: 99 res = "string"; 100 break; 101 case UCL_BOOLEAN: 102 res = "boolean"; 103 break; 104 case UCL_NULL: 105 case UCL_USERDATA: 106 res = "null"; 107 break; 108 } 109 110 return res; 111} 112 113/* 114 * Create validation error 115 */ 116static void 117ucl_schema_create_error (struct ucl_schema_error *err, 118 enum ucl_schema_error_code code, ucl_object_t *obj, 119 const char *fmt, ...) 120{ 121 va_list va; 122 123 if (err != NULL) { 124 err->code = code; 125 err->obj = obj; 126 va_start (va, fmt); 127 vsnprintf (err->msg, sizeof (err->msg), fmt, va); 128 va_end (va); 129 } 130} 131 132/* 133 * Check whether we have a pattern specified 134 */ 135static ucl_object_t * 136ucl_schema_test_pattern (ucl_object_t *obj, const char *pattern) 137{ 138 regex_t reg; 139 ucl_object_t *res = NULL, *elt; 140 ucl_object_iter_t iter = NULL; 141 142 if (regcomp (®, pattern, REG_EXTENDED | REG_NOSUB) == 0) { 143 while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) { 144 if (regexec (®, ucl_object_key (elt), 0, NULL, 0) == 0) { 145 res = elt; 146 break; 147 } 148 } 149 regfree (®); 150 } 151 152 return res; 153} 154 155/* 156 * Check dependencies for an object 157 */ 158static bool 159ucl_schema_validate_dependencies (ucl_object_t *deps, 160 ucl_object_t *obj, struct ucl_schema_error *err, 161 ucl_object_t *root) 162{ 163 ucl_object_t *elt, *cur, *cur_dep; 164 ucl_object_iter_t iter = NULL, piter; 165 bool ret = true; 166 167 while (ret && (cur = ucl_iterate_object (deps, &iter, true)) != NULL) { 168 elt = ucl_object_find_key (obj, ucl_object_key (cur)); 169 if (elt != NULL) { 170 /* Need to check dependencies */ 171 if (cur->type == UCL_ARRAY) { 172 piter = NULL; 173 while (ret && (cur_dep = ucl_iterate_object (cur, &piter, true)) != NULL) { 174 if (ucl_object_find_key (obj, ucl_object_tostring (cur_dep)) == NULL) { 175 ucl_schema_create_error (err, UCL_SCHEMA_MISSING_DEPENDENCY, elt, 176 "dependency %s is missing for key %s", 177 ucl_object_tostring (cur_dep), ucl_object_key (cur)); 178 ret = false; 179 break; 180 } 181 } 182 } 183 else if (cur->type == UCL_OBJECT) { 184 ret = ucl_schema_validate (cur, obj, true, err, root); 185 } 186 } 187 } 188 189 return ret; 190} 191 192/* 193 * Validate object 194 */ 195static bool 196ucl_schema_validate_object (ucl_object_t *schema, 197 ucl_object_t *obj, struct ucl_schema_error *err, 198 ucl_object_t *root) 199{ 200 ucl_object_t *elt, *prop, *found, *additional_schema = NULL, 201 *required = NULL, *pat, *pelt; 202 ucl_object_iter_t iter = NULL, piter = NULL; 203 bool ret = true, allow_additional = true; 204 int64_t minmax; 205 206 while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { 207 if (elt->type == UCL_OBJECT && 208 strcmp (ucl_object_key (elt), "properties") == 0) { 209 piter = NULL; 210 while (ret && (prop = ucl_iterate_object (elt, &piter, true)) != NULL) { 211 found = ucl_object_find_key (obj, ucl_object_key (prop)); 212 if (found) { 213 ret = ucl_schema_validate (prop, found, true, err, root); 214 } 215 } 216 } 217 else if (strcmp (ucl_object_key (elt), "additionalProperties") == 0) { 218 if (elt->type == UCL_BOOLEAN) { 219 if (!ucl_object_toboolean (elt)) { 220 /* Deny additional fields completely */ 221 allow_additional = false; 222 } 223 } 224 else if (elt->type == UCL_OBJECT) { 225 /* Define validator for additional fields */ 226 additional_schema = elt; 227 } 228 else { 229 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, 230 "additionalProperties attribute is invalid in schema"); 231 ret = false; 232 break; 233 } 234 } 235 else if (strcmp (ucl_object_key (elt), "required") == 0) { 236 if (elt->type == UCL_ARRAY) { 237 required = elt; 238 } 239 else { 240 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, 241 "required attribute is invalid in schema"); 242 ret = false; 243 break; 244 } 245 } 246 else if (strcmp (ucl_object_key (elt), "minProperties") == 0 247 && ucl_object_toint_safe (elt, &minmax)) { 248 if (obj->len < minmax) { 249 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 250 "object has not enough properties: %u, minimum is: %u", 251 obj->len, (unsigned)minmax); 252 ret = false; 253 break; 254 } 255 } 256 else if (strcmp (ucl_object_key (elt), "maxProperties") == 0 257 && ucl_object_toint_safe (elt, &minmax)) { 258 if (obj->len > minmax) { 259 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 260 "object has too many properties: %u, maximum is: %u", 261 obj->len, (unsigned)minmax); 262 ret = false; 263 break; 264 } 265 } 266 else if (strcmp (ucl_object_key (elt), "patternProperties") == 0) { 267 piter = NULL; 268 while (ret && (prop = ucl_iterate_object (elt, &piter, true)) != NULL) { 269 found = ucl_schema_test_pattern (obj, ucl_object_key (prop)); 270 if (found) { 271 ret = ucl_schema_validate (prop, found, true, err, root); 272 } 273 } 274 } 275 else if (elt->type == UCL_OBJECT && 276 strcmp (ucl_object_key (elt), "dependencies") == 0) { 277 ret = ucl_schema_validate_dependencies (elt, obj, err, root); 278 } 279 } 280 281 if (ret) { 282 /* Additional properties */ 283 if (!allow_additional || additional_schema != NULL) { 284 /* Check if we have exactly the same properties in schema and object */ 285 iter = NULL; 286 prop = ucl_object_find_key (schema, "properties"); 287 while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) { 288 found = ucl_object_find_key (prop, ucl_object_key (elt)); 289 if (found == NULL) { 290 /* Try patternProperties */ 291 piter = NULL; 292 pat = ucl_object_find_key (schema, "patternProperties"); 293 while ((pelt = ucl_iterate_object (pat, &piter, true)) != NULL) { 294 found = ucl_schema_test_pattern (obj, ucl_object_key (pelt)); 295 if (found != NULL) { 296 break; 297 } 298 } 299 } 300 if (found == NULL) { 301 if (!allow_additional) { 302 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 303 "object has non-allowed property %s", 304 ucl_object_key (elt)); 305 ret = false; 306 break; 307 } 308 else if (additional_schema != NULL) { 309 if (!ucl_schema_validate (additional_schema, elt, true, err, root)) { 310 ret = false; 311 break; 312 } 313 } 314 } 315 } 316 } 317 /* Required properties */ 318 if (required != NULL) { 319 iter = NULL; 320 while ((elt = ucl_iterate_object (required, &iter, true)) != NULL) { 321 if (ucl_object_find_key (obj, ucl_object_tostring (elt)) == NULL) { 322 ucl_schema_create_error (err, UCL_SCHEMA_MISSING_PROPERTY, obj, 323 "object has missing property %s", 324 ucl_object_tostring (elt)); 325 ret = false; 326 break; 327 } 328 } 329 } 330 } 331 332 333 return ret; 334} 335 336static bool 337ucl_schema_validate_number (ucl_object_t *schema, 338 ucl_object_t *obj, struct ucl_schema_error *err) 339{ 340 ucl_object_t *elt, *test; 341 ucl_object_iter_t iter = NULL; 342 bool ret = true, exclusive = false; 343 double constraint, val; 344 const double alpha = 1e-16; 345 346 while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { 347 if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && 348 strcmp (ucl_object_key (elt), "multipleOf") == 0) { 349 constraint = ucl_object_todouble (elt); 350 if (constraint <= 0) { 351 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, 352 "multipleOf must be greater than zero"); 353 ret = false; 354 break; 355 } 356 val = ucl_object_todouble (obj); 357 if (fabs (remainder (val, constraint)) > alpha) { 358 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 359 "number %.4f is not multiple of %.4f, remainder is %.7f", 360 val, constraint); 361 ret = false; 362 break; 363 } 364 } 365 else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && 366 strcmp (ucl_object_key (elt), "maximum") == 0) { 367 constraint = ucl_object_todouble (elt); 368 test = ucl_object_find_key (schema, "exclusiveMaximum"); 369 if (test && test->type == UCL_BOOLEAN) { 370 exclusive = ucl_object_toboolean (test); 371 } 372 val = ucl_object_todouble (obj); 373 if (val > constraint || (exclusive && val >= constraint)) { 374 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 375 "number is too big: %.3f, maximum is: %.3f", 376 val, constraint); 377 ret = false; 378 break; 379 } 380 } 381 else if ((elt->type == UCL_FLOAT || elt->type == UCL_INT) && 382 strcmp (ucl_object_key (elt), "minimum") == 0) { 383 constraint = ucl_object_todouble (elt); 384 test = ucl_object_find_key (schema, "exclusiveMinimum"); 385 if (test && test->type == UCL_BOOLEAN) { 386 exclusive = ucl_object_toboolean (test); 387 } 388 val = ucl_object_todouble (obj); 389 if (val < constraint || (exclusive && val <= constraint)) { 390 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 391 "number is too small: %.3f, minimum is: %.3f", 392 val, constraint); 393 ret = false; 394 break; 395 } 396 } 397 } 398 399 return ret; 400} 401 402static bool 403ucl_schema_validate_string (ucl_object_t *schema, 404 ucl_object_t *obj, struct ucl_schema_error *err) 405{ 406 ucl_object_t *elt; 407 ucl_object_iter_t iter = NULL; 408 bool ret = true; 409 int64_t constraint; 410 regex_t re; 411 412 while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { 413 if (elt->type == UCL_INT && 414 strcmp (ucl_object_key (elt), "maxLength") == 0) { 415 constraint = ucl_object_toint (elt); 416 if (obj->len > constraint) { 417 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 418 "string is too big: %.3f, maximum is: %.3f", 419 obj->len, constraint); 420 ret = false; 421 break; 422 } 423 } 424 else if (elt->type == UCL_INT && 425 strcmp (ucl_object_key (elt), "minLength") == 0) { 426 constraint = ucl_object_toint (elt); 427 if (obj->len < constraint) { 428 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 429 "string is too short: %.3f, minimum is: %.3f", 430 obj->len, constraint); 431 ret = false; 432 break; 433 } 434 } 435 else if (elt->type == UCL_STRING && 436 strcmp (ucl_object_key (elt), "pattern") == 0) { 437 if (regcomp (&re, ucl_object_tostring (elt), 438 REG_EXTENDED | REG_NOSUB) != 0) { 439 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, 440 "cannot compile pattern %s", ucl_object_tostring (elt)); 441 ret = false; 442 break; 443 } 444 if (regexec (&re, ucl_object_tostring (obj), 0, NULL, 0) != 0) { 445 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 446 "string doesn't match regexp %s", 447 ucl_object_tostring (elt)); 448 ret = false; 449 } 450 regfree (&re); 451 } 452 } 453 454 return ret; 455} 456 457struct ucl_compare_node { 458 ucl_object_t *obj; 459 TREE_ENTRY(ucl_compare_node) link; 460 struct ucl_compare_node *next; 461}; 462 463typedef TREE_HEAD(_tree, ucl_compare_node) ucl_compare_tree_t; 464 465TREE_DEFINE(ucl_compare_node, link) 466 467static int 468ucl_schema_elt_compare (struct ucl_compare_node *n1, struct ucl_compare_node *n2) 469{ 470 ucl_object_t *o1 = n1->obj, *o2 = n2->obj; 471 472 return ucl_object_compare (o1, o2); 473} 474 475static bool 476ucl_schema_array_is_unique (ucl_object_t *obj, struct ucl_schema_error *err) 477{ 478 ucl_compare_tree_t tree = TREE_INITIALIZER (ucl_schema_elt_compare); 479 ucl_object_iter_t iter = NULL; 480 ucl_object_t *elt; 481 struct ucl_compare_node *node, test, *nodes = NULL, *tmp; 482 bool ret = true; 483 484 while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) { 485 test.obj = elt; 486 node = TREE_FIND (&tree, ucl_compare_node, link, &test); 487 if (node != NULL) { 488 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, elt, 489 "duplicate values detected while uniqueItems is true"); 490 ret = false; 491 break; 492 } 493 node = calloc (1, sizeof (*node)); 494 if (node == NULL) { 495 ucl_schema_create_error (err, UCL_SCHEMA_UNKNOWN, elt, 496 "cannot allocate tree node"); 497 ret = false; 498 break; 499 } 500 node->obj = elt; 501 TREE_INSERT (&tree, ucl_compare_node, link, node); 502 LL_PREPEND (nodes, node); 503 } 504 505 LL_FOREACH_SAFE (nodes, node, tmp) { 506 free (node); 507 } 508 509 return ret; 510} 511 512static bool 513ucl_schema_validate_array (ucl_object_t *schema, 514 ucl_object_t *obj, struct ucl_schema_error *err, 515 ucl_object_t *root) 516{ 517 ucl_object_t *elt, *it, *found, *additional_schema = NULL, 518 *first_unvalidated = NULL; 519 ucl_object_iter_t iter = NULL, piter = NULL; 520 bool ret = true, allow_additional = true, need_unique = false; 521 int64_t minmax; 522 523 while (ret && (elt = ucl_iterate_object (schema, &iter, true)) != NULL) { 524 if (strcmp (ucl_object_key (elt), "items") == 0) { 525 if (elt->type == UCL_ARRAY) { 526 found = obj->value.av; 527 while (ret && (it = ucl_iterate_object (elt, &piter, true)) != NULL) { 528 if (found) { 529 ret = ucl_schema_validate (it, found, false, err, root); 530 found = found->next; 531 } 532 } 533 if (found != NULL) { 534 /* The first element that is not validated */ 535 first_unvalidated = found; 536 } 537 } 538 else if (elt->type == UCL_OBJECT) { 539 /* Validate all items using the specified schema */ 540 while (ret && (it = ucl_iterate_object (obj, &piter, true)) != NULL) { 541 ret = ucl_schema_validate (elt, it, false, err, root); 542 } 543 } 544 else { 545 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, 546 "items attribute is invalid in schema"); 547 ret = false; 548 break; 549 } 550 } 551 else if (strcmp (ucl_object_key (elt), "additionalItems") == 0) { 552 if (elt->type == UCL_BOOLEAN) { 553 if (!ucl_object_toboolean (elt)) { 554 /* Deny additional fields completely */ 555 allow_additional = false; 556 } 557 } 558 else if (elt->type == UCL_OBJECT) { 559 /* Define validator for additional fields */ 560 additional_schema = elt; 561 } 562 else { 563 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, elt, 564 "additionalItems attribute is invalid in schema"); 565 ret = false; 566 break; 567 } 568 } 569 else if (elt->type == UCL_BOOLEAN && 570 strcmp (ucl_object_key (elt), "uniqueItems") == 0) { 571 need_unique = ucl_object_toboolean (elt); 572 } 573 else if (strcmp (ucl_object_key (elt), "minItems") == 0 574 && ucl_object_toint_safe (elt, &minmax)) { 575 if (obj->len < minmax) { 576 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 577 "array has not enough items: %u, minimum is: %u", 578 obj->len, (unsigned)minmax); 579 ret = false; 580 break; 581 } 582 } 583 else if (strcmp (ucl_object_key (elt), "maxItems") == 0 584 && ucl_object_toint_safe (elt, &minmax)) { 585 if (obj->len > minmax) { 586 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 587 "array has too many items: %u, maximum is: %u", 588 obj->len, (unsigned)minmax); 589 ret = false; 590 break; 591 } 592 } 593 } 594 595 if (ret) { 596 /* Additional properties */ 597 if (!allow_additional || additional_schema != NULL) { 598 if (first_unvalidated != NULL) { 599 if (!allow_additional) { 600 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 601 "array has undefined item"); 602 ret = false; 603 } 604 else if (additional_schema != NULL) { 605 elt = first_unvalidated; 606 while (elt) { 607 if (!ucl_schema_validate (additional_schema, elt, false, 608 err, root)) { 609 ret = false; 610 break; 611 } 612 elt = elt->next; 613 } 614 } 615 } 616 } 617 /* Required properties */ 618 if (ret && need_unique) { 619 ret = ucl_schema_array_is_unique (obj, err); 620 } 621 } 622 623 return ret; 624} 625 626/* 627 * Returns whether this object is allowed for this type 628 */ 629static bool 630ucl_schema_type_is_allowed (ucl_object_t *type, ucl_object_t *obj, 631 struct ucl_schema_error *err) 632{ 633 ucl_object_iter_t iter = NULL; 634 ucl_object_t *elt; 635 const char *type_str; 636 ucl_type_t t; 637 638 if (type == NULL) { 639 /* Any type is allowed */ 640 return true; 641 } 642 643 if (type->type == UCL_ARRAY) { 644 /* One of allowed types */ 645 while ((elt = ucl_iterate_object (type, &iter, true)) != NULL) { 646 if (ucl_schema_type_is_allowed (elt, obj, err)) { 647 return true; 648 } 649 } 650 } 651 else if (type->type == UCL_STRING) { 652 type_str = ucl_object_tostring (type); 653 if (!ucl_string_to_type (type_str, &t)) { 654 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, type, 655 "Type attribute is invalid in schema"); 656 return false; 657 } 658 if (obj->type != t) { 659 /* Some types are actually compatible */ 660 if (obj->type == UCL_TIME && t == UCL_FLOAT) { 661 return true; 662 } 663 else if (obj->type == UCL_INT && t == UCL_FLOAT) { 664 return true; 665 } 666 else { 667 ucl_schema_create_error (err, UCL_SCHEMA_TYPE_MISMATCH, obj, 668 "Invalid type of %s, expected %s", 669 ucl_object_type_to_string (obj->type), 670 ucl_object_type_to_string (t)); 671 } 672 } 673 else { 674 /* Types are equal */ 675 return true; 676 } 677 } 678 679 return false; 680} 681 682/* 683 * Check if object is equal to one of elements of enum 684 */ 685static bool 686ucl_schema_validate_enum (ucl_object_t *en, ucl_object_t *obj, 687 struct ucl_schema_error *err) 688{ 689 ucl_object_iter_t iter = NULL; 690 ucl_object_t *elt; 691 bool ret = false; 692 693 while ((elt = ucl_iterate_object (en, &iter, true)) != NULL) { 694 if (ucl_object_compare (elt, obj) == 0) { 695 ret = true; 696 break; 697 } 698 } 699 700 if (!ret) { 701 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 702 "object is not one of enumerated patterns"); 703 } 704 705 return ret; 706} 707 708 709/* 710 * Check a single ref component 711 */ 712static ucl_object_t * 713ucl_schema_resolve_ref_component (ucl_object_t *cur, 714 const char *refc, int len, 715 struct ucl_schema_error *err) 716{ 717 ucl_object_t *res = NULL; 718 char *err_str; 719 int num, i; 720 721 if (cur->type == UCL_OBJECT) { 722 /* Find a key inside an object */ 723 res = ucl_object_find_keyl (cur, refc, len); 724 if (res == NULL) { 725 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, 726 "reference %s is invalid, missing path component", refc); 727 return NULL; 728 } 729 } 730 else if (cur->type == UCL_ARRAY) { 731 /* We must figure out a number inside array */ 732 num = strtoul (refc, &err_str, 10); 733 if (err_str != NULL && *err_str != '/' && *err_str != '\0') { 734 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, 735 "reference %s is invalid, invalid item number", refc); 736 return NULL; 737 } 738 res = cur->value.av; 739 i = 0; 740 while (res != NULL) { 741 if (i == num) { 742 break; 743 } 744 res = res->next; 745 } 746 if (res == NULL) { 747 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, cur, 748 "reference %s is invalid, item number %d does not exist", 749 refc, num); 750 return NULL; 751 } 752 } 753 else { 754 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, 755 "reference %s is invalid, contains primitive object in the path", 756 refc); 757 return NULL; 758 } 759 760 return res; 761} 762/* 763 * Find reference schema 764 */ 765static ucl_object_t * 766ucl_schema_resolve_ref (ucl_object_t *root, const char *ref, 767 struct ucl_schema_error *err) 768{ 769 const char *p, *c; 770 ucl_object_t *res = NULL; 771 772 773 if (ref[0] != '#') { 774 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root, 775 "reference %s is invalid, not started with #", ref); 776 return NULL; 777 } 778 if (ref[1] == '/') { 779 p = &ref[2]; 780 } 781 else if (ref[1] == '\0') { 782 return root; 783 } 784 else { 785 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, root, 786 "reference %s is invalid, not started with #/", ref); 787 return NULL; 788 } 789 790 c = p; 791 res = root; 792 793 while (*p != '\0') { 794 if (*p == '/') { 795 if (p - c == 0) { 796 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, 797 "reference %s is invalid, empty path component", ref); 798 return NULL; 799 } 800 /* Now we have some url part, so we need to figure out where we are */ 801 res = ucl_schema_resolve_ref_component (res, c, p - c, err); 802 if (res == NULL) { 803 return NULL; 804 } 805 c = p + 1; 806 } 807 p ++; 808 } 809 810 if (p - c != 0) { 811 res = ucl_schema_resolve_ref_component (res, c, p - c, err); 812 } 813 814 if (res == NULL || res->type != UCL_OBJECT) { 815 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, res, 816 "reference %s is invalid, cannot find specified object", 817 ref); 818 return NULL; 819 } 820 821 return res; 822} 823 824static bool 825ucl_schema_validate_values (ucl_object_t *schema, ucl_object_t *obj, 826 struct ucl_schema_error *err) 827{ 828 ucl_object_t *elt, *cur; 829 int64_t constraint, i; 830 831 elt = ucl_object_find_key (schema, "maxValues"); 832 if (elt != NULL && elt->type == UCL_INT) { 833 constraint = ucl_object_toint (elt); 834 cur = obj; 835 i = 0; 836 while (cur) { 837 if (i > constraint) { 838 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 839 "object has more values than defined: %ld", 840 (long int)constraint); 841 return false; 842 } 843 i ++; 844 cur = cur->next; 845 } 846 } 847 elt = ucl_object_find_key (schema, "minValues"); 848 if (elt != NULL && elt->type == UCL_INT) { 849 constraint = ucl_object_toint (elt); 850 cur = obj; 851 i = 0; 852 while (cur) { 853 if (i >= constraint) { 854 break; 855 } 856 i ++; 857 cur = cur->next; 858 } 859 if (i < constraint) { 860 ucl_schema_create_error (err, UCL_SCHEMA_CONSTRAINT, obj, 861 "object has less values than defined: %ld", 862 (long int)constraint); 863 return false; 864 } 865 } 866 867 return true; 868} 869 870static bool 871ucl_schema_validate (ucl_object_t *schema, 872 ucl_object_t *obj, bool try_array, 873 struct ucl_schema_error *err, 874 ucl_object_t *root) 875{ 876 ucl_object_t *elt, *cur; 877 ucl_object_iter_t iter = NULL; 878 bool ret; 879 880 if (schema->type != UCL_OBJECT) { 881 ucl_schema_create_error (err, UCL_SCHEMA_INVALID_SCHEMA, schema, 882 "schema is %s instead of object", ucl_object_type_to_string (schema->type)); 883 return false; 884 } 885 886 if (try_array) { 887 /* 888 * Special case for multiple values 889 */ 890 if (!ucl_schema_validate_values (schema, obj, err)) { 891 return false; 892 } 893 LL_FOREACH (obj, cur) { 894 if (!ucl_schema_validate (schema, cur, false, err, root)) { 895 return false; 896 } 897 } 898 return true; 899 } 900 901 elt = ucl_object_find_key (schema, "enum"); 902 if (elt != NULL && elt->type == UCL_ARRAY) { 903 if (!ucl_schema_validate_enum (elt, obj, err)) { 904 return false; 905 } 906 } 907 908 elt = ucl_object_find_key (schema, "allOf"); 909 if (elt != NULL && elt->type == UCL_ARRAY) { 910 iter = NULL; 911 while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) { 912 ret = ucl_schema_validate (cur, obj, true, err, root); 913 if (!ret) { 914 return false; 915 } 916 } 917 } 918 919 elt = ucl_object_find_key (schema, "anyOf"); 920 if (elt != NULL && elt->type == UCL_ARRAY) { 921 iter = NULL; 922 while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) { 923 ret = ucl_schema_validate (cur, obj, true, err, root); 924 if (ret) { 925 break; 926 } 927 } 928 if (!ret) { 929 return false; 930 } 931 else { 932 /* Reset error */ 933 err->code = UCL_SCHEMA_OK; 934 } 935 } 936 937 elt = ucl_object_find_key (schema, "oneOf"); 938 if (elt != NULL && elt->type == UCL_ARRAY) { 939 iter = NULL; 940 ret = false; 941 while ((cur = ucl_iterate_object (elt, &iter, true)) != NULL) { 942 if (!ret) { 943 ret = ucl_schema_validate (cur, obj, true, err, root); 944 } 945 else if (ucl_schema_validate (cur, obj, true, err, root)) { 946 ret = false; 947 break; 948 } 949 } 950 if (!ret) { 951 return false; 952 } 953 } 954 955 elt = ucl_object_find_key (schema, "not"); 956 if (elt != NULL && elt->type == UCL_OBJECT) { 957 if (ucl_schema_validate (elt, obj, true, err, root)) { 958 return false; 959 } 960 else { 961 /* Reset error */ 962 err->code = UCL_SCHEMA_OK; 963 } 964 } 965 966 elt = ucl_object_find_key (schema, "$ref"); 967 if (elt != NULL) { 968 cur = ucl_schema_resolve_ref (root, ucl_object_tostring (elt), err); 969 if (cur == NULL) { 970 return false; 971 } 972 if (!ucl_schema_validate (cur, obj, try_array, err, root)) { 973 return false; 974 } 975 } 976 977 elt = ucl_object_find_key (schema, "type"); 978 if (!ucl_schema_type_is_allowed (elt, obj, err)) { 979 return false; 980 } 981 982 switch (obj->type) { 983 case UCL_OBJECT: 984 return ucl_schema_validate_object (schema, obj, err, root); 985 break; 986 case UCL_ARRAY: 987 return ucl_schema_validate_array (schema, obj, err, root); 988 break; 989 case UCL_INT: 990 case UCL_FLOAT: 991 return ucl_schema_validate_number (schema, obj, err); 992 break; 993 case UCL_STRING: 994 return ucl_schema_validate_string (schema, obj, err); 995 break; 996 default: 997 break; 998 } 999 1000 return true; 1001} 1002 1003bool 1004ucl_object_validate (ucl_object_t *schema, 1005 ucl_object_t *obj, struct ucl_schema_error *err) 1006{ 1007 return ucl_schema_validate (schema, obj, true, err, schema); 1008} 1009