Software /
code /
prosody
Comparison
teal-src/util/datamapper.tl @ 12133:11060c8919b6
util.datamapper: Add support for $ref pointers
Allows reuse of repetitive definitions in schemas.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Wed, 29 Dec 2021 17:57:09 +0100 |
parent | 11481:b2f9782497dd |
child | 12580:a9dbf657c894 |
comparison
equal
deleted
inserted
replaced
12132:4ff0d33dfb2b | 12133:11060c8919b6 |
---|---|
19 -- TODO s/number/integer/ once we have appropriate math.type() compat | 19 -- TODO s/number/integer/ once we have appropriate math.type() compat |
20 -- | 20 -- |
21 | 21 |
22 local st = require "util.stanza"; | 22 local st = require "util.stanza"; |
23 local json = require"util.json" | 23 local json = require"util.json" |
24 local pointer = require"util.jsonpointer"; | |
24 | 25 |
25 local json_type_name = json.json_type_name; | 26 local json_type_name = json.json_type_name; |
26 local json_schema_object = require "util.jsonschema" | 27 local json_schema_object = require "util.jsonschema" |
27 local type schema_t = boolean | json_type_name | json_schema_object | 28 local type schema_t = boolean | json_type_name | json_schema_object |
28 | 29 |
53 "in_text_tag" | 54 "in_text_tag" |
54 "in_attribute" | 55 "in_attribute" |
55 "in_single_attribute" | 56 "in_single_attribute" |
56 "in_children" | 57 "in_children" |
57 "in_wrapper" | 58 "in_wrapper" |
59 end | |
60 | |
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 | |
63 local referenced = pointer.resolve(root as table, schema["$ref"]:sub(2)) as schema_t; | |
64 if referenced ~= nil then | |
65 return referenced | |
66 end | |
67 end | |
68 return schema; | |
58 end | 69 end |
59 | 70 |
60 local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string ) | 71 local function unpack_propschema( propschema : schema_t, propname : string, current_ns : string ) |
61 : json_type_name, value_goes, string, string, string, string, { any } | 72 : json_type_name, value_goes, string, string, string, string, { any } |
62 local proptype : json_type_name = "string" | 73 local proptype : json_type_name = "string" |
110 end | 121 end |
111 | 122 |
112 return proptype, value_where, name, namespace, prefix, single_attribute, enums | 123 return proptype, value_where, name, namespace, prefix, single_attribute, enums |
113 end | 124 end |
114 | 125 |
115 local parse_object : function (schema : schema_t, s : st.stanza_t) : { string : any } | 126 local parse_object : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any } |
116 local parse_array : function (schema : schema_t, s : st.stanza_t) : { any } | 127 local parse_array : function (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { any } |
117 | 128 |
118 local function extract_value (s : st.stanza_t, value_where : value_goes, proptype : json.json_type_name, name : string, namespace : string, prefix : string, single_attribute : string, enums : { any }) : string | 129 local function extract_value (s : st.stanza_t, value_where : value_goes, proptype : json.json_type_name, name : string, namespace : string, prefix : string, single_attribute : string, enums : { any }) : string |
119 if value_where == "in_tag_name" then | 130 if value_where == "in_tag_name" then |
120 local c : st.stanza_t | 131 local c : st.stanza_t |
121 if proptype == "boolean" then | 132 if proptype == "boolean" then |
152 elseif value_where == "in_text_tag" then | 163 elseif value_where == "in_text_tag" then |
153 return s:get_child_text(name, namespace) | 164 return s:get_child_text(name, namespace) |
154 end | 165 end |
155 end | 166 end |
156 | 167 |
157 function parse_object (schema : schema_t, s : st.stanza_t) : { string : any } | 168 function parse_object (schema : schema_t, s : st.stanza_t, root : json_schema_object) : { string : any } |
158 local out : { string : any } = {} | 169 local out : { string : any } = {} |
170 schema = resolve_schema(schema, root) | |
159 if schema is json_schema_object and schema.properties then | 171 if schema is json_schema_object and schema.properties then |
160 for prop, propschema in pairs(schema.properties) do | 172 for prop, propschema in pairs(schema.properties) do |
173 propschema = resolve_schema(propschema, root) | |
161 | 174 |
162 local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns) | 175 local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns) |
163 | 176 |
164 if value_where == "in_children" and propschema is json_schema_object then | 177 if value_where == "in_children" and propschema is json_schema_object then |
165 if proptype == "object" then | 178 if proptype == "object" then |
166 local c = s:get_child(name, namespace) | 179 local c = s:get_child(name, namespace) |
167 if c then | 180 if c then |
168 out[prop] = parse_object(propschema, c); | 181 out[prop] = parse_object(propschema, c, root); |
169 end | 182 end |
170 elseif proptype == "array" then | 183 elseif proptype == "array" then |
171 local a = parse_array(propschema, s); | 184 local a = parse_array(propschema, s, root); |
172 if a and a[1] ~= nil then | 185 if a and a[1] ~= nil then |
173 out[prop] = a; | 186 out[prop] = a; |
174 end | 187 end |
175 else | 188 else |
176 error "unreachable" | 189 error "unreachable" |
177 end | 190 end |
178 elseif value_where == "in_wrapper" and propschema is json_schema_object and proptype == "array" then | 191 elseif value_where == "in_wrapper" and propschema is json_schema_object and proptype == "array" then |
179 local wrapper = s:get_child(name, namespace); | 192 local wrapper = s:get_child(name, namespace); |
180 if wrapper then | 193 if wrapper then |
181 out[prop] = parse_array(propschema, wrapper); | 194 out[prop] = parse_array(propschema, wrapper, root); |
182 end | 195 end |
183 else | 196 else |
184 local value : string = extract_value (s, value_where, proptype, name, namespace, prefix, single_attribute, enums) | 197 local value : string = extract_value (s, value_where, proptype, name, namespace, prefix, single_attribute, enums) |
185 | 198 |
186 out[prop] = totype(proptype, value) | 199 out[prop] = totype(proptype, value) |
189 end | 202 end |
190 | 203 |
191 return out | 204 return out |
192 end | 205 end |
193 | 206 |
194 function parse_array (schema : json_schema_object, s : st.stanza_t) : { any } | 207 function parse_array (schema : json_schema_object, s : st.stanza_t, root : json_schema_object) : { any } |
195 local itemschema : schema_t = schema.items; | 208 local itemschema : schema_t = resolve_schema(schema.items, root); |
196 local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns) | 209 local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns) |
197 local attr_name : string | 210 local attr_name : string |
198 if value_where == "in_single_attribute" then -- FIXME this shouldn't be needed | 211 if value_where == "in_single_attribute" then -- FIXME this shouldn't be needed |
199 value_where = "in_attribute"; | 212 value_where = "in_attribute"; |
200 attr_name = single_attribute; | 213 attr_name = single_attribute; |
202 local out : { any } = {} | 215 local out : { any } = {} |
203 | 216 |
204 if proptype == "object" then | 217 if proptype == "object" then |
205 if itemschema is json_schema_object then | 218 if itemschema is json_schema_object then |
206 for c in s:childtags(child_name, namespace) do | 219 for c in s:childtags(child_name, namespace) do |
207 table.insert(out, parse_object(itemschema, c)); | 220 table.insert(out, parse_object(itemschema, c, root)); |
208 end | 221 end |
209 else | 222 else |
210 error "array items must be schema object" | 223 error "array items must be schema object" |
211 end | 224 end |
212 elseif proptype == "array" then | 225 elseif proptype == "array" then |
213 if itemschema is json_schema_object then | 226 if itemschema is json_schema_object then |
214 for c in s:childtags(child_name, namespace) do | 227 for c in s:childtags(child_name, namespace) do |
215 table.insert(out, parse_array(itemschema, c)); | 228 table.insert(out, parse_array(itemschema, c, root)); |
216 end | 229 end |
217 end | 230 end |
218 else | 231 else |
219 for c in s:childtags(child_name, namespace) do | 232 for c in s:childtags(child_name, namespace) do |
220 local value : string = extract_value (c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums) | 233 local value : string = extract_value (c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums) |
225 return out; | 238 return out; |
226 end | 239 end |
227 | 240 |
228 local function parse (schema : json_schema_object, s : st.stanza_t) : table | 241 local function parse (schema : json_schema_object, s : st.stanza_t) : table |
229 if schema.type == "object" then | 242 if schema.type == "object" then |
230 return parse_object(schema, s) | 243 return parse_object(schema, s, schema) |
231 elseif schema.type == "array" then | 244 elseif schema.type == "array" then |
232 return parse_array(schema, s) | 245 return parse_array(schema, s, schema) |
233 else | 246 else |
234 error "top-level scalars unsupported" | 247 error "top-level scalars unsupported" |
235 end | 248 end |
236 end | 249 end |
237 | 250 |
245 elseif proptype == "boolean" then | 258 elseif proptype == "boolean" then |
246 return v and "1" or "0" | 259 return v and "1" or "0" |
247 end | 260 end |
248 end | 261 end |
249 | 262 |
250 local unparse : function (json_schema_object, table, string, string, st.stanza_t) : st.stanza_t | 263 local unparse : function (json_schema_object, table, string, string, st.stanza_t, json_schema_object) : st.stanza_t |
251 | 264 |
252 local function unparse_property(out : st.stanza_t, v : any, proptype : json_type_name, propschema : schema_t, value_where : value_goes, name : string, namespace : string, current_ns : string, prefix : string, single_attribute : string) | 265 local function unparse_property(out : st.stanza_t, v : any, proptype : json_type_name, propschema : schema_t, value_where : value_goes, name : string, namespace : string, current_ns : string, prefix : string, single_attribute : string, root : json_schema_object) |
266 | |
253 if value_where == "in_attribute" then | 267 if value_where == "in_attribute" then |
254 local attr = name | 268 local attr = name |
255 if prefix then | 269 if prefix then |
256 attr = prefix .. ':' .. name | 270 attr = prefix .. ':' .. name |
257 elseif namespace and namespace ~= current_ns then | 271 elseif namespace and namespace ~= current_ns then |
282 out:tag(v, propattr):up(); | 296 out:tag(v, propattr):up(); |
283 elseif proptype == "boolean" and v == true then | 297 elseif proptype == "boolean" and v == true then |
284 out:tag(name, propattr):up(); | 298 out:tag(name, propattr):up(); |
285 end | 299 end |
286 elseif proptype == "object" and propschema is json_schema_object and v is table then | 300 elseif proptype == "object" and propschema is json_schema_object and v is table then |
287 local c = unparse(propschema, v, name, namespace); | 301 local c = unparse(propschema, v, name, namespace, nil, root); |
288 if c then | 302 if c then |
289 out:add_direct_child(c); | 303 out:add_direct_child(c); |
290 end | 304 end |
291 elseif proptype == "array" and propschema is json_schema_object and v is table then | 305 elseif proptype == "array" and propschema is json_schema_object and v is table then |
292 if value_where == "in_wrapper" then | 306 if value_where == "in_wrapper" then |
293 local c = unparse(propschema, v, name, namespace); | 307 local c = unparse(propschema, v, name, namespace, nil, root); |
294 if c then | 308 if c then |
295 out:add_direct_child(c); | 309 out:add_direct_child(c); |
296 end | 310 end |
297 else | 311 else |
298 unparse(propschema, v, name, namespace, out); | 312 unparse(propschema, v, name, namespace, out, root); |
299 end | 313 end |
300 else | 314 else |
301 out:text_tag(name, toxmlstring(proptype, v), propattr) | 315 out:text_tag(name, toxmlstring(proptype, v), propattr) |
302 end | 316 end |
303 end | 317 end |
304 end | 318 end |
305 | 319 |
306 function unparse ( schema : json_schema_object, t : table, current_name : string, current_ns : string, ctx : st.stanza_t ) : st.stanza_t | 320 function unparse ( schema : json_schema_object, t : table, current_name : string, current_ns : string, ctx : st.stanza_t, root : json_schema_object ) : st.stanza_t |
321 | |
322 if root == nil then root = schema end | |
307 | 323 |
308 if schema.xml then | 324 if schema.xml then |
309 if schema.xml.name then | 325 if schema.xml.name then |
310 current_name = schema.xml.name | 326 current_name = schema.xml.name |
311 end | 327 end |
318 local out = ctx or st.stanza(current_name, { xmlns = current_ns }) | 334 local out = ctx or st.stanza(current_name, { xmlns = current_ns }) |
319 | 335 |
320 if schema.type == "object" then | 336 if schema.type == "object" then |
321 | 337 |
322 for prop, propschema in pairs(schema.properties) do | 338 for prop, propschema in pairs(schema.properties) do |
339 propschema = resolve_schema(propschema, root) | |
323 local v = t[prop] | 340 local v = t[prop] |
324 | 341 |
325 if v ~= nil then | 342 if v ~= nil then |
326 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns) | 343 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns) |
327 unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute) | 344 unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) |
328 end | 345 end |
329 end | 346 end |
330 return out; | 347 return out; |
331 | 348 |
332 elseif schema.type == "array" then | 349 elseif schema.type == "array" then |
333 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(schema.items, current_name, current_ns) | 350 local itemschema = resolve_schema(schema.items, root) |
351 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns) | |
334 for _, item in ipairs(t as { string }) do | 352 for _, item in ipairs(t as { string }) do |
335 unparse_property(out, item, proptype, schema.items, value_where, name, namespace, current_ns, prefix, single_attribute) | 353 unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) |
336 end | 354 end |
337 return out; | 355 return out; |
338 end | 356 end |
339 end | 357 end |
340 | 358 |