util.datamapper: Improve handling of schemas with non-obvious "type"

The JSON Schema specification says that schemas are objects or booleans,
and that the 'type' property is optional and can be an array.

This module previously allowed bare type names as schemas and did not
really handle booleans.

It now handles missing 'type' properties and boolean 'true' as a schema.
Objects and arrays are guessed based on the presence of 'properties' or
'items' field.
This commit is contained in:
Kim Alvefur 2022-07-08 17:32:48 +02:00
parent e700edc50f
commit 89359b70dc
3 changed files with 63 additions and 25 deletions

View file

@ -25,7 +25,7 @@ describe("util.datamapper", function()
from = attr();
type = attr();
id = attr();
body = "string";
body = true; -- should be assumed to be a string
lang = {type = "string"; xml = {attribute = true; prefix = "xml"}};
delay = {
type = "object";
@ -56,7 +56,8 @@ describe("util.datamapper", function()
xml = {namespace = "urn:xmpp:reactions:0"; name = "reactions"};
properties = {
to = {type = "string"; xml = {attribute = true; name = "id"}};
reactions = {type = "array"; items = {type = "string"; xml = {name = "reaction"}}};
-- should be assumed to be array since it has 'items'
reactions = { items = { xml = { name = "reaction" } } };
};
};
stanza_ids = {
@ -190,7 +191,8 @@ describe("util.datamapper", function()
version = {
type = "object";
xml = {name = "query"; namespace = "jabber:iq:version"};
properties = {name = "string"; version = "string"; os = "string"};
-- properties should be assumed to be strings
properties = {name = true; version = {}; os = {}};
};
};
};

View file

@ -25,7 +25,7 @@ local pointer = require"util.jsonpointer";
local json_type_name = json.json_type_name;
local json_schema_object = require "util.jsonschema"
local type schema_t = boolean | json_type_name | json_schema_object
local type schema_t = boolean | json_schema_object
local function toboolean ( s : string ) : boolean
if s == "true" or s == "1" then
@ -59,15 +59,28 @@ local enum value_goes
end
local function resolve_schema(schema : schema_t, root : json_schema_object) : schema_t
if schema is json_schema_object and 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 then
return referenced
if schema is json_schema_object then
if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
return pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t;
end
end
return schema;
end
local function guess_schema_type(schema : json_schema_object) : json_type_name
local schema_types = schema.type
if schema_types is json_type_name then
return schema_types
elseif schema_types ~= nil then
error "schema has unsupported 'type' property"
elseif schema.properties then
return "object"
elseif schema.items then
return "array"
end
return "string" -- default assumption
end
local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string )
: json_type_name, value_goes, string, string, string, string, { any }
local proptype : json_type_name = "string"
@ -79,9 +92,9 @@ local function unpack_propschema( propschema : schema_t, propname : string, curr
local enums : { any }
if propschema is json_schema_object then
proptype = propschema.type
elseif propschema is json_type_name then
proptype = propschema
proptype = guess_schema_type(propschema);
elseif propschema is string then -- Teal says this can never be a string, but it could before so best be sure
error("schema as string is not supported: "..propschema.." {"..current_ns.."}"..propname)
end
if proptype == "object" or proptype == "array" then
@ -120,6 +133,10 @@ local function unpack_propschema( propschema : schema_t, propname : string, curr
end
end
if current_ns == "urn:xmpp:reactions:0" and name == "reactions" then
assert(proptype=="array")
end
return proptype, value_where, name, namespace, prefix, single_attribute, enums
end
@ -239,9 +256,10 @@ function parse_array (schema : json_schema_object, s : st.stanza_t, root : json_
end
local function parse (schema : json_schema_object, s : st.stanza_t) : table
if schema.type == "object" then
local s_type = guess_schema_type(schema)
if s_type == "object" then
return parse_object(schema, s, schema)
elseif schema.type == "array" then
elseif s_type == "array" then
return parse_array(schema, s, schema)
else
error "top-level scalars unsupported"
@ -333,7 +351,8 @@ function unparse ( schema : json_schema_object, t : table, current_name : string
local out = ctx or st.stanza(current_name, { xmlns = current_ns })
if schema.type == "object" then
local s_type = guess_schema_type(schema)
if s_type == "object" then
for prop, propschema in pairs(schema.properties) do
propschema = resolve_schema(propschema, root)
@ -346,7 +365,7 @@ function unparse ( schema : json_schema_object, t : table, current_name : string
end
return out;
elseif schema.type == "array" then
elseif s_type == "array" then
local itemschema = resolve_schema(schema.items, root)
local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns)
for _, item in ipairs(t as { string }) do

View file

@ -1,3 +1,5 @@
-- This file is generated from teal-src/util/datamapper.lua
local st = require("util.stanza");
local pointer = require("util.jsonpointer");
@ -29,15 +31,28 @@ end
local value_goes = {}
local function resolve_schema(schema, root)
if type(schema) == "table" and schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
local referenced = pointer.resolve(root, schema["$ref"]:sub(2));
if referenced ~= nil then
return referenced
if type(schema) == "table" then
if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
return pointer.resolve(root, schema["$ref"]:sub(2))
end
end
return schema
end
local function guess_schema_type(schema)
local schema_types = schema.type
if type(schema_types) == "string" then
return schema_types
elseif schema_types ~= nil then
error("schema has unsupported 'type' property")
elseif schema.properties then
return "object"
elseif schema.items then
return "array"
end
return "string"
end
local function unpack_propschema(propschema, propname, current_ns)
local proptype = "string"
@ -49,9 +64,9 @@ local function unpack_propschema(propschema, propname, current_ns)
local enums
if type(propschema) == "table" then
proptype = propschema.type
proptype = guess_schema_type(propschema);
elseif type(propschema) == "string" then
proptype = propschema
error("schema as string is not supported: " .. propschema .. " {" .. current_ns .. "}" .. propname)
end
if proptype == "object" or proptype == "array" then
@ -209,9 +224,10 @@ function parse_array(schema, s, root)
end
local function parse(schema, s)
if schema.type == "object" then
local s_type = guess_schema_type(schema)
if s_type == "object" then
return parse_object(schema, s, schema)
elseif schema.type == "array" then
elseif s_type == "array" then
return parse_array(schema, s, schema)
else
error("top-level scalars unsupported")
@ -306,7 +322,8 @@ function unparse(schema, t, current_name, current_ns, ctx, root)
local out = ctx or st.stanza(current_name, {xmlns = current_ns})
if schema.type == "object" then
local s_type = guess_schema_type(schema)
if s_type == "object" then
for prop, propschema in pairs(schema.properties) do
propschema = resolve_schema(propschema, root)
@ -319,7 +336,7 @@ function unparse(schema, t, current_name, current_ns, ctx, root)
end
return out
elseif schema.type == "array" then
elseif s_type == "array" then
local itemschema = resolve_schema(schema.items, root)
local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns)
for _, item in ipairs(t) do