Mercurial > prosody-hg
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 |
| parents | b2f9782497dd |
| children | 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 |
