aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKim Alvefur <zash@zash.se>2021-03-07 00:57:36 +0100
committerKim Alvefur <zash@zash.se>2021-03-07 00:57:36 +0100
commit081eb23c54b685c01e531468e6ba59740d779878 (patch)
treeda41100aef148a93053fd4d07677ec7661172a45
parent02da1378f86be8afd5f0fda367f1466072898786 (diff)
downloadprosody-081eb23c54b685c01e531468e6ba59740d779878.tar.gz
prosody-081eb23c54b685c01e531468e6ba59740d779878.zip
util.datamapper: Library for extracting data from stanzas
Based on the XML support in the OpenAPI specification.
-rw-r--r--.luacheckrc3
-rw-r--r--spec/util_datamapper_spec.lua56
-rw-r--r--teal-src/util/datamapper.tl100
-rw-r--r--util/datamapper.lua96
4 files changed, 255 insertions, 0 deletions
diff --git a/.luacheckrc b/.luacheckrc
index a2a64474..43a508f8 100644
--- a/.luacheckrc
+++ b/.luacheckrc
@@ -29,6 +29,9 @@ files["util/"] = {
files["util/jsonschema.lua"] = {
ignore = { "211" };
}
+files["util/datamapper.lua"] = {
+ ignore = { "211" };
+}
files["plugins/"] = {
module = true;
allow_defined_top = true;
diff --git a/spec/util_datamapper_spec.lua b/spec/util_datamapper_spec.lua
new file mode 100644
index 00000000..0e1dfdab
--- /dev/null
+++ b/spec/util_datamapper_spec.lua
@@ -0,0 +1,56 @@
+local xml
+local map
+
+setup(function()
+ xml = require "util.xml";
+ map = require "util.datamapper";
+end);
+
+describe("util.datampper", function()
+
+ local s, x, d
+ setup(function()
+
+ local function attr() return {type = "string"; xml = {attribute = true}} end
+ s = {
+ type = "object";
+ xml = {name = "message"; namespace = "jabber:client"};
+ properties = {
+ to = attr();
+ from = attr();
+ type = attr();
+ id = attr();
+ body = "string";
+ lang = {type = "string"; xml = {attribute = true; prefix = "xml"}};
+ delay = {
+ type = "object";
+ xml = {namespace = "urn:xmpp:delay"; name = "delay"};
+ properties = {stamp = attr(); from = attr(); reason = {type = "string"; xml = {text = true}}};
+ };
+ };
+ };
+
+ x = xml.parse [[
+ <message xmlns="jabber:client" xml:lang="en" to="a@test" from="b@test" type="chat" id="1">
+ <body>Hello</body>
+ <delay xmlns='urn:xmpp:delay' from='test' stamp='2021-03-07T15:59:08+00:00'>Becasue</delay>
+ </message>
+ ]];
+
+ d = {
+ to = "a@test";
+ from = "b@test";
+ type = "chat";
+ id = "1";
+ lang = "en";
+ body = "Hello";
+ delay = {from = "test"; stamp = "2021-03-07T15:59:08+00:00"; reason = "Becasue"};
+ };
+ end);
+
+ describe("parse", function()
+ it("works", function()
+ assert.same(d, map.parse(s, x));
+ end);
+ end);
+end)
diff --git a/teal-src/util/datamapper.tl b/teal-src/util/datamapper.tl
new file mode 100644
index 00000000..b58f94ae
--- /dev/null
+++ b/teal-src/util/datamapper.tl
@@ -0,0 +1,100 @@
+local st = require "util.stanza";
+local js = require "util.jsonschema"
+
+local function toboolean ( s : string ) : boolean
+ if s == "true" or s == "1" then
+ return true
+ elseif s == "false" or s == "0" then
+ return false
+ end
+end
+
+local function parse_object (schema : js.schema_t, s : st.stanza_t) : table
+ local out : { string : any } = {}
+ if schema.properties then
+ for prop, propschema in pairs(schema.properties) do
+ -- TODO factor out, if it's generic enough
+ local name = prop
+ local namespace = s.attr.xmlns;
+ local prefix : string = nil
+ local is_attribute = false
+ local is_text = false
+
+ local proptype : js.schema_t.type_e
+ if propschema is js.schema_t then
+ proptype = propschema.type
+ elseif propschema is js.schema_t.type_e then
+ proptype = propschema
+ end
+
+ if propschema is js.schema_t and propschema.xml then
+ if propschema.xml.name then
+ name = propschema.xml.name
+ end
+ if propschema.xml.namespace then
+ namespace = propschema.xml.namespace
+ end
+ if propschema.xml.prefix then
+ prefix = propschema.xml.prefix
+ end
+ if propschema.xml.attribute then
+ is_attribute = true
+ elseif propschema.xml.text then
+ is_text = true
+ end
+ end
+
+ if is_attribute then
+ local attr = name
+ if prefix then
+ attr = prefix .. ':' .. name
+ elseif namespace ~= s.attr.xmlns then
+ attr = namespace .. "\1" .. name
+ end
+ if proptype == "string" then
+ out[prop] = s.attr[attr]
+ elseif proptype == "integer" or proptype == "number" then
+ -- TODO floor if integer ?
+ out[prop] = tonumber(s.attr[attr])
+ elseif proptype == "boolean" then
+ out[prop] = toboolean(s.attr[attr])
+ -- else TODO
+ end
+
+ elseif is_text then
+ if proptype == "string" then
+ out[prop] = s:get_text()
+ elseif proptype == "integer" or proptype == "number" then
+ out[prop] = tonumber(s:get_text())
+ end
+
+ else
+
+ if proptype == "string" then
+ out[prop] = s:get_child_text(name, namespace)
+ elseif proptype == "integer" or proptype == "number" then
+ out[prop] = tonumber(s:get_child_text(name, namespace))
+ elseif proptype == "object" and propschema is js.schema_t then
+ local c = s:get_child(name, namespace)
+ if c then
+ out[prop] = parse_object(propschema, c);
+ end
+ -- else TODO
+ end
+ end
+ end
+ end
+
+ return out
+end
+
+local function parse (schema : js.schema_t, s : st.stanza_t) : table
+ if schema.type == "object" then
+ return parse_object(schema, s)
+ end
+end
+
+return {
+ parse = parse,
+ -- unparse = unparse, -- TODO
+}
diff --git a/util/datamapper.lua b/util/datamapper.lua
new file mode 100644
index 00000000..05f4da8c
--- /dev/null
+++ b/util/datamapper.lua
@@ -0,0 +1,96 @@
+local st = require("util.stanza");
+
+local function toboolean(s)
+ if s == "true" or s == "1" then
+ return true
+ elseif s == "false" or s == "0" then
+ return false
+ end
+end
+
+local function parse_object(schema, s)
+ local out = {}
+ if schema.properties then
+ for prop, propschema in pairs(schema.properties) do
+
+ local name = prop
+ local namespace = s.attr.xmlns;
+ local prefix = nil
+ local is_attribute = false
+ local is_text = false
+
+ local proptype
+ if type(propschema) == "table" then
+ proptype = propschema.type
+ elseif type(propschema) == "string" then
+ proptype = propschema
+ end
+
+ if type(propschema) == "table" and propschema.xml then
+ if propschema.xml.name then
+ name = propschema.xml.name
+ end
+ if propschema.xml.namespace then
+ namespace = propschema.xml.namespace
+ end
+ if propschema.xml.prefix then
+ prefix = propschema.xml.prefix
+ end
+ if propschema.xml.attribute then
+ is_attribute = true
+ elseif propschema.xml.text then
+ is_text = true
+ end
+ end
+
+ if is_attribute then
+ local attr = name
+ if prefix then
+ attr = prefix .. ":" .. name
+ elseif namespace ~= s.attr.xmlns then
+ attr = namespace .. "\1" .. name
+ end
+ if proptype == "string" then
+ out[prop] = s.attr[attr]
+ elseif proptype == "integer" or proptype == "number" then
+
+ out[prop] = tonumber(s.attr[attr])
+ elseif proptype == "boolean" then
+ out[prop] = toboolean(s.attr[attr])
+
+ end
+
+ elseif is_text then
+ if proptype == "string" then
+ out[prop] = s:get_text()
+ elseif proptype == "integer" or proptype == "number" then
+ out[prop] = tonumber(s:get_text())
+ end
+
+ else
+
+ if proptype == "string" then
+ out[prop] = s:get_child_text(name, namespace)
+ elseif proptype == "integer" or proptype == "number" then
+ out[prop] = tonumber(s:get_child_text(name, namespace))
+ elseif proptype == "object" and type(propschema) == "table" then
+ local c = s:get_child(name, namespace)
+ if c then
+ out[prop] = parse_object(propschema, c);
+ end
+
+ end
+ end
+ end
+ end
+
+ return out
+end
+
+local function parse(schema, s)
+ if schema.type == "object" then
+ return parse_object(schema, s)
+ end
+end
+
+return {parse = parse}