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>
		
			
				
	
	
		
			524 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ucode
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			524 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ucode
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env -S ucode -R
 | |
| 
 | |
| let fs = require("fs");
 | |
| let math = require("math");
 | |
| 
 | |
| // words sourced from https://github.com/imsky/wordlists/
 | |
| let verbs = [
 | |
| 	'access', 'address', 'bookmark', 'browse', 'build', 'chat', 'click', 'close', 'code', 'compile', 'connect',
 | |
| 	'debug', 'deploy', 'develop', 'download', 'downsize', 'drag', 'evangelize', 'execute', 'flub', 'generate',
 | |
| 	'insource', 'install', 'link', 'load', 'log', 'make', 'marinate', 'message', 'noodle', 'onboard', 'open',
 | |
| 	'paper', 'patch', 'ping', 'populate', 'post', 'productize', 'receive', 'release', 'resonate', 'run', 'scroll',
 | |
| 	'search', 'send', 'share', 'step', 'sunset', 'surface', 'task', 'transition', 'triangulate', 'type', 'unpack',
 | |
| 	'upload', 'vector', 'view', 'visit'
 | |
| ];
 | |
| 
 | |
| let nouns = [
 | |
| 	'admin', 'agreeance', 'alignment', 'application', 'architecture', 'argument', 'availability', 'backburner',
 | |
| 	'bandwidth', 'baseline', 'benefit', 'block', 'boondoggle', 'brass', 'buy-in', 'capital', 'chain', 'channel',
 | |
| 	'code', 'comment', 'community', 'content', 'convergence', 'coopetition', 'cowboy', 'creative', 'data', 'deck',
 | |
| 	'deliverable', 'delta', 'dialogue', 'disconnect', 'dog', 'empowerment', 'experience', 'expertise', 'float',
 | |
| 	'flunky', 'function', 'functionality', 'gatekeeper', 'gofer', 'goldbricker', 'hardball', 'idea', 'ideation',
 | |
| 	'imperative', 'improvement', 'infomediary', 'information', 'infrastructure', 'initiative', 'innovation',
 | |
| 	'integer', 'interface', 'issue', 'item', 'keyword', 'kicker', 'kudos', 'language', 'leadership', 'learning',
 | |
| 	'line', 'linkage', 'literal', 'market', 'material', 'method', 'methodology', 'metrics', 'mindshare', 'model',
 | |
| 	'name', 'network', 'niche', 'number', 'object', 'opportunity', 'ownership', 'paradigm', 'parameter',
 | |
| 	'partnership', 'pivot', 'platform', 'portal', 'potentiality', 'practice', 'procedure', 'process', 'product',
 | |
| 	'pushback', 'relationship', 'report', 'resource', 'result', 'runway', 'scenario', 'schema', 'scope', 'scrub',
 | |
| 	'service', 'shrink', 'sidebar', 'silo', 'skill', 'skillset', 'snippet', 'solution', 'source', 'space',
 | |
| 	'strategy', 'string', 'synergy', 'syntax', 'system', 'talent', 'technology', 'type', 'upside', 'value',
 | |
| 	'vector', 'verbiage', 'vision'
 | |
| ];
 | |
| 
 | |
| let adjectives = [
 | |
| 	'absolute', 'achromatic', 'acoustic', 'adiabatic', 'alternating', 'atomic', 'binding', 'brownian', 'buoyant',
 | |
| 	'central', 'chief', 'chromatic', 'closed', 'coherent', 'corporate', 'critical', 'customer', 'dense', 'direct',
 | |
| 	'direct', 'district', 'dynamic', 'electric', 'electrical', 'endothermic', 'exothermic', 'forward', 'free',
 | |
| 	'fundamental', 'future', 'global', 'gravitational', 'human', 'internal', 'internal', 'international',
 | |
| 	'isobaric', 'isochoric', 'isothermal', 'kinetic', 'latent', 'lead', 'legacy', 'magnetic',  'mechanical',
 | |
| 	'national', 'natural', 'nuclear', 'open', 'optical', 'potential', 'primary', 'principal', 'progressive',
 | |
| 	'quantum', 'radiant', 'radioactive', 'rectilinear', 'regional', 'relative', 'resolving', 'resonnt',
 | |
| 	'resultant', 'rigid', 'senior', 'volumetric'
 | |
| ];
 | |
| 
 | |
| function is_array(val)
 | |
| {
 | |
| 	return (type(val) == "array" && length(val) > 0);
 | |
| }
 | |
| 
 | |
| function random_string(len)
 | |
