Software /
code /
prosody
Comparison
util/datamapper.lua @ 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 |
---|---|
1 local st = require("util.stanza"); | 1 local st = require("util.stanza"); |
2 local pointer = require("util.jsonpointer"); | |
2 | 3 |
3 local schema_t = {} | 4 local schema_t = {} |
4 | 5 |
5 local function toboolean(s) | 6 local function toboolean(s) |
6 if s == "true" or s == "1" then | 7 if s == "true" or s == "1" then |
24 return tonumber(s) | 25 return tonumber(s) |
25 end | 26 end |
26 end | 27 end |
27 | 28 |
28 local value_goes = {} | 29 local value_goes = {} |
30 | |
31 local function resolve_schema(schema, root) | |
32 if type(schema) == "table" and schema["$ref"] and schema["$ref"]:sub(1, 1) == "#" then | |
33 local referenced = pointer.resolve(root, schema["$ref"]:sub(2)); | |
34 if referenced ~= nil then | |
35 return referenced | |
36 end | |
37 end | |
38 return schema | |
39 end | |
29 | 40 |
30 local function unpack_propschema(propschema, propname, current_ns) | 41 local function unpack_propschema(propschema, propname, current_ns) |
31 | 42 |
32 local proptype = "string" | 43 local proptype = "string" |
33 local value_where = propname and "in_text_tag" or "in_text" | 44 local value_where = propname and "in_text_tag" or "in_text" |
122 elseif value_where == "in_text_tag" then | 133 elseif value_where == "in_text_tag" then |
123 return s:get_child_text(name, namespace) | 134 return s:get_child_text(name, namespace) |
124 end | 135 end |
125 end | 136 end |
126 | 137 |
127 function parse_object(schema, s) | 138 function parse_object(schema, s, root) |
128 local out = {} | 139 local out = {} |
140 schema = resolve_schema(schema, root) | |
129 if type(schema) == "table" and schema.properties then | 141 if type(schema) == "table" and schema.properties then |
130 for prop, propschema in pairs(schema.properties) do | 142 for prop, propschema in pairs(schema.properties) do |
143 propschema = resolve_schema(propschema, root) | |
131 | 144 |
132 local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns) | 145 local proptype, value_where, name, namespace, prefix, single_attribute, enums = unpack_propschema(propschema, prop, s.attr.xmlns) |
133 | 146 |
134 if value_where == "in_children" and type(propschema) == "table" then | 147 if value_where == "in_children" and type(propschema) == "table" then |
135 if proptype == "object" then | 148 if proptype == "object" then |
136 local c = s:get_child(name, namespace) | 149 local c = s:get_child(name, namespace) |
137 if c then | 150 if c then |
138 out[prop] = parse_object(propschema, c); | 151 out[prop] = parse_object(propschema, c, root); |
139 end | 152 end |
140 elseif proptype == "array" then | 153 elseif proptype == "array" then |
141 local a = parse_array(propschema, s); | 154 local a = parse_array(propschema, s, root); |
142 if a and a[1] ~= nil then | 155 if a and a[1] ~= nil then |
143 out[prop] = a; | 156 out[prop] = a; |
144 end | 157 end |
145 else | 158 else |
146 error("unreachable") | 159 error("unreachable") |
147 end | 160 end |
148 elseif value_where == "in_wrapper" and type(propschema) == "table" and proptype == "array" then | 161 elseif value_where == "in_wrapper" and type(propschema) == "table" and proptype == "array" then |
149 local wrapper = s:get_child(name, namespace); | 162 local wrapper = s:get_child(name, namespace); |
150 if wrapper then | 163 if wrapper then |
151 out[prop] = parse_array(propschema, wrapper); | 164 out[prop] = parse_array(propschema, wrapper, root); |
152 end | 165 end |
153 else | 166 else |
154 local value = extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums) | 167 local value = extract_value(s, value_where, proptype, name, namespace, prefix, single_attribute, enums) |
155 | 168 |
156 out[prop] = totype(proptype, value) | 169 out[prop] = totype(proptype, value) |
159 end | 172 end |
160 | 173 |
161 return out | 174 return out |
162 end | 175 end |
163 | 176 |
164 function parse_array(schema, s) | 177 function parse_array(schema, s, root) |
165 local itemschema = schema.items; | 178 local itemschema = resolve_schema(schema.items, root); |
166 local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns) | 179 local proptype, value_where, child_name, namespace, prefix, single_attribute, enums = unpack_propschema(itemschema, nil, s.attr.xmlns) |
167 local attr_name | 180 local attr_name |
168 if value_where == "in_single_attribute" then | 181 if value_where == "in_single_attribute" then |
169 value_where = "in_attribute"; | 182 value_where = "in_attribute"; |
170 attr_name = single_attribute; | 183 attr_name = single_attribute; |
172 local out = {} | 185 local out = {} |
173 | 186 |
174 if proptype == "object" then | 187 if proptype == "object" then |
175 if type(itemschema) == "table" then | 188 if type(itemschema) == "table" then |
176 for c in s:childtags(child_name, namespace) do | 189 for c in s:childtags(child_name, namespace) do |
177 table.insert(out, parse_object(itemschema, c)); | 190 table.insert(out, parse_object(itemschema, c, root)); |
178 end | 191 end |
179 else | 192 else |
180 error("array items must be schema object") | 193 error("array items must be schema object") |
181 end | 194 end |
182 elseif proptype == "array" then | 195 elseif proptype == "array" then |
183 if type(itemschema) == "table" then | 196 if type(itemschema) == "table" then |
184 for c in s:childtags(child_name, namespace) do | 197 for c in s:childtags(child_name, namespace) do |
185 table.insert(out, parse_array(itemschema, c)); | 198 table.insert(out, parse_array(itemschema, c, root)); |
186 end | 199 end |
187 end | 200 end |
188 else | 201 else |
189 for c in s:childtags(child_name, namespace) do | 202 for c in s:childtags(child_name, namespace) do |
190 local value = extract_value(c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums) | 203 local value = extract_value(c, value_where, proptype, attr_name or child_name, namespace, prefix, single_attribute, enums) |
195 return out | 208 return out |
196 end | 209 end |
197 | 210 |
198 local function parse(schema, s) | 211 local function parse(schema, s) |
199 if schema.type == "object" then | 212 if schema.type == "object" then |
200 return parse_object(schema, s) | 213 return parse_object(schema, s, schema) |
201 elseif schema.type == "array" then | 214 elseif schema.type == "array" then |
202 return parse_array(schema, s) | 215 return parse_array(schema, s, schema) |
203 else | 216 else |
204 error("top-level scalars unsupported") | 217 error("top-level scalars unsupported") |
205 end | 218 end |
206 end | 219 end |
207 | 220 |
217 end | 230 end |
218 end | 231 end |
219 | 232 |
220 local unparse | 233 local unparse |
221 | 234 |
222 local function unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute) | 235 local function unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, |
236 single_attribute, root) | |
237 | |
223 if value_where == "in_attribute" then | 238 if value_where == "in_attribute" then |
224 local attr = name | 239 local attr = name |
225 if prefix then | 240 if prefix then |
226 attr = prefix .. ":" .. name | 241 attr = prefix .. ":" .. name |
227 elseif namespace and namespace ~= current_ns then | 242 elseif namespace and namespace ~= current_ns then |
252 out:tag(v, propattr):up(); | 267 out:tag(v, propattr):up(); |
253 elseif proptype == "boolean" and v == true then | 268 elseif proptype == "boolean" and v == true then |
254 out:tag(name, propattr):up(); | 269 out:tag(name, propattr):up(); |
255 end | 270 end |
256 elseif proptype == "object" and type(propschema) == "table" and type(v) == "table" then | 271 elseif proptype == "object" and type(propschema) == "table" and type(v) == "table" then |
257 local c = unparse(propschema, v, name, namespace); | 272 local c = unparse(propschema, v, name, namespace, nil, root); |
258 if c then | 273 if c then |
259 out:add_direct_child(c); | 274 out:add_direct_child(c); |
260 end | 275 end |
261 elseif proptype == "array" and type(propschema) == "table" and type(v) == "table" then | 276 elseif proptype == "array" and type(propschema) == "table" and type(v) == "table" then |
262 if value_where == "in_wrapper" then | 277 if value_where == "in_wrapper" then |
263 local c = unparse(propschema, v, name, namespace); | 278 local c = unparse(propschema, v, name, namespace, nil, root); |
264 if c then | 279 if c then |
265 out:add_direct_child(c); | 280 out:add_direct_child(c); |
266 end | 281 end |
267 else | 282 else |
268 unparse(propschema, v, name, namespace, out); | 283 unparse(propschema, v, name, namespace, out, root); |
269 end | 284 end |
270 else | 285 else |
271 out:text_tag(name, toxmlstring(proptype, v), propattr) | 286 out:text_tag(name, toxmlstring(proptype, v), propattr) |
272 end | 287 end |
273 end | 288 end |
274 end | 289 end |
275 | 290 |
276 function unparse(schema, t, current_name, current_ns, ctx) | 291 function unparse(schema, t, current_name, current_ns, ctx, root) |
292 | |
293 if root == nil then | |
294 root = schema | |
295 end | |
277 | 296 |
278 if schema.xml then | 297 if schema.xml then |
279 if schema.xml.name then | 298 if schema.xml.name then |
280 current_name = schema.xml.name | 299 current_name = schema.xml.name |
281 end | 300 end |
288 local out = ctx or st.stanza(current_name, {xmlns = current_ns}) | 307 local out = ctx or st.stanza(current_name, {xmlns = current_ns}) |
289 | 308 |
290 if schema.type == "object" then | 309 if schema.type == "object" then |
291 | 310 |
292 for prop, propschema in pairs(schema.properties) do | 311 for prop, propschema in pairs(schema.properties) do |
312 propschema = resolve_schema(propschema, root) | |
293 local v = t[prop] | 313 local v = t[prop] |
294 | 314 |
295 if v ~= nil then | 315 if v ~= nil then |
296 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns) | 316 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(propschema, prop, current_ns) |
297 unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute) | 317 unparse_property(out, v, proptype, propschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) |
298 end | 318 end |
299 end | 319 end |
300 return out | 320 return out |
301 | 321 |
302 elseif schema.type == "array" then | 322 elseif schema.type == "array" then |
303 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(schema.items, current_name, current_ns) | 323 local itemschema = resolve_schema(schema.items, root) |
324 local proptype, value_where, name, namespace, prefix, single_attribute = unpack_propschema(itemschema, current_name, current_ns) | |
304 for _, item in ipairs(t) do | 325 for _, item in ipairs(t) do |
305 unparse_property(out, item, proptype, schema.items, value_where, name, namespace, current_ns, prefix, single_attribute) | 326 unparse_property(out, item, proptype, itemschema, value_where, name, namespace, current_ns, prefix, single_attribute, root) |
306 end | 327 end |
307 return out | 328 return out |
308 end | 329 end |
309 end | 330 end |
310 | 331 |