util.datamapper: Add initial support for parsing arrays
authorKim Alvefur <zash@zash.se>
Thu, 18 Mar 2021 12:57:25 +0100
changeset 11461 6a51749af7f4
parent 11460 4e376a43fe40
child 11462 0e00fa518688
util.datamapper: Add initial support for parsing arrays
spec/util_datamapper_spec.lua
teal-src/util/datamapper.tl
util/datamapper.lua
--- a/spec/util_datamapper_spec.lua	Sun Mar 14 16:50:49 2021 +0100
+++ b/spec/util_datamapper_spec.lua	Thu Mar 18 12:57:25 2021 +0100
@@ -46,6 +46,11 @@
 					type = "string";
 					xml = {name = "origin-id"; namespace = "urn:xmpp:sid:0"; x_single_attribute = "id"};
 				};
+				reactions = {
+					type = "array";
+					xml = {namespace = "urn:xmpp:reactions:0"; wrapped = true};
+					items = {type = "string"; xml = {name = "reaction"}};
+				};
 			};
 		};
 
@@ -57,6 +62,10 @@
 				<active xmlns='http://jabber.org/protocol/chatstates'/>
 				<fallback xmlns='urn:xmpp:fallback:0'/>
 				<origin-id xmlns='urn:xmpp:sid:0' id='qgkmMdPB'/>
+				<reactions id='744f6e18-a57a-11e9-a656-4889e7820c76' xmlns='urn:xmpp:reactions:0'>
+					<reaction>👋</reaction>
+					<reaction>🐢</reaction>
+				</reactions>
 				</message>
 				]];
 
@@ -71,6 +80,10 @@
 			state = "active";
 			fallback = true;
 			origin_id = "qgkmMdPB";
+			reactions = {
+				"👋",
+				"🐢",
+			};
 		};
 	end);
 
@@ -85,11 +98,21 @@
 			local u = map.unparse(s, d);
 			assert.equal("message", u.name);
 			assert.same(x.attr, u.attr);
-			assert.equal(#x.tags-1, #u.tags)
 			assert.equal(x:get_child_text("body"), u:get_child_text("body"));
 			assert.equal(x:get_child_text("delay", "urn:xmpp:delay"), u:get_child_text("delay", "urn:xmpp:delay"));
 			assert.same(x:get_child("delay", "urn:xmpp:delay").attr, u:get_child("delay", "urn:xmpp:delay").attr);
 			assert.same(x:get_child("origin-id", "urn:xmpp:sid:0").attr, u:get_child("origin-id", "urn:xmpp:sid:0").attr);
+			for _, tag in ipairs(x.tags) do
+				if tag.name ~= "UNRELATED" and tag.name ~= "reactions" then
+					assert.truthy(u:get_child(tag.name, tag.attr.xmlns) or u:get_child(tag.name), tag:top_tag())
+				end
+			end
+			assert.equal(#x.tags-2, #u.tags)
+
+			pending("arrays", function ()
+				assert.truthy(u:get_child("reactions", "urn:xmpp:reactions:0"))
+			end);
+
 		end);
 	end);
 end)
--- a/teal-src/util/datamapper.tl	Sun Mar 14 16:50:49 2021 +0100
+++ b/teal-src/util/datamapper.tl	Thu Mar 18 12:57:25 2021 +0100
@@ -49,6 +49,7 @@
 	"in_attribute"
 	"in_single_attribute"
 	"in_children"
+	"in_wrapper"
 end
 
 local function unpack_propschema( propschema : js.schema_t | js.schema_t.type_e, propname : string, current_ns : string )
@@ -67,6 +68,10 @@
 		proptype = propschema
 	end
 
+	if proptype == "object" or proptype == "array" then
+		value_where = "in_children"
+	end
+
 	if propschema is js.schema_t then
 		local xml = propschema.xml
 		if xml then
@@ -79,8 +84,9 @@
 			if xml.prefix then
 				prefix = xml.prefix
 			end
-
-			if xml.attribute then
+			if proptype == "array" and xml.wrapped then
+				value_where = "in_wrapper"
+			elseif xml.attribute then
 				value_where = "in_attribute"
 			elseif xml.text then
 				value_where = "in_text"
@@ -98,15 +104,13 @@
 		end
 	end
 
-	if proptype == "object" or proptype == "array" then
-		value_where = "in_children"
-	end
-
 	return proptype, value_where, name, namespace, prefix, single_attribute, enums
 end
 
+local parse_object : function (schema : js.schema_t, s : st.stanza_t) : { string : any }
+local parse_array : function (schema : js.schema_t, s : st.stanza_t) : { any }
 
