teal-src/util/jsonschema.tl
changeset 12983 fbbf4f0db8f0
parent 12982 088d278c75b5
child 12984 6ebad8e16b3b
--- a/teal-src/util/jsonschema.tl	Fri Mar 17 19:38:39 2023 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,376 +0,0 @@
--- Copyright (C) 2021 Kim Alvefur
---
--- This project is MIT/X11 licensed. Please see the
--- COPYING file in the source package for more information.
---
--- Based on
--- https://json-schema.org/draft/2020-12/json-schema-core.html
--- https://json-schema.org/draft/2020-12/json-schema-validation.html
---
-
-if not math.type then require "util.mathcompat" end
-
-local json = require"util.json"
-local null = json.null;
-
-local pointer = require "util.jsonpointer"
-
-local type json_type_name = json.json_type_name
-
--- json_type_name here is non-standard
-local type schema_t = boolean | json_schema_object
-
-local record json_schema_object
-	type json_type_name = json.json_type_name
-	type schema_object = json_schema_object
-
-	type : json_type_name | { json_type_name }
-	enum : { any }
-	const : any
-
-	allOf : { schema_t }
-	anyOf : { schema_t }
-	oneOf : { schema_t }
-
-	["not"] : schema_t
-	["if"] : schema_t
-	["then"] : schema_t
-	["else"] : schema_t
-
-	["$ref"] : string
-
-	-- numbers
-	multipleOf : number
-	maximum : number
-	exclusiveMaximum : number
-	minimum : number
-	exclusiveMinimum : number
-
-	-- strings
-	maxLength : integer
-	minLength : integer
-	pattern : string -- NYI
-	format : string
-
-	-- arrays
-	prefixItems : { schema_t }
-	items : schema_t
-	contains : schema_t
-	maxItems : integer
-	minItems : integer
-	uniqueItems : boolean
-	maxContains : integer -- NYI
-	minContains : integer -- NYI
-
-	-- objects
-	properties : { string : schema_t }
-	maxProperties : integer -- NYI
-	minProperties : integer -- NYI
-	required : { string }
-	dependentRequired : { string : { string } }
-	additionalProperties: schema_t
-	patternProperties: schema_t -- NYI
-	propertyNames : schema_t
-
-	-- xml
-	record xml_t
-		name : string
-		namespace : string
-		prefix : string
-		attribute : boolean
-		wrapped : boolean
-
-		-- nonstantard, maybe in the future
-		text : boolean
-		x_name_is_value : boolean
-		x_single_attribute : string
-	end
-
-	xml : xml_t
-
-	-- descriptive
-	title : string
-	description : string
-	deprecated : boolean
-	readOnly : boolean
-	writeOnly : boolean
-
-	-- methods
-	validate : function ( schema_t, any, json_schema_object ) : boolean
-end
-
--- TODO validator function per schema property
-
-local function simple_validate(schema : json_type_name | { json_type_name }, data : any) : boolean
-	if schema == nil then
-		return true
-	elseif schema == "object" and data is table then
-		return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "string")
-	elseif schema == "array" and data is table then
-		return type(data) == "table" and (next(data)==nil or type((next(data, nil))) == "number")
-	elseif schema == "integer" then
-		return math.type(data) == schema
-	elseif schema == "null" then
-		return data == null
-	elseif schema is { json_type_name } then
-		for _, one in ipairs(schema as { json_type_name }) do
-			if simple_validate(one, data) then
-				return true
-			end
-		end
-		return false
-	else
-		return type(data) == schema
-	end
-end
-
-local complex_validate : function ( json_schema_object, any, json_schema_object ) : boolean
-
-local function validate (schema : schema_t, data : any, root : json_schema_object) : boolean
-	if schema is boolean then
-		return schema
-	else
-		return complex_validate(schema, data, root)
-	end
-end
-
-function complex_validate (schema : json_schema_object, data : any, root : json_schema_object) : boolean
-
-	if root == nil then
-		root = schema
-	end
-
-	if schema["$ref"] and schema["$ref"]:sub(1,1) == "#" then
-		local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t
-		if referenced ~= nil and referenced ~= root and referenced ~= schema then
-			if not validate(referenced, data, root) then
-				return false;
-			end
-		end
-	end
-
-	if not simple_validate(schema.type, data) then
-		return false;
-	end
-
-	if schema.type == "object" then
-		if data is table then
-			-- just check that there the keys are all strings
-			for k in pairs(data) do
-				if not k is string then
-					return false
-				end
-			end
-		end
-	end
-
-	if schema.type == "array" then
-		if data is table then
-			-- just check that there the keys are all numbers
-			for i in pairs(data) do
-				if not i is integer then
-					return false
-				end
-			end
-		end
-	end
-
-	if schema["enum"] ~= nil then
-		local match = false
-		for _, v in ipairs(schema["enum"]) do
-			if v == data then
-				-- FIXME supposed to do deep-compare
-				match = true
-				break
-			end
-		end
-		if not match then
-			return false
-		end
-	end
-
-	-- XXX this is measured in byte, while JSON measures in ... bork
-	-- TODO use utf8.len?
-	if data is string then
-		if schema.maxLength and #data > schema.maxLength then
-			return false
-		end
-		if schema.minLength and #data < schema.minLength then
-			return false
-		end
-	end
-
-	if data is number then
-		if schema.multipleOf and (data == 0 or data % schema.multipleOf ~= 0) then
-			return false
-		end
-
-		if schema.maximum and not ( data <= schema.maximum ) then
-			return false
-		end
-
-		if schema.exclusiveMaximum and not ( data < schema.exclusiveMaximum ) then
-			return false
-		end
-
-		if schema.minimum and not ( data >= schema.minimum ) then
-			return false
-		end
-
-		if schema.exclusiveMinimum and not ( data > schema.exclusiveMinimum ) then
-			return false
-		end
-	end
-
-	if schema.allOf then
-		for _, sub in ipairs(schema.allOf) do
-			if not validate(sub, data, root) then
-				return false
-			end
-		end
-	end
-
-	if schema.oneOf then
-		local valid = 0
-		for _, sub in ipairs(schema.oneOf) do
-			if validate(sub, data, root) then
-				valid = valid + 1
-			end
-		end
-		if valid ~= 1 then
-			return false
-		end
-	end
-
-	if schema.anyOf then
-		local match = false
-		for _, sub in ipairs(schema.anyOf) do
-			if validate(sub, data, root) then
-				match = true
-				break
-			end
-		end
-		if not match then
-			return false
-		end
-	end
-
-	if schema["not"] then
-		if validate(schema["not"], data, root) then
-			return false
-		end
-	end
-
-	if schema["if"] ~= nil then
-		if validate(schema["if"], data, root) then
-			if schema["then"] then
-				return validate(schema["then"], data, root)
-			end
-		else
-			if schema["else"] then
-				return validate(schema["else"], data, root)
-			end
-		end
-	end
-
-	if schema.const ~= nil and schema.const ~= data then
-		return false
-	end
-
-	if data is table then
-
-		if schema.maxItems and #data > schema.maxItems then
-			return false
-		end
-
-		if schema.minItems and #data < schema.minItems then
-			return false
-		end
-
-		if schema.required then
-			for _, k in ipairs(schema.required) do
-				if data[k] == nil then
-					return false
-				end
-			end
-		end
-
-		if schema.propertyNames ~= nil then
-			for k in pairs(data) do
-				if not validate(schema.propertyNames, k, root) then
-					return false
-				end
-			end
-		end
-
-		if schema.properties then
-			for k, sub in pairs(schema.properties) do
-				if data[k] ~= nil and not validate(sub, data[k], root) then
-					return false
-				end
-			end
-		end
-
-		if schema.additionalProperties ~= nil then
-			for k, v in pairs(data) do
-				if schema.properties == nil or schema.properties[k as string] == nil then
-					if not validate(schema.additionalProperties, v, root) then
-						return false
-					end
-				end
-			end
-		end
-
-		if schema.uniqueItems then
-			-- only works for scalars, would need to deep-compare for objects/arrays/tables
-			local values : { any : boolean } = {}
-			for _, v in pairs(data) do
-				if values[v] then
-					return false
-				end
-				values[v] = true
-			end
-		end
-
-		local p = 0
-		if schema.prefixItems ~= nil then
-			for i, s in ipairs(schema.prefixItems) do
-				if data[i] == nil then
-					break
-				elseif validate(s, data[i], root) then
-					p = i
-				else
-					return false
-				end
-			end
-		end
-
-		if schema.items ~= nil then
-			for i = p+1, #data do
-				if not validate(schema.items, data[i], root) then
-					return false
-				end
-			end
-		end
-
-		if schema.contains ~= nil then
-			local found = false
-			for i = 1, #data do
-				if validate(schema.contains, data[i], root) then
-					found = true
-					break
-				end
-			end
-			if not found then
-				return false
-			end
-		end
-	end
-
-	return true;
-end
-
-
-json_schema_object.validate = validate;
-
-return json_schema_object;