Comparison

teal-src/util/datamapper.tl @ 12580:a9dbf657c894 0.12

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.
author Kim Alvefur <zash@zash.se>
date Fri, 08 Jul 2022 17:32:48 +0200
parent 12133:11060c8919b6
child 12776:4715301a3504
comparison
equal deleted inserted replaced
12579:ca6a43fe0231 12580:a9dbf657c894
23 local json = require"util.json" 23 local json = require"util.json"
24 local pointer = require"util.jsonpointer"; 24 local pointer = require"util.jsonpointer";
25 25
26 local json_type_name = json.json_type_name; 26 local json_type_name = json.json_type_name;
27 local json_schema_object = require "util.jsonschema" 27 local json_schema_object = require "util.jsonschema"
28 local type schema_t = boolean | json_type_name | json_schema_object 28 local type schema_t = boolean | json_schema_object
29 29
30 local function toboolean ( s : string ) : boolean 30 local function toboolean ( s : string ) : boolean
31 if s == "true" or s == "1" then 31 if s == "true" or s == "1" then
32 return true 32 return true
33 elseif s == "false" or s == "0" then 33 elseif s == "false" or s == "0" then
57 "in_children" 57 "in_children"
58 "in_wrapper" 58 "in_wrapper"
59 end 59 end
60 60
61 local function resolve_schema(schema : schema_t, root : json_schema_object) : schema_t 61 local function resolve_schema(schema : schema_t, root : json_schema_object) : schema_t
62 if schema is json_schema_object and schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then 62 if schema is json_schema_object then
63 local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t; 63 if schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then
64 if referenced ~= nil then 64 return pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t;
65 return referenced
66 end 65 end
67 end 66 end
68 return schema; 67 return schema;
68 end
69
70 local function guess_schema_type(schema : json_schema_object) : json_type_name
71 local schema_types = schema.type
72 if schema_types is json_type_name then
73 return schema_types
74 elseif schema_types ~= nil then
75 error "schema has unsupported 'type' property"
76 elseif schema.properties then
77 return "object"
78 elseif schema.items then
79 return "array"
80 end
81 return "string" -- default assumption
69 end 82 end
70 83
71 local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string ) 84 local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string )
72 : json_type_name, value_goes, string, string, string, string, { any } 85 : json_type_name, value_goes, string, string, string, string, { any }
73 local proptype : json_type_name = "string" 86 local proptype : json_type_name = "string"
77 local prefix : string 90 local prefix : string
78 local single_attribute : string 91 local single_attribute : string
79 local enums : { any } 92 local enums : { any }
80 93
81 if propschema is json_schema_object then 94 if propschema is json_schema_object then
82 proptype = propschema.type 95 proptype = guess_schema_type(propschema);
83 elseif propschema is json_type_name then 96 elseif propschema is string then -- Teal says this can never be a string, but it could before so best be sure
84 proptype = propschema 97 error("schema as string is not supported: "..propschema.." {"..current_ns.."}"..propname)
85 end 98 end
86 99
87 if proptype == "object" or proptype == "array" then 100 if proptype == "object" or proptype == "array" then
88 value_where = "in_children" 101 value_where = "in_children"
89 end 102 end
116 if propschema["const"] then 129 if propschema["const"] then
117 enums = { propschema["const"] } 130 enums = { propschema["const"] }
118 elseif propschema["enum"] then 131 elseif propschema["enum"] then
119 enums = propschema["enum"] 132 enums = propschema["enum"]
120 end 133 end
134 end
135
136 if current_ns == "urn:xmpp:reactions:0" and name == "reactions" then
137 assert(proptype=="array")
121 end 138 end
122 139
123 return proptype, value_where, name, namespace, prefix, single_attribute, enums 140 return proptype, value_where, name, namespace, prefix, single_attribute, enums
124 end 141 end
125 142
237 end 254 end
238 return out; 255 return out;
239 end 256 end
240 257
241 local function parse (schema : json_schema_object, s : st.stanza_t) : table 258 local function parse (schema : json_schema_object, s : st.stanza_t) : table
242 if schema.type == "object" then 259 local s_type = guess_schema_type(schema)
260 if s_type == "object" then
243 return parse_object(schema, s, schema) 261 return parse_object(schema, s, schema)
244 elseif schema.type == "array" then 262 elseif s_type == "array" then
245 return parse_array(schema, s, schema) 263 return parse_array(schema, s, schema)
246 else 264 else
247 error "top-level scalars unsupported" 265 error "top-level scalars unsupported"
248 end 266 end
249 end 267 end
331 -- TODO prefix? 349 -- TODO prefix?
332 end 350 end
333 351
334 local out = ctx or st.stanza(current_name, { xmlns = current_ns }) 352 local out = ctx or st.stanza(current_name, { xmlns = current_ns })
335 353
336 if schema.type == "object" then 354 local s_type = guess_schema_type(schema)
355 if s_type == "object" then
337 356
338 for prop, propschema in pairs(schema.properties) do 357 for prop, propschema in pairs(schema.properties) do
339 propschema = resolve_schema(propschema, root) 358 propschema = resolve_schema(propschema, root)
340 local v = t[prop] 359 local v = t[prop]
341 360
344 unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) 363 unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
345 end 364 end
346 end 365 end
347 return out; 366 return out;
348 367
349 elseif schema.type == "array" then 368 elseif s_type == "array" then
350 local itemschema = resolve_schema(schema.items, root) 369 local itemschema = resolve_schema(schema.items, root)
351 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns) 370 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns)
352 for _, item in ipairs(t as { string }) do 371 for _, item in ipairs(t as { string }) do
353 unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) 372 unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root)
354 end 373 end