mirror of
				https://github.com/Telecominfraproject/ols-ucentral-schema.git
				synced 2025-10-31 01:58:03 +00:00 
			
		
		
		
	 b5531dd0a0
			
		
	
	b5531dd0a0
	
	
	
		
			
			The `uc-portrange` string format will match single ports and ranges in `minport-maxport` notation. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
		
			
				
	
	
		
			603 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Ucode
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			603 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Ucode
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env -S ucode -R
 | |
| "use strict";
 | |
| 
 | |
| let fs = require("fs");
 | |
| let math = require("math");
 | |
| 
 | |
| function to_property_name(name)
 | |
| {
 | |
| 	return replace(replace(
 | |
| 		name, //replace(name, /-(.)/g, (_, c) => uc(c)),
 | |
| 		/[^A-Za-z0-9_]+/g,
 | |
| 		'_'
 | |
| 	), /^[0-9]/, '_$&');
 | |
| }
 | |
| 
 | |
| function to_method_name(verb, name)
 | |
| {
 | |
| 	return verb + replace(
 | |
| 		replace(name, /(^|-|\.)(.)/g, (_, p, c) => uc(c)),
 | |
| 		/[^A-Za-z0-9_]+/g,
 | |
| 		'_'
 | |
| 	);
 | |
| }
 | |
| 
 | |
| function to_json_ptr(propertyName)
 | |
| {
 | |
| 	return replace(propertyName, /[~\/]/g, m => (m == '~' ? '~0' : '~1'));
 | |
| }
 | |
| 
 | |
| function to_text_list(listValue, quoteItems)
 | |
| {
 | |
| 	let res = [];
 | |
| 	let lastidx = length(listValue) - 1;
 | |
| 
 | |
| 	for (let i, v in listValue) {
 | |
| 		if (i > 0)
 | |
| 			push(res, (i < lastidx) ? ', ' : ' or ');
 | |
| 
 | |
| 		push(res, quoteItems ? sprintf('%J', v) : v);
 | |
| 	}
 | |
| 
 | |
| 	return join('', res);
 | |
| }
 | |
| 
 | |
| 
 | |