-local function parse_object (schema : js.schema_t, s : st.stanza_t) : table
+function parse_object (schema : js.schema_t, s : st.stanza_t) : { string : any }
 	local out : { string : any } = {}
 	if schema.properties then
 		for prop, propschema in pairs(schema.properties) do
@@ -153,10 +157,22 @@
 					if c then
 						out[prop] = parse_object(propschema, c);
 					end
-				-- else TODO
+				elseif proptype == "array" then
+					out[prop] = parse_array(propschema, s);
+				else
+					error "unreachable"
 				end
+			elseif value_where == "in_wrapper" and propschema is js.schema_t and proptype == "array" then
+				local wrapper = s:get_child(name, namespace);
+				if wrapper then
+					out[prop] = parse_array(propschema, wrapper);
+				else
+					error "unreachable"
 			end
-			if value_where ~= "in_children" then
+			else
+				error "unreachable"
+			end
+			if value_where ~= "in_children" and value_where ~= "in_wrapper" then
 				out[prop] = totype(proptype, value)
 			end
 		end
@@ -165,9 +181,31 @@
 	return out
 end
 
+function parse_array (schema : js.schema_t, s : st.stanza_t) : { any }
+	local proptype, value_where, child_name, namespace = unpack_propschema(schema.items, nil, s.attr.xmlns)
+	local out : { any } = {}
+	for c in s:childtags(child_name, namespace) do
+		local value : string;
+		if value_where == "in_text_tag" then
+			value = c:get_text();
+		else
+			error "NYI"
+		end
+
+		if value ~= nil then
+			table.insert(out, value);
+		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)
+	elseif schema.type == "array" then
+		return parse_array(schema, s)
+	else
+		error "top-level scalars unsupported"
 	end
 end
 
--- a/util/datamapper.lua	Sun Mar 14 16:50:49 2021 +0100
+++ b/util/datamapper.lua	Thu Mar 18 12:57:25 2021 +0100
@@ -38,6 +38,10 @@
 		proptype = propschema
 	end
 
+	if proptype == "object" or proptype == "array" then
+		value_where = "in_children"
+	end
+
 	if type(propschema) == "table" then
 		local xml = propschema.xml
 		if xml then
@@ -50,8 +54,9 @@
 			if xml.prefix then
 				prefix = xml.prefix
 			end
-
-			if xml.attribute then
+			if proptype == "array" and xml.wrapped then
+				value_where = "in_wrapper"
+			elseif xml.attribute then
 				value_where = "in_attribute"
 			elseif xml.text then
 				value_where = "in_text"
@@ -69,14 +74,13 @@
 		end
 	end
 
-	if proptype == "object" or proptype == "array" then
-		value_where = "in_children"
-	end
-
 	return proptype, value_where, name, namespace, prefix, single_attribute, enums
 end
 
-local function parse_object(schema, s)
+local parse_object
+local parse_array
+
+function parse_object(schema, s)
 	local out = {}
 	if schema.properties then
 		for prop, propschema in pairs(schema.properties) do
@@ -123,10 +127,22 @@
 					if c then
 						out[prop] = parse_object(propschema, c);
 					end
-
+				elseif proptype == "array" then
+					out[prop] = parse_array(propschema, s);
+				else
+					error("unreachable")
 				end
+			elseif value_where == "in_wrapper" and type(propschema) == "table" and proptype == "array" then
+				local wrapper = s:get_child(name, namespace);
+				if wrapper then
+					out[prop] = parse_array(propschema, wrapper);
+				else
+					error("unreachable")
+				end
+			else
+				error("unreachable")
 			end
-			if value_where ~= "in_children" then
+			if value_where ~= "in_children" and value_where ~= "in_wrapper" then
 				out[prop] = totype(proptype, value)
 			end
 		end
@@ -135,9 +151,31 @@
 	return out
 end
 
+function parse_array(schema, s)
+	local proptype, value_where, child_name, namespace = unpack_propschema(schema.items, nil, s.attr.xmlns)
+	local out = {}
+	for c in s:childtags(child_name, namespace) do
+		local value;
+		if value_where == "in_text_tag" then
+			value = c:get_text();
+		else
+			error("NYI")
+		end
+
+		if value ~= nil then
+			table.insert(out, value);
+		end
+	end
+	return out
+end
+
 local function parse(schema, s)
 	if schema.type == "object" then
 		return parse_object(schema, s)
+	elseif schema.type == "array" then
+		return parse_array(schema, s)
+	else
+		error("top-level scalars unsupported")
 	end
 end