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