| let GeneratorProto = {
 | |
| 	format_validators: {
 | |
| 		"uc-cidr4": {
 | |
| 			desc: 'IPv4 CIDR',
 | |
| 			code: [
 | |
| 				'let m = match(value, /^(auto|[0-9.]+)\\/([0-9]+)$/);',
 | |
| 				'return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 4) && +m[2] <= 32) : false;'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-cidr6": {
 | |
| 			desc: 'IPv6 CIDR',
 | |
| 			code: [
 | |
| 				'let m = match(value, /^(auto|[0-9a-fA-F:.]+)\\/([0-9]+)$/);',
 | |
| 				'return m ? ((m[1] == "auto" || length(iptoarr(m[1])) == 16) && +m[2] <= 128) : false;'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-cidr": {
 | |
| 			desc: 'IPv4 or IPv6 CIDR',
 | |
| 			code: [
 | |
| 				'let m = match(value, /^(auto|[0-9a-fA-F:.]+)\\/([0-9]+)$/);',
 | |
| 				'if (!m) return false;',
 | |
| 				'let l = (m[1] == "auto") ? 16 : length(iptoarr(m[1]));',
 | |
| 				'return (l > 0 && +m[2] <= (l * 8));'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-mac": {
 | |
| 			desc: 'MAC address',
 | |
| 			code: [
 | |
| 				'return match(value, /^[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]$/i);'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-host": {
 | |
| 			desc: 'hostname or IP address',
 | |
| 			code: [
 | |
| 				'if (length(iptoarr(value)) != 0) return true;',
 | |
| 				'if (length(value) > 255) return false;',
 | |
| 				'let labels = split(value, ".");',
 | |
| 				'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0);'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-timeout": {
 | |
| 			desc: 'timeout value',
 | |
| 			code: [
 | |
| 				'return match(value, /^[0-9]+[smhdw]$/);'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-base64": {
 | |
| 			desc: 'base64 encoded data',
 | |
| 			code: [
 | |
| 				'return b64dec(value) != null;'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-portrange": {
 | |
| 			desc: 'network port range',
 | |
| 			code: [
 | |
| 				'let ports = match(value, /^([0-9]|[1-9][0-9]*)(-([0-9]|[1-9][0-9]*))?$/);',
 | |
| 				'if (!ports) return false;',
 | |
| 				'let min = +ports[1], max = ports[2] ? +ports[3] : min;',
 | |
| 				'return (min <= 65535 && max <= 65535 && max >= min);'
 | |
| 			]
 | |
| 		},
 | |
| 		"hostname": {
 | |
| 			desc: 'hostname',
 | |
| 			code: [
 | |
| 				'if (length(value) > 255) return false;',
 | |
| 				'let labels = split(value, ".");',
 | |
| 				'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]{1,2}|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0);'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-fqdn": {
 | |
| 			desc: 'fully qualified domain name',
 | |
| 			code: [
 | |
| 				'if (length(value) > 255) return false;',
 | |
| 				'let labels = split(value, ".");',
 | |
| 				'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]{1,2}|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 1);'
 | |
| 			]
 | |
| 		},
 | |
| 		"uc-ip": {
 | |
| 			desc: 'IPv4 or IPv6 address',
 | |
| 			code: [
 | |
| 				'return (length(iptoarr(value)) == 4 || length(iptoarr(value)) == 16);'
 | |
| 			]
 | |
| 		},
 | |
| 		"ipv4": {
 | |
| 			desc: 'IPv4 address',
 | |
| 			code: [
 | |
| 				'return (length(iptoarr(value)) == 4);'
 | |
| 			]
 | |
| 		},
 | |
| 		"ipv6": {
 | |
| 			desc: 'IPv6 address',
 | |
| 			code: [
 | |
| 				'return (length(iptoarr(value)) == 16);'
 | |
| 			]
 | |
| 		},
 | |
| 		"uri": {
 | |
| 			desc: 'URI',
 | |
| 			code: [
 | |
| 				'if (index(value, "data:") == 0) return true;',
 | |
| 				'let m = match(value, /^[a-z+-]+:\\/\\/([^\\/]+).*$/);',
 | |
| 				'if (!m) return false;',
 | |
| 				'if (length(iptoarr(m[1])) != 0) return true;',
 | |
| 				'if (length(m[1]) > 255) return false;',
 | |
| 				'let labels = split(m[1], ".");',
 | |
| 				'return (length(filter(labels, label => !match(label, /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9])$/))) == 0 && length(labels) > 0);'
 | |
| 			]
 | |
| 		}
 | |
| 	},
 | |
| 
 | |
| 	type_keywords: {
 | |
| 		number: [
 | |
| 			'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum',
 | |
| 			'multipleOf'
 | |
| 		],
 | |
| 		string: [
 | |
| 			'pattern', 'format', 'minLength', 'maxLength'
 | |
| 		],
 | |
| 		array: [
 | |
| 			'minItems', 'maxItems', 'items'
 | |
| 		],
 | |
| 		object: [
 | |
| 			'additionalProperties', 'minProperties', 'maxProperties',
 | |
| 			'properties', 'propertyNames'
 | |
| 		]
 | |
| 	},
 | |
| 
 | |
| 	has_type_keywords: function(typeName, valueSpec) {
 | |
| 		if (!exists(this.type_keywords, typeName))
 | |
| 			return false;
 | |
| 
 | |
| 		for (let keyword in this.type_keywords[typeName])
 | |
| 			if (exists(valueSpec, keyword))
 | |
| 				return true;
 | |
| 
 | |
| 		return false;
 | |
| 	},
 | |
| 
 | |
| 	is_ref: function(value)
 | |
| 	{
 | |
| 		return (
 | |
| 			type(value) == "object" &&
 | |
| 			exists(value, "$ref") &&
 | |
| 			//length(value) == 1 &&
 | |
| 			type(value["$ref"]) == "string"
 | |
| 		) ? value["$ref"] : null;
 | |
| 	},
 | |
| 
 | |
| 	emit_test_expression: function(indent, condExpr, errorMsg)
 | |
| 	{
 | |
| 		this.print(indent, 'if (%s)', condExpr);
 | |
| 		this.print(indent, '	push(errors, [ location, %J ]);', errorMsg);
 | |
| 		this.print(indent, '');
 | |
| 	},
 | |
| 
 | |
| 	emit_generic_tests: function(indent, valueExpr, valueSpec)
 | |
| 	{
 | |
| 		let typeMap = {
 | |
| 			string: 'type(%s) != "string"',
 | |
| 			number: '!(type(%s) in [ "int", "double" ])',
 | |
| 			integer: 'type(%s) != "int"',
 | |
| 			boolean: 'type(%s) != "bool"',
 | |
| 			array: 'type(%s) != "array"',
 | |
| 			object: 'type(%s) != "object"',
 | |
| 			null: 'type(%s) != null'
 | |
| 		};
 | |
| 
 | |
| 		if (exists(valueSpec, 'type')) {
 | |
| 			let types = filter(
 | |
| 				(type(valueSpec.type) == 'array') ? valueSpec.type : [ valueSpec.type ],
 | |
| 				typeName => {
 | |
| 					return exists(typeMap, typeName)
 | |
| 						? true
 | |
| 						: (warn("Unrecognized type name '" + type + "'.\n"), false);
 | |
| 				}
 | |
| 			);
 | |
| 
 | |
| 			this.emit_test_expression(indent,
 | |
| 				join(' && ', map(types, typeName => sprintf(typeMap[typeName], valueExpr))),
 | |
| 				sprintf('must be of type %s', to_text_list(types)));
 | |
| 		}
 | |
| 
 | |
| 		if (type(valueSpec.enum) == 'array' && length(valueSpec.enum) > 0)
 | |
| 			this.emit_test_expression(indent,
 | |
| 				sprintf('!(%s in %J)', valueExpr, valueSpec.enum),
 | |
| 				sprintf('must be one of %s', to_text_list(valueSpec.enum, true)));
 | |
| 
 | |
| 		if (exists(valueSpec, 'const'))
 | |
| 			this.emit_test_expression(indent,
 | |
| 				sprintf('%s != %J', valueExpr, valueSpec.const),
 | |
| 				sprintf('must have value %J', valueSpec.const));
 | |
| 	},
 | |
| 
 | |
| 	emit_number_tests: function(indent, valueExpr, valueSpec)
 | |
| 	{
 | |
| 		if (!this.has_type_keywords('number', valueSpec))
 | |
| 			return;
 | |
| 
 | |
| 		this.print(indent, 'if (type(%s) in [ "int", "double" ]) {', valueExpr);
 | |
| 
 | |
| 		if (exists(valueSpec, 'multipleOf')) {
 | |
| 			this.emit_test_expression(indent + '\t',
 | |
| 				sprintf('%s / %J != int(%s / %J)', valueExpr, valueSpec.multipleOf, valueExpr, valueSpec.multipleOf),
 | |
| 				sprintf('must be divisible by %J', valueSpec.multipleOf));
 | |
| 		}
 | |
| 
 | |
| 		let constraints = {
 | |
| 			'maximum': [ '>', 'lower than or equal to' ],
 | |
| 			'minimum': [ '<', 'bigger than or equal to' ],
 | |
| 			'exclusiveMaximum': [ '>=', 'lower than' ],
 | |
| 			'exclusiveMinimum': [ '<=', 'bigger than' ]
 | |
| 		};
 | |
| 
 | |
| 		for (let keyword, op_desc in constraints) {
 | |
| 			if (exists(valueSpec, keyword))
 | |
| 				this.emit_test_expression(indent + '\t',
 | |
| 					sprintf('%s %s %J', valueExpr, op_desc[0], valueSpec[keyword]),
 | |
| 					sprintf('must be %s %J', op_desc[1], valueSpec[keyword]));
 | |
| 		}
 | |
| 
 | |
| 		this.print(indent, '}\n');
 | |
| 	},
 | |
| 
 | |
| 	emit_string_tests: function(indent, valueExpr, valueSpec)
 | |
| 	{
 | |
| 		if (!this.has_type_keywords('string', valueSpec))
 | |
| 			return;
 | |
| 
 | |
| 		this.print(indent, 'if (type(%s) == "string") {', valueExpr);
 | |
| 
 | |
| 		if (exists(valueSpec, 'pattern')) {
 | |
| 			try {
 | |
| 				regexp(valueSpec.pattern);
 | |
| 
 | |
| 				this.emit_test_expression(indent + '\t',
 | |
| 					sprintf('!match(%s, regexp(%J))', valueExpr, valueSpec.pattern),
 | |
| 					sprintf('must match regular expression /%s/', valueSpec.pattern));
 | |
| 			}
 | |
| 			catch (e) {
 | |
| 				warn("Uncompilable regular expression '" + valueSpec.pattern + "': " + e + '.\n');
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if (exists(valueSpec, 'maxLength')) {
 | |
| 			this.emit_test_expression(indent + '\t',
 | |
| 				sprintf('length(%s) > %J', valueExpr, valueSpec.maxLength),
 | |
| 				sprintf('must be at most %J characters long', valueSpec.maxLength));
 | |
| 		}
 | |
| 
 | |
| 		if (exists(valueSpec, 'minLength')) {
 | |
| 			this.emit_test_expression(indent + '\t',
 | |
| 				sprintf('length(%s) < %J', valueExpr, valueSpec.minLength),
 | |
| 				sprintf('must be at least %J characters long', valueSpec.minLength));
 | |
| 		}
 | |
| 
 | |
| 		if (exists(valueSpec, 'format')) {
 | |
| 			if (exists(this.format_validators, valueSpec.format)) {
 | |
| 				this.emit_test_expression(indent + '\t',
 | |
| 					sprintf('!%s(%s)', to_method_name('match', valueSpec.format), valueExpr),
 | |
| 					sprintf('must be a valid %s', this.format_validators[valueSpec.format].desc));
 | |
| 			}
 | |
| 			else {
 | |
| 				warn("Unrecognized string format '" + valueSpec.format + '".\n');
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.print(indent, '}\n');
 | |
| 	},
 | |
| 
 | |
| 	emit_array_tests: function(indent, valueExpr, valueSpec)
 | |
| 	{
 | |
| 		if (!this.has_type_keywords('array', valueSpec))
 | |
| 			return;
 | |
| 
 | |
| 		this.print(indent, 'if (type(%s) == "array") {', valueExpr);
 | |
| 
 | |
| 		if (exists(valueSpec, 'maxItems')) {
 | |
| 			this.emit_test_expression(indent + '\t',
 | |
| 				sprintf('length(%s) > %J', valueExpr, valueSpec.maxItems),
 | |
| 				sprintf('must not have more than %J items', valueSpec.maxItems));
 | |
| 		}
 | |
| 
 | |
| 		if (exists(valueSpec, 'minItems')) {
 | |
| 			this.emit_test_expression(indent + '\t',
 | |
| 				sprintf('length(%s) < %J', valueExpr, valueSpec.minItems),
 | |
| 				sprintf('must have at least %J items', valueSpec.minItems));
 | |
| 		}
 | |
| 
 | |
| 		/* XXX: uniqueItems, maxContains, minContains */
 | |
| 
 | |
| 		let itemSpec = valueSpec.items,
 | |
| 		    isRef = this.is_ref(itemSpec);
 | |
| 
 | |
| 		if (type(itemSpec) == "object") {
 | |
| 			if (isRef) {
 | |
| 				this.print(indent, '	return map(value, (item, i) => %s(location + "/" + i, item, errors));',
 | |
| 					to_method_name('instantiate', replace(isRef, '#/$defs/', '')));
 | |
| 			}
 | |
| 			else {
 | |
| 				this.emit_spec_validation_function(indent + '\t', 'parse', 'item', itemSpec);
 | |
| 				this.print(indent, '	return map(value, (item, i) => parseItem(location + "/" + i, item, errors));');
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.print(indent, '}\n');
 | |
| 	},
 | |
| 
 | |
| 	emit_object_tests: function(indent, valueExpr, valueSpec)
 | |
| 	{
 | |
| 		if (!this.has_type_keywords('object', valueSpec))
 | |
| 			return;
 | |
| 
 | |
| 		this.print(indent, 'if (type(%s) == "object") {', valueExpr);
 | |
| 
 | |
| 		if (exists(valueSpec, 'maxProperties')) {
 | |
| 			this.emit_test_expression(indent + '\t',
 | |
| 				sprintf('length(%s) > %J', valueExpr, valueSpec.maxProperties),
 | |
| 				sprintf('must have at most %J properties', valueSpec.maxProperties));
 | |
| 		}
 | |
| 
 | |
| 		if (exists(valueSpec, 'minProperties')) {
 | |
| 			this.emit_test_expression(inden + '\t',
 | |
| 				sprintf('length(%s) < %J', valueExpr, valueSpec.minProperties),
 | |
| 				sprintf('must have at least %J properties', valueSpec.minProperties));
 | |
| 		}
 | |
| 
 | |
| 		if (type(valueSpec.propertyNames) == "object") {
 | |
| 			let keySpec = { type: 'string', ...(valueSpec.propertyNames) };
 | |
| 
 | |
| 			this.print(indent, '	for (let propertyName in value) {');
 | |
| 			this.emit_spec_validation_tests(indent + '\t\t', 'propertyName', keySpec);
 | |
| 			this.print(indent, '	}');
 | |
| 			this.print(indent, '');
 | |
| 		}
 | |
| 
 | |
| 		if (exists(valueSpec, "$ref")) {
 | |
| 			let fn = to_method_name('instantiate', replace(valueSpec['$ref'], '#/$defs/', ''));
 | |
| 
 | |
| 			if (valueSpec.additionalProperties === true)
 | |
| 				this.print(indent, '	let obj = { ...value, ...(%s(location, value, errors)) };\n', fn);
 | |
| 			else
 | |
| 				this.print(indent, '	let obj = %s(location, value, errors);\n', fn);
 | |
| 		}
 | |
| 		else {
 | |
| 			if (valueSpec.additionalProperties === true)
 | |
| 				this.print(indent, '	let obj = { ...value };\n');
 | |
| 			else
 | |
| 				this.print(indent, '	let obj = {};\n');
 | |
| 		}
 | |
| 
 | |
| 		if (type(valueSpec.properties) == "object") {
 | |
| 			for (let objectPropertyName, propertySpec in valueSpec.properties) {
 | |
| 				let valueExpr = sprintf('value[%J]', objectPropertyName),
 | |
| 				    isRef = this.is_ref(propertySpec);
 | |
| 
 | |
| 				if (!isRef) {
 | |
| 					this.emit_spec_validation_function(
 | |
| 						indent + '\t',
 | |
| 						'parse',
 | |
| 						objectPropertyName,
 | |
| 						propertySpec);
 | |
| 				}
 | |
| 
 | |
| 				this.print(indent, '	if (exists(value, %J)) {',
 | |
| 					objectPropertyName);
 | |
| 
 | |
| 				if (isRef) {
 | |
| 					this.print(indent, '		obj.%s = %s(location + "/%s", %s, errors);',
 | |
| 						to_property_name(objectPropertyName),
 | |
| 						to_method_name('instantiate', replace(isRef, '#/$defs/', '')),
 | |
| 						to_json_ptr(objectPropertyName),
 | |
| 						valueExpr);
 | |
| 				}
 | |
| 				else {
 | |
| 					this.print(indent, '		obj.%s = %s(location + "/%s", %s, errors);',
 | |
| 						to_property_name(objectPropertyName),
 | |
| 						to_method_name('parse', objectPropertyName),
 | |
| 						to_json_ptr(objectPropertyName),
 | |
| 						valueExpr);
 | |
| 				}
 | |
| 
 | |
| 				this.print(indent, '	}');
 | |
| 
 | |
| 				if (exists(propertySpec, 'default')) {
 | |
| 					this.print(indent, '	else {');
 | |
| 
 | |
| 					this.print(indent, '		obj.%s = %J;',
 | |
| 						to_property_name(objectPropertyName),
 | |
| 						propertySpec.default);
 | |
| 
 | |
| 					this.print(indent, '	}');
 | |
| 				}
 | |
| 				else if (objectPropertyName in valueSpec.required) {
 | |
| 					this.print(indent, '	else {');
 | |
| 					this.print(indent, '		push(errors, [ location, "is required" ]);');
 | |
| 					this.print(indent, '	}');
 | |
| 				}
 | |
| 
 | |
| 				this.print(indent, '');
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		this.print(indent, '	return obj;');
 | |
| 
 | |
| 		this.print(indent, '}\n');
 | |
| 	},
 | |
| 
 | |
| 	read_schema: function(path)
 | |
| 	{
 | |
| 		let fd = fs.open(path, "r");
 | |
| 
 | |
| 		if (!fd)
 | |
| 			return {};
 | |
| 
 | |
| 		let schema = json(fd.read("all"));
 | |
| 
 | |
| 		fd.close();
 | |
| 
 | |
| 		return schema;
 | |
| 	},
 | |
| 
 | |
| 	print: function(indent, fmt, ...args)
 | |
| 	{
 | |
| 		if (length(fmt)) {
 | |
| 			print(indent);
 | |
| 			printf(fmt, ...args);
 | |
| 		}
 | |
| 
 | |
| 		print("\n");
 | |
| 	},
 | |
| 
 | |
| 	emit_spec_validation_tests: function(indent, valueExpr, valueSpec)
 | |
| 	{
 | |
| 		let variantSpecs, variantErrorCond, variantErrorMsg;
 | |
| 
 | |
| 		if (type(valueSpec.anyOf) == "array" && length(valueSpec.anyOf) > 0) {
 | |
| 			variantSpecs = valueSpec.anyOf;
 | |
| 			variantErrorCond = '== 0';
 | |
| 			variantErrorMsg = 'at least one';
 | |
| 		}
 | |
| 		else if (type(valueSpec.oneOf) == "array" && length(valueSpec.oneOf) > 0) {
 | |
| 			variantSpecs = valueSpec.oneOf;
 | |
| 			variantErrorCond = '!= 1';
 | |
| 			variantErrorMsg = 'exactly one';
 | |
| 		}
 | |
| 		else if (type(valueSpec.allOf) == "array" && length(valueSpec.allOf) > 0) {
 | |
| 			variantSpecs = valueSpec.allOf;
 | |
| 			variantErrorCond = sprintf('!= %d', length(variantSpecs));
 | |
| 			variantErrorMsg = 'all';
 | |
| 		}
 | |
| 
 | |
| 		if (variantSpecs) {
 | |
| 			let functionNames = [];
 | |
| 
 | |
| 			for (let i, subSpec in variantSpecs)
 | |
| 				push(functionNames, this.emit_spec_validation_function(indent, 'parseVariant', i, subSpec));
 | |
| 
 | |
| 			this.print(indent, 'let success = 0, tryval, tryerr, vvalue = null, verrors = [];\n');
 | |
| 
 | |
| 			for (let functionName in functionNames) {
 | |
| 				this.print(indent, 'tryerr = [];');
 | |
| 				this.print(indent, 'tryval = %s(location, value, tryerr);', functionName);
 | |
| 				this.print(indent, 'if (!length(tryerr)) {');
 | |
| 				this.print(indent, '	if (type(vvalue) == "object" && type(tryval) == "object")');
 | |
| 				this.print(indent, '		vvalue = { ...vvalue, ...tryval };');
 | |
| 				this.print(indent, '	else');
 | |
| 				this.print(indent, '		vvalue = tryval;\n');
 | |
| 				this.print(indent, '	success++;');
 | |
| 				this.print(indent, '}');
 | |
| 				this.print(indent, 'else {');
 | |
| 				this.print(indent, '	push(verrors, join(" and\\n", map(tryerr, err => "\\t - " + err[1])));');
 | |
| 				this.print(indent, '}\n');
 | |
| 			}
 | |
| 
 | |
| 			this.print(indent, 'if (success %s) {', variantErrorCond);
 | |
| 			this.print(indent, '	if (length(verrors))');
 | |
| 			this.print(indent, '		push(errors, [ location, "must match %s of the following constraints:\\n" + join("\\n- or -\\n", verrors) ]);', variantErrorMsg);
 | |
| 			this.print(indent, '	else');
 | |
| 			this.print(indent, '		push(errors, [ location, "must match only one variant" ]);');
 | |
| 			this.print(indent, '	return null;');
 | |
| 			this.print(indent, '}\n');
 | |
| 			this.print(indent, 'value = vvalue;\n');
 | |
| 		}
 | |
| 
 | |
| 		this.emit_number_tests(indent, valueExpr, valueSpec);
 | |
| 		this.emit_string_tests(indent, valueExpr, valueSpec);
 | |
| 		this.emit_array_tests(indent, valueExpr, valueSpec);
 | |
| 		this.emit_object_tests(indent, valueExpr, valueSpec);
 | |
| 		this.emit_generic_tests(indent, valueExpr, valueSpec);
 | |
| 	},
 | |
| 
 | |
| 	emit_format_validation_function: function(indent, verb, formatName, formatCode)
 | |
| 	{
 | |
| 		let functionName = to_method_name(verb, formatName);
 | |
| 
 | |
| 		this.print(indent, 'function %s(value) {', functionName);
 | |
| 
 | |
| 		for (let line in formatCode)
 | |
| 			this.print(indent, '	' + line);
 | |
| 
 | |
| 		this.print(indent, '}\n');
 | |
| 	},
 | |
| 
 | |
| 	emit_spec_validation_function: function(indent, verb, propertyName, valueSpec)
 | |
| 	{
 | |
| 		let functionName = to_method_name(verb, propertyName),
 | |
| 		    isRef = this.is_ref(valueSpec);
 | |
| 
 | |
| 		this.print(indent, 'function %s(location, value, errors) {', functionName);
 | |
| 
 | |
| 		if (isRef) {
 | |
| 			this.print(indent, '	value = %s(location, value, errors);\n',
 | |
| 				to_method_name('instantiate', replace(isRef, '#/$defs/', '')));
 | |
| 		}
 | |
| 
 | |
| 		this.emit_spec_validation_tests(indent + '\t', 'value', valueSpec);
 | |
| 
 | |
| 		this.print(indent, '	return value;');
 | |
| 		this.print(indent, '}\n');
 | |
| 
 | |
| 		return functionName;
 | |
| 	},
 | |
| 
 | |
| 	generate: function()
 | |
| 	{
 | |
| 		let indent = '';
 | |
| 
 | |
| 		this.schema = this.read_schema(this.path);
 | |
| 
 | |
| 		this.print(indent, '// Automatically generated from %s - do not edit!', this.path);
 | |
| 		this.print(indent, '"use strict";\n');
 | |
| 
 | |
| 		for (let formatName, formatCode in this.format_validators)
 | |
| 			this.emit_format_validation_function(indent, 'match', formatName, formatCode.code);
 | |
| 
 | |
| 		for (let definitionId, definitionSpec in this.schema['$defs'])
 | |
| 			this.emit_spec_validation_function(indent, 'instantiate', definitionId, definitionSpec);
 | |
| 
 | |
| 		this.emit_spec_validation_function(indent, 'new', "UCentralState", this.schema);
 | |
| 
 | |
| 		this.print(indent, 'return {');
 | |
| 		this.print(indent, '	validate: (value, errors) => {');
 | |
| 		this.print(indent, '		let err = [];');
 | |
| 		this.print(indent, '		let res = newUCentralState("", value, err);');
 | |
| 		this.print(indent, '		if (errors) push(errors, ...map(err, e => "[E] (In " + e[0] + ") Value " + e[1]));');
 | |
| 		this.print(indent, '		return length(err) ? null : res;');
 | |
| 		this.print(indent, '	}');
 | |
| 		this.print(indent, '};');
 | |
| 	}
 | |
| };
 | |
| 
 | |
| function instantiateGenerator(path) {
 | |
| 	return proto({ path }, GeneratorProto);
 | |
| }
 | |
| 
 | |
| let generator = instantiateGenerator("./ucentral.schema.pretty.json");
 | |
| 
 | |
| generator.generate();
 |