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