Software /
code /
prosody
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 |