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 (&reg, pattern, REG_EXTENDED | REG_NOSUB) == 0) {
143		while ((elt = ucl_iterate_object (obj, &iter, true)) != NULL) {
144			if (regexec (&reg, ucl_object_key (elt), 0, NULL, 0) == 0) {
145				res = elt;
146				break;
147			}
148		}
149		regfree (&reg);
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