Software /
code /
prosody
File
teal-src/util/jsonschema.tl @ 11455:a5050e21ab08
util.datamapper: Separate extraction of xml from coercion to target type
Now it gets the text, attribute or name first, then turns it into
whatever the schema wants. This should be easier to further factor out
into preparation for array support.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Sun, 14 Mar 2021 03:06:37 +0100 |
parent | 11449:dabd1fae0540 |
child | 11459:86904555bffc |
line wrap: on
line source
-- 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 -- local record schema_t enum type_e "null" "boolean" "object" "array" "number" "string" "integer" end type : type_e 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 -- numbers multipleOf : number maximum : number exclusiveMaximum : number minimum : number exclusiveMinimum : number -- strings maxLength : number minLength : number pattern : string format : string -- arrays prefixItems : { schema_t } items : schema_t contains : schema_t maxItems : number minItems : number uniqueItems : boolean maxContains : number minContains : number -- objects properties : { string : schema_t | type_e } maxProperties : number minProperties : number required : { string } dependentRequired : { string : { string } } additionalProperties: schema_t patternProperties: schema_t 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 end local type_e = schema_t.type_e -- TODO validator function per schema property local type_validators : { type_e : function (schema_t, any) : boolean } = {} local function simple_validate(schema : type_e, data : any) : boolean if 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 else return type(data) == schema end end type_validators.string = function (schema : schema_t, data : any) : boolean -- 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 return true end return false end type_validators.number = function (schema : schema_t, data : number) : boolean if schema.multipleOf and 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 return true end type_validators.integer = type_validators.number local function validate(schema : schema_t | type_e | boolean, data : any) : boolean if schema is boolean then return schema end if schema is type_e then return simple_validate(schema, data) end if schema is schema_t then if schema.allOf then for _, sub in ipairs(schema.allOf) do if not validate(sub, data) then return false end end return true end if schema.oneOf then local valid = 0 for _, sub in ipairs(schema.oneOf) do if validate(sub, data) then valid = valid + 1 end end return valid == 1 end if schema.anyOf then for _, sub in ipairs(schema.anyOf) do if validate(sub, data) then return true end end return false end if schema["not"] then if validate(schema["not"], data) then return false end end if schema["if"] then if validate(schema["if"], data) then if schema["then"] then return validate(schema["then"], data) end else if schema["else"] then return validate(schema["else"], data) end end end if schema.const ~= nil and schema.const ~= data then return false end if schema["enum"] ~= nil then for _, v in ipairs(schema["enum"]) do if v == data then return true end end return false end if schema.type then if not simple_validate(schema.type, data) then return false end local validator = type_validators[schema.type] if validator then return validator(schema, data) end end return true end end type_validators.table = function (schema : schema_t, data : any) : boolean 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.properties then local additional : schema_t | boolean = schema.additionalProperties or true for k, v in pairs(data) do if schema.propertyNames and not validate(schema.propertyNames, k) then return false end local s = schema.properties[k as string] or additional as schema_t if not validate(s, v) then return false end end elseif schema.additionalProperties then for k, v in pairs(data) do if schema.propertyNames and not validate(schema.propertyNames, k) then return false end if not validate(schema.additionalProperties, v) then return false 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 then for i, s in ipairs(schema.prefixItems) do if validate(s, data[i]) then p = i else return false end end end if schema.items then for i = p+1, #data do if not validate(schema.items, data[i]) then return false end end end if schema.contains then local found = false for i = 1, #data do if validate(schema.contains, data[i]) then found = true break end end if not found then return false end end return true end return false end type_validators.object = function (schema : schema_t, data : any) : boolean if data is table then for k in pairs(data) do if not k is string then return false end end return type_validators.table(schema, data) end return false end type_validators.array = function (schema : schema_t, data : any) : boolean if data is table then -- just check that there the keys are all numbers for i in pairs(data) do if not i is number then return false end end return type_validators.table(schema, data) end return false end return { validate = validate; schema_t = schema_t; }