| {
 | |
| 	let chars = [];
 | |
| 
 | |
| 	for (let i = 0; i < len; i++) {
 | |
| 		let b = math.rand() % 52;
 | |
| 		push(chars, (b > 26 ? 65 : 97) + (b % 26));
 | |
| 	}
 | |
| 
 | |
| 	return chr(...chars);
 | |
| }
 | |
| 
 | |
| function random_item(array)
 | |
| {
 | |
| 	if (type(array) != "array")
 | |
| 		return null;
 | |
| 
 | |
| 	return array[math.rand() % length(array)];
 | |
| }
 | |
| 
 | |
| function random_phrase(min, max) {
 | |
| 	let str, len;
 | |
| 	let prefix = '';
 | |
| 
 | |
| 	while (true) {
 | |
| 		let verb = random_item(verbs),
 | |
| 		    adjective, noun;
 | |
| 
 | |
| 		while (true) {
 | |
| 			adjective = random_item(adjectives);
 | |
| 
 | |
| 			if (adjective != verb)
 | |
| 				break;
 | |
| 		}
 | |
| 
 | |
| 		while (true) {
 | |
| 			noun = random_item(nouns);
 | |
| 
 | |
| 			if (noun != verb && noun != adjective)
 | |
| 				break;
 | |
| 		}
 | |
| 
 | |
| 		str = prefix + verb + '-' + adjective + '-' + noun;
 | |
| 		len = length(str);
 | |
| 
 | |
| 		if (len < min) {
 | |
| 			prefix = str + '-';
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (len > max)
 | |
| 			str = substr(str, 0, max);
 | |
| 
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	return str;
 | |
| }
 | |
| 
 | |
| function random_ip4addr() {
 | |
| 	return sprintf(random_item([
 | |
| 		'10.%d.%d.%d',
 | |
| 		'172.16.%d.%d',
 | |
| 		'172.17.%d.%d',
 | |
| 		'172.18.%d.%d',
 | |
| 		'172.19.%d.%d',
 | |
| 		'172.20.%d.%d',
 | |
| 		'172.21.%d.%d',
 | |
| 		'172.22.%d.%d',
 | |
| 		'172.23.%d.%d',
 | |
| 		'172.24.%d.%d',
 | |
| 		'172.25.%d.%d',
 | |
| 		'172.26.%d.%d',
 | |
| 		'172.27.%d.%d',
 | |
| 		'172.28.%d.%d',
 | |
| 		'172.29.%d.%d',
 | |
| 		'172.30.%d.%d',
 | |
| 		'172.31.%d.%d',
 | |
| 		'192.168.%d.%d'
 | |
| 	]), math.rand() % 256, math.rand() % 256, math.rand() % 256);
 | |
| }
 | |
| 
 | |
| function random_ip6addr() {
 | |
| 	return sprintf('2001:db8:%x:%x:%x:%x:%x:%x',
 | |
| 		math.rand() % 0x10000, math.rand() % 0x10000, math.rand() % 0x10000,
 | |
| 		math.rand() % 0x10000, math.rand() % 0x10000, math.rand() % 0x10000);
 | |
| }
 | |
| 
 | |
| function random_hostname() {
 | |
| 	return random_phrase(0, Infinity) + '.example.org';
 | |
| }
 | |
| 
 | |
| function random_email() {
 | |
| 	return random_phrase(0, Infinity) + '@example.org';
 | |
| }
 | |
| 
 | |
| function random_uri() {
 | |
| 	return 'https://example.org/' + random_phrase(0, Infinity);
 | |
| }
 | |
| 
 | |
| function random_value(kind, minLength, maxLength) {
 | |
| 	switch (kind) {
 | |
| 	case 'ipv4':
 | |
| 		return random_ip4addr();
 | |
| 
 | |
| 	case 'ipv6':
 | |
| 		return random_ip6addr();
 | |
| 
 | |
| 	case 'email':
 | |
| 	case 'idn-email':
 | |
| 		return random_email();
 | |
| 
 | |
| 	case 'hostname':
 | |
| 	case 'idn-hostname':
 | |
| 	case 'uc-fqdn':
 | |
| 		return random_hostname();
 | |
| 
 | |
| 	case 'uri':
 | |
| 	case 'uri-reference':
 | |
| 	case 'iri':
 | |
| 	case 'iri-reference':
 | |
| 		return random_uri();
 | |
| 
 | |
| 	case 'uc-ip':
 | |
| 		switch (math.rand() % 2) {
 | |
| 		case 0: return random_ip4addr();
 | |
| 		case 1: return random_ip6addr();
 | |
| 		}
 | |
| 
 | |
| 		break;
 | |
| 
 | |
| 	case 'uc-host':
 | |
| 		switch (math.rand() % 3) {
 | |
| 		case 0: return random_hostname();
 | |
| 		case 1: return random_ip4addr();
 | |
| 		case 2: return random_ip6addr();
 | |
| 		}
 | |
| 
 | |
| 		break;
 | |
| 
 | |
| 	case 'uc-mac':
 | |
| 		return sprintf('02:%02x:%02x:%02x:%02x:%02x',
 | |
| 			math.rand() % 256, math.rand() % 256,
 | |
| 			math.rand() % 256, math.rand() % 256,
 | |
| 			math.rand() % 256);
 | |
| 
 | |
| 	case 'uc-cidr4':
 | |
| 		return sprintf('%s/%d',
 | |
| 			random_ip4addr(), math.rand() % 33);
 | |
| 
 | |
| 	case 'uc-cidr6':
 | |
| 		return sprintf('%s/%d',
 | |
| 			random_ip6addr(), math.rand() % 129);
 | |
| 
 | |
| 	case 'uc-cidr':
 | |
| 		switch (math.rand() % 2) {
 | |
| 		case 0: return sprintf('%s/%d', random_ip4addr(), math.rand() % 33);
 | |
| 		case 1: return sprintf('%s/%d', random_ip6addr(), math.rand() % 129);
 | |
| 		}
 | |
| 
 | |
| 		break;
 | |
| 
 | |
| 	case 'uc-base64':
 | |
| 		return b64enc(random_phrase(50, 100));
 | |
| 
 | |
| 	case 'uc-portrange':
 | |
| 		switch (math.rand() % 2) {
 | |
| 		case 0: return sprintf('%d', math.rand() % 65536);
 | |
| 		case 1: return join('-', sort([ math.rand() % 65536, math.rand() % 65536 ]));
 | |
| 		}
 | |
| 
 | |
| 		break;
 | |
| 
 | |
| 	default:
 | |
| 		if (minLength <= 15 && maxLength >= 20)
 | |
| 			return random_phrase(minLength, maxLength);
 | |
| 		else
 | |
| 			return random_string(minLength + (math.rand() % ((maxLength - minLength) + 1)));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| let GeneratorProto = {
 | |
| 	type_keywords: {
 | |
| 		number: [
 | |
| 			'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum',
 | |
| 			'multipleOf'
 | |
| 		],
 | |
| 		string: [
 | |
| 			'pattern', 'format', 'minLength', 'maxLength'
 | |
| 		],
 | |
| 		array: [
 | |
| 			'minItems', 'maxItems', 'items'
 | |
| 		],
 | |
| 		object: [
 | |
| 			'additionalProperties', 'minProperties', 'maxProperties',
 | |
| 			'properties', 'propertyNames'
 | |
| 		]
 | |
| 	},
 | |
| 
 | |
| 	is_ref: function(value)
 | |
| 	{
 | |
| 		return (
 | |
| 			type(value) == "object" &&
 | |
| 			exists(value, "$ref") &&
 | |
| 			//length(value) == 1 &&
 | |
| 			type(value["$ref"]) == "string"
 | |
| 		) ? value["$ref"] : null;
 | |
| 	},
 | |
| 
 | |
| 	is_number: function(value)
 | |
| 	{
 | |
| 		if (type(value) == "int" || type(value) == "double")
 | |
| 			return value;
 | |
| 
 | |
| 		return NaN;
 | |
| 	},
 | |
| 
 | |
| 	lookup_ptr: function(ptr)
 | |
| 	{
 | |
| 		let m = match(ptr, /^([^#]*)#(\/.*)$/);
 | |
| 
 | |
| 		if (!m)
 | |
| 			return null;
 | |
| 
 | |
| 		// Can't resolve cross-document references yet
 | |
| 		if (m[1] != "")
 | |
| 			return null;
 | |
| 
 | |
| 		let ref = null;
 | |
| 
 | |
| 		for (let idx, part in split(m[2], "/")) {
 | |
| 			part = replace(part, /~[01]/g, (m) => (m == "~0" ? "~" : "/" ));
 | |
| 
 | |
| 			if (idx == 0)
 | |
| 				ref = this.schema;
 | |
| 			else if (type(ref) == "object" || type(ref) == "array")
 | |
| 				ref = ref[part];
 | |
| 			else
 | |
| 				return null;
 | |
| 		}
 | |
| 
 | |
| 		return ref;
 | |
| 	},
 | |
| 
 | |
| 	infer_type: function(valueSpec)
 | |
| 	{
 | |
| 		if (exists(valueSpec, 'type')) {
 | |
| 			if (type(valueSpec.type) == 'array')
 | |
| 				return random_item(valueSpec.type);
 | |
| 
 | |
| 			return valueSpec.type;
 | |
| 		}
 | |
| 
 | |
| 		for (let type, keywords in this.type_keywords) {
 | |
| 			for (let keyword in keywords)
 | |
| 				if (exists(valueSpec, keyword))
 | |
| 					return type;
 | |
| 		}
 | |
| 
 | |
| 		return null;
 | |
| 	},
 | |
| 
 | |
| 	emit_generic: function(objectSpec)
 | |
| 	{
 | |
| 		if (is_array(objectSpec.enum))
 | |
| 			return random_item(objectSpec.enum);
 | |
| 
 | |
| 		if (exists(objectSpec, "const"))
 | |
| 			return objectSpec.const;
 | |
| 
 | |
| 		return NaN;
 | |
| 	},
 | |
| 
 | |
| 	emit_object: function(objectSpec)
 | |
| 	{
 | |
| 		let object = {};
 | |
| 
 | |
| 		if (type(objectSpec.properties) == "object") {
 | |
| 			for (let propertyName, propertySpec in objectSpec.properties) {
 | |
| 				let value = this.emit_spec(propertySpec);
 | |
| 
 | |
| 				object[propertyName] = value;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return object;
 | |
| 	},
 | |
| 
 | |
| 	emit_array: function(arraySpec)
 | |
| 	{
 | |
| 		let array = [];
 | |
| 
 | |
| 		if (type(arraySpec.items) == "object") {
 | |
| 			let len = 3;
 | |
| 
 | |
| 			if (type(arraySpec.minItems) == "int" && arraySpec.minItems > len)
 | |
| 				len = arraySpec.minItems;
 | |
| 
 | |
| 			if (type(arraySpec.maxItems) == "int" && arraySpec.maxItems < len)
 | |
| 				len = arraySpec.maxItems;
 | |
| 
 | |
| 			if (is_array(arraySpec.items.enum) && length(arraySpec.items.enum) < len)
 | |
| 				len = length(arraySpec.items.enum);
 | |
| 
 | |
| 			let examples = [ ...(is_array(arraySpec.items.examples) ? arraySpec.items.examples : []) ];
 | |
| 
 | |
| 			for (let i = 0; i < len; i++) {
 | |
| 				let item;
 | |
| 
 | |
| 				if (length(examples)) {
 | |
| 					item = random_item(examples);
 | |
| 					examples = filter(examples, (i) => i != item);
 | |
| 				}
 | |
| 				else {
 | |
| 					item = this.emit_spec(arraySpec.items, true);
 | |
| 				}
 | |
| 
 | |
| 				push(array, item);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		return array;
 | |
| 	},
 | |
| 
 | |
| 	emit_string: function(stringSpec, noExample)
 | |
| 	{
 | |
| 		let rv = this.emit_generic(stringSpec);
 | |
| 
 | |
| 		if (rv == rv)
 | |
| 			return rv;
 | |
| 
 | |
| 		if (!noExample && is_array(stringSpec.examples))
 | |
| 			return random_item(stringSpec.examples);
 | |
| 		else if (exists(stringSpec, "default"))
 | |
| 			return stringSpec.default;
 | |
| 
 | |
| 		let minLength = 0;
 | |
| 		let maxLength = 256;
 | |
| 
 | |
| 		if (type(stringSpec.minLength) == "int")
 | |
| 			minLength = stringSpec.minLength;
 | |
| 
 | |
| 		if (type(stringSpec.maxLength) == "int")
 | |
| 			maxLength = stringSpec.maxLength;
 | |
| 
 | |
| 		return random_value(stringSpec.format, minLength, maxLength);
 | |
| 	},
 | |
| 
 | |
| 	emit_number: function(numberSpec)
 | |
| 	{
 | |
| 		let rv = this.emit_generic(numberSpec);
 | |
| 
 | |
| 		if (rv == rv)
 | |
| 			return rv;
 | |
| 
 | |
| 		let multiplicator = 1;
 | |
| 
 | |
| 		if (type(numberSpec.default) == "double" ||
 | |
| 		    length(filter(numberSpec.examples, i => type(i) == "double")))
 | |
| 			multiplicator = 0.1;
 | |
| 
 | |
| 		if (this.is_number(numberSpec.multipleOf) > 0)
 | |
| 			multiplicator = numberSpec.multipleOf;
 | |
| 
 | |
| 		let min = this.is_number(numberSpec.minimum);
 | |
| 		let max = this.is_number(numberSpec.maximum);
 | |
| 		let exMin = this.is_number(numberSpec.exclusiveMinimum);
 | |
| 		let exMax = this.is_number(numberSpec.exclusiveMaximum);
 | |
| 
 | |
| 		if (is_array(numberSpec.examples))
 | |
| 			return random_item(numberSpec.examples);
 | |
| 
 | |
| 		while (true) {
 | |
| 			let n = multiplicator * math.rand();
 | |
| 
 | |
| 			if (max == max)
 | |
| 				n = n % max;
 | |
| 			else if (exMax == exMax)
 | |
| 				n = n % exMax;
 | |
| 
 | |
| 			if (min == min && n < min)
 | |
| 				continue;
 | |
| 
 | |
| 			if (exMin == exMin && n <= min)
 | |
| 				continue;
 | |
| 
 | |
| 			return n;
 | |
| 		}
 | |
| 	},
 | |
| 
 | |
| 	emit_boolean: function(boolSpec)
 | |
| 	{
 | |
| 		let rv = this.emit_generic(boolSpec);
 | |
| 
 | |
| 		if (rv == rv)
 | |
| 			return rv;
 | |
| 
 | |
| 		if (is_array(boolSpec.examples))
 | |
| 			return random_item(boolSpec.examples);
 | |
| 		else if (exists(boolSpec, "default"))
 | |
| 			return (boolSpec.default == true);
 | |
| 
 | |
| 		return ((math.rand() % 2) == 0);
 | |
| 	},
 | |
| 
 | |
| 	emit_spec: function(valueSpec, noExample)
 | |
| 	{
 | |
| 		let ref = this.is_ref(valueSpec);
 | |
| 
 | |
| 		if (ref) {
 | |
| 			let refValueSpec = this.lookup_ptr(ref);
 | |
| 
 | |
| 			assert(type(refValueSpec) == "object",
 | |
| 				"$ref value '"  + ref + "' points to non-object value");
 | |
| 
 | |
| 			valueSpec = { ...refValueSpec, ...valueSpec };
 | |
| 		}
 | |
| 
 | |
| 		let alternatives = valueSpec.anyOf || valueSpec.allOf || valueSpec.oneOf;
 | |
| 
 | |
| 		if (type(alternatives) == 'array' && length(alternatives) > 0)
 | |
| 			return this.emit_spec(alternatives[math.rand() % length(alternatives)], noExample);
 | |
| 
 | |
| 		switch (this.infer_type(valueSpec)) {
 | |
| 		case "object":
 | |
| 			return this.emit_object(valueSpec);
 | |
| 
 | |
| 		case "array":
 | |
| 			return this.emit_array(valueSpec);
 | |
| 
 | |
| 		case "string":
 | |
| 			return this.emit_string(valueSpec, noExample);
 | |
| 
 | |
| 		case "integer":
 | |
| 		case "number":
 | |
| 			return this.emit_number(valueSpec);
 | |
| 
 | |
| 		case "boolean":
 | |
| 			return this.emit_boolean(valueSpec);
 | |
| 
 | |
| 		default:
 | |
| 			warn("Unknown type " + valueSpec.type + " (" + valueSpec + ")\n");
 | |
| 		}
 | |
| 	},
 | |
| 
 | |
| 	read_schema: function(path)
 | |
| 	{
 | |
| 		let fd = fs.open(path, "r");
 | |
| 
 | |
| 		if (!fd)
 | |
| 			return {};
 | |
| 
 | |
| 		let schema = json(fd.read("all"));
 | |
| 
 | |
| 		fd.close();
 | |
| 
 | |
| 		return schema;
 | |
| 	},
 | |
| 
 | |
| 	generate: function()
 | |
| 	{
 | |
| 		this.schema = this.read_schema(this.path);
 | |
| 
 | |
| 		return this.emit_spec(this.schema);
 | |
| 	}
 | |
| };
 | |
| 
 | |
| function createGenerator(path) {
 | |
| 	return proto({ path }, GeneratorProto);
 | |
| }
 | |
| 
 | |
| let generator = createGenerator("./ucentral.schema.pretty.json");
 | |
| let document = generator.generate();
 | |
| 
 | |
| printf("%.J\n", document);
 |