Changeset

4518:073f5397c1d2

mod_rest: Replace most mappings by using util.datamapper All the stuff now goes into a JSON file that describes the mappings.
author Kim Alvefur <zash@zash.se>
date Sun, 21 Mar 2021 23:54:06 +0100
parents 4517:d6a3201a65c0
children 4519:ea1fd703bb27
files mod_rest/README.markdown mod_rest/jsonmap.lib.lua mod_rest/res/schema-xmpp.json
diffstat 3 files changed, 238 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- a/mod_rest/README.markdown	Mon Mar 22 21:32:43 2021 +0100
+++ b/mod_rest/README.markdown	Sun Mar 21 23:54:06 2021 +0100
@@ -8,6 +8,7 @@
       mod_rest.jsonmap: jsonmap.lib.lua
     copy_directories:
     - example
+    - res
 ---
 
 # Introduction
--- a/mod_rest/jsonmap.lib.lua	Mon Mar 22 21:32:43 2021 +0100
+++ b/mod_rest/jsonmap.lib.lua	Sun Mar 21 23:54:06 2021 +0100
@@ -3,37 +3,26 @@
 local json = require "util.json";
 local st = require "util.stanza";
 local xml = require "util.xml";
+local map = require "util.datamapper";
 
+local schema do
+	local f = assert(module:load_resource("res/schema-xmpp.json"));
+	schema = json.decode(f:read("*a"))
+	f:close();
+	-- Copy common properties to all stanza kinds
+	if schema._common then
+		for key, prop in pairs(schema._common) do
+			for _, copyto in pairs(schema.properties) do
+				copyto.properties[key] = prop;
+			end
+		end
+		schema._common = nil;
+	end
+end
+
+-- Some mappings that are still hard to do in a nice way with util.datamapper
 local field_mappings; -- in scope for "func" mappings
 field_mappings = {
-	-- top level stanza attributes
-	-- needed here to mark them as known fields
-	kind = "attr",
-	type = "attr",
-	to = "attr",
-	from = "attr",
-	id = "attr",
-	lang = "attr",
-
-	-- basic message
-	body = "text_tag",
-	subject = "text_tag",
-	thread = "text_tag",
-
-	-- basic presence
-	show = "text_tag",
-	status = "text_tag",
-	priority = "text_tag",
-
-	state = { type = "name", xmlns = "http://jabber.org/protocol/chatstates" },
-	nick = { type = "text_tag", xmlns = "http://jabber.org/protocol/nick", tagname = "nick" },
-	delay = { type = "attr", xmlns = "urn:xmpp:delay", tagname = "delay", attr = "stamp" },
-	replace = { type = "attr", xmlns = "urn:xmpp:message-correct:0", tagname = "replace", attr = "id" },
-
-	-- XEP-0045 MUC
-	-- TODO history, password, ???
-	join = { type = "bool_tag", xmlns = "http://jabber.org/protocol/muc", tagname = "x" },
-
 	-- XEP-0071
 	html = {
 		type = "func", xmlns = "http://jabber.org/protocol/xhtml-im", tagname = "html",
@@ -47,31 +36,6 @@
 		end;
 	};
 
-	-- XEP-0199: XMPP Ping
-	ping = { type = "bool_tag", xmlns = "urn:xmpp:ping", tagname = "ping" },
-
-	-- XEP-0092: Software Version
-	version = { type = "func", xmlns = "jabber:iq:version", tagname = "query",
-		st2json = function (s)
-			return {
-				name = s:get_child_text("name");
-				version = s:get_child_text("version");
-				os = s:get_child_text("os");
-			}
-		end,
-		json2st = function (s)
-			local v = st.stanza("query", { xmlns = "jabber:iq:version" });
-			if type(s) == "table" then
-				v:text_tag("name", s.name);
-				v:text_tag("version", s.version);
-				if s.os then
-					v:text_tag("os", s.os);
-				end
-			end
-			return v;
-		end
-	};
-
 	-- XEP-0030
 	disco = {
 		type = "func", xmlns = "http://jabber.org/protocol/disco#info", tagname = "query",
@@ -228,6 +192,7 @@
 	};
 
 	-- XEP-0066: Out of Band Data
+	-- TODO Replace by oob.url in datamapper schema
 	oob_url = { type = "func", xmlns = "jabber:x:oob", tagname = "x",
 		-- XXX namespace depends on whether it's in an iq or message stanza
 		st2json = function (s)
@@ -416,6 +381,10 @@
 
 local byxmlname = {};
 for k, spec in pairs(field_mappings) do
+	for _, replace in pairs(schema.properties) do
+		replace.properties[k] = nil
+	end
+
 	if type(spec) == "table" then
 		spec.key = k;
 		if spec.xmlns and spec.tagname then
@@ -461,14 +430,8 @@
 }
 
 local function st2json(s)
-	local t = {
-		kind = s.name,
-		type = s.attr.type,
-		to = s.attr.to,
-		from = s.attr.from,
-		id = s.attr.id,
-		lang = s.attr["xml:lang"],
-	};
+	local t = map.parse(schema.properties[s.name], s);
+
 	if s.name == "presence" and not s.attr.type then
 		t.type = "available";
 	end
@@ -501,17 +464,7 @@
 			mapping = byxmlname[prefix];
 		end
 
-		if not mapping then -- luacheck: ignore 542
-			-- pass
-		elseif mapping.type == "text_tag" then
-			t[mapping.key] = tag:get_text();
-		elseif mapping.type == "name" then
-			t[mapping.key] = tag.name;
-		elseif mapping.type == "attr" then
-			t[mapping.key] = tag.attr[mapping.attr];
-		elseif mapping.type == "bool_tag" then
-			t[mapping.key] = true;
-		elseif mapping.type == "func" and mapping.st2json then
+		if mapping and mapping.type == "func" and mapping.st2json then
 			t[mapping.key] = mapping.st2json(tag);
 		end
 	end
@@ -547,27 +500,17 @@
 		end
 	end
 
-	if t_type == "available" then
+	if kind == "presence" and t_type == "available" then
 		t_type = nil;
+	elseif kind == "iq" and not t_type then
+		t_type = "get";
 	end
 
-	local s = st.stanza(kind or "message", {
-		type = t_type;
-		to = str(t.to) and jid.prep(t.to);
-		from = str(t.to) and jid.prep(t.from);
-		id = str(t.id),
-		["xml:lang"] = str(t.lang),
-	});
+	local s = map.unparse(schema.properties[kind or "message"], t);
 
-	if t.to and not s.attr.to then
-		return nil, "invalid-jid-to";
-	end
-	if t.from and not s.attr.from then
-		return nil, "invalid-jid-from";
-	end
-	if kind == "iq" and not s.attr.type then
-		s.attr.type = "get";
-	end
+	s.attr.type = t_type;
+	s.attr.to = str(t.to) and jid.prep(t.to);
+	s.attr.from = str(t.to) and jid.prep(t.from);
 
 	if type(t.error) == "table" then
 		return st.error_reply(st.reply(s), str(t.error.type), str(t.error.condition), str(t.error.text), str(t.error.by));
@@ -578,25 +521,9 @@
 
 	for k, v in pairs(t) do
 		local mapping = field_mappings[k];
-		if mapping then
-			if mapping == "text_tag" then
-				s:text_tag(k, v);
-			elseif mapping == "attr" then -- luacheck: ignore 542
-				-- handled already
-			elseif mapping.type == "text_tag" then
-				s:text_tag(mapping.tagname or k, v, mapping.xmlns and { xmlns = mapping.xmlns });
-			elseif mapping.type == "name" then
-				s:tag(v, { xmlns = mapping.xmlns }):up();
-			elseif mapping.type == "attr" then
-				s:tag(mapping.tagname or k, { xmlns = mapping.xmlns, [mapping.attr or k] = v }):up();
-			elseif mapping.type == "bool_tag" then
-				s:tag(mapping.tagname or k, { xmlns = mapping.xmlns }):up();
-			elseif mapping.type == "func" and mapping.json2st then
+		if mapping and mapping.type == "func" and mapping.json2st then
 				s:add_child(mapping.json2st(v)):up();
 			end
-		else
-			return nil, "unknown-field";
-		end
 	end
 
 	s:reset();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mod_rest/res/schema-xmpp.json	Sun Mar 21 23:54:06 2021 +0100
@@ -0,0 +1,204 @@
+{
+   "_common" : {
+      "delay" : {
+         "format" : "date-time",
+         "title" : "XEP-0203: Delayed Delivery",
+         "type" : "string",
+         "xml" : {
+            "name" : "delay",
+            "namespace" : "urn:xmpp:delay",
+            "x_single_attribute" : "stamp"
+         }
+      },
+      "from" : {
+         "description" : "the sender of the stanza",
+         "example" : "bob@example.net",
+         "format" : "xmpp-jid",
+         "type" : "string",
+         "xml" : {
+            "attribute" : true
+         }
+      },
+      "id" : {
+         "description" : "Reasonably unique id. mod_rest generates one if left out.",
+         "type" : "string",
+         "xml" : {
+            "attribute" : true
+         }
+      },
+      "lang" : {
+         "description" : "Language code",
+         "example" : "en",
+         "type" : "string",
+         "xml" : {
+            "attribute" : true,
+            "prefix" : "xml"
+         }
+      },
+      "nick" : {
+         "type" : "string",
+         "xml" : {
+            "name" : "nick",
+            "namespace" : "http://jabber.org/protocol/nick"
+         }
+      },
+      "to" : {
+         "description" : "the intended recipient for the stanza",
+         "example" : "alice@another.example",
+         "format" : "xmpp-jid",
+         "type" : "string",
+         "xml" : {
+            "attribute" : true
+         }
+      },
+      "type" : {
+         "description" : "Stanza type",
+         "type" : "string",
+         "xml" : {
+            "attribute" : true
+         }
+      }
+   },
+   "properties" : {
+      "iq" : {
+         "properties" : {
+            "ping" : {
+               "description" : "Test reachability of some XMPP address",
+               "enum" : [
+                  true
+               ],
+               "title" : "XEP-0199: XMPP Ping",
+               "type" : "boolean",
+               "xml" : {
+                  "name" : "ping",
+                  "namespace" : "urn:xmpp:ping",
+                  "x_name_is_value" : true
+               }
+            },
+            "version" : {
+               "description" : "Ask about software version information",
+               "properties" : {
+                  "name" : {
+                     "example" : "My Software",
+                     "type" : "string"
+                  },
+                  "os" : {
+                     "example" : "Linux",
+                     "type" : "string"
+                  },
+                  "version" : {
+                     "example" : "1.0.0",
+                     "type" : "string"
+                  }
+               },
+               "required" : [
+                  "name",
+                  "version"
+               ],
+               "title" : "XEP-0092: Software Version",
+               "type" : "object",
+               "xml" : {
+                  "name" : "query",
+                  "namespace" : "jabber:iq:version"
+               }
+            }
+         },
+         "type" : "object",
+         "xml" : {
+            "name" : "iq"
+         }
+      },
+      "message" : {
+         "properties" : {
+            "body" : {
+               "description" : "Human-readable chat message",
+               "example" : "Hello, World!",
+               "type" : "string"
+            },
+            "replace" : {
+               "description" : "For indicating that a message is a correction of the last sent message.",
+               "title" : "XEP-0308: Last Message Correction",
+               "type" : "string",
+               "xml" : {
+                  "name" : "replace",
+                  "namespace" : "urn:xmpp:message-correct:0",
+                  "x_single_attribute" : "id"
+               }
+            },
+            "state" : {
+               "description" : "Chat state notifications, e.g. \"is typing...\"",
+               "enum" : [
+                  "active",
+                  "inactive",
+                  "gone",
+                  "composing",
+                  "paused"
+               ],
+               "type" : "string",
+               "xml" : {
+                  "namespace" : "http://jabber.org/protocol/chatstates",
+                  "x_name_is_value" : true
+               }
+            },
+            "subject" : {
+               "description" : "Subject of message or group chat",
+               "example" : "Talking about stuff",
+               "type" : "string"
+            },
+            "thread" : {
+               "description" : "Message thread identifier",
+               "properties" : {
+                  "id" : {
+                     "type" : "string",
+                     "xml" : {
+                        "text" : true
+                     }
+                  },
+                  "parent" : {
+                     "type" : "string",
+                     "xml" : {
+                        "attribute" : true
+                     }
+                  }
+               },
+               "type" : "object"
+            }
+         },
+         "type" : "object",
+         "xml" : {
+            "name" : "message"
+         }
+      },
+      "presence" : {
+         "properties" : {
+            "priority" : {
+               "description" : "Presence priority",
+               "type" : "string"
+            },
+            "show" : {
+               "description" : "indicator of availability, ie away or not",
+               "enum" : [
+                  "away",
+                  "chat",
+                  "dnd",
+                  "xa"
+               ],
+               "type" : "string"
+            },
+            "status" : {
+               "description" : "Textual status message.",
+               "type" : "string"
+            }
+         },
+         "type" : "object",
+         "xml" : {
+            "name" : "presence"
+         }
+      }
+   },
+   "type" : "object",
+   "xml" : {
+      "name" : "xmpp",
+      "namespace" : "jabber:client"
+   }
+}