mod_rest/jsonmap.lib.lua
changeset 4522 073f5397c1d2
parent 4505 42f43f1383db
child 4523 ea1fd703bb27
equal deleted inserted replaced
4521:d6a3201a65c0 4522:073f5397c1d2
     1 local array = require "util.array";
     1 local array = require "util.array";
     2 local jid = require "util.jid";
     2 local jid = require "util.jid";
     3 local json = require "util.json";
     3 local json = require "util.json";
     4 local st = require "util.stanza";
     4 local st = require "util.stanza";
     5 local xml = require "util.xml";
     5 local xml = require "util.xml";
     6 
     6 local map = require "util.datamapper";
       
     7 
       
     8 local schema do
       
     9 	local f = assert(module:load_resource("res/schema-xmpp.json"));
       
    10 	schema = json.decode(f:read("*a"))
       
    11 	f:close();
       
    12 	-- Copy common properties to all stanza kinds
       
    13 	if schema._common then
       
    14 		for key, prop in pairs(schema._common) do
       
    15 			for _, copyto in pairs(schema.properties) do
       
    16 				copyto.properties[key] = prop;
       
    17 			end
       
    18 		end
       
    19 		schema._common = nil;
       
    20 	end
       
    21 end
       
    22 
       
    23 -- Some mappings that are still hard to do in a nice way with util.datamapper
     7 local field_mappings; -- in scope for "func" mappings
    24 local field_mappings; -- in scope for "func" mappings
     8 field_mappings = {
    25 field_mappings = {
     9 	-- top level stanza attributes
       
    10 	-- needed here to mark them as known fields
       
    11 	kind = "attr",
       
    12 	type = "attr",
       
    13 	to = "attr",
       
    14 	from = "attr",
       
    15 	id = "attr",
       
    16 	lang = "attr",
       
    17 
       
    18 	-- basic message
       
    19 	body = "text_tag",
       
    20 	subject = "text_tag",
       
    21 	thread = "text_tag",
       
    22 
       
    23 	-- basic presence
       
    24 	show = "text_tag",
       
    25 	status = "text_tag",
       
    26 	priority = "text_tag",
       
    27 
       
    28 	state = { type = "name", xmlns = "http://jabber.org/protocol/chatstates" },
       
    29 	nick = { type = "text_tag", xmlns = "http://jabber.org/protocol/nick", tagname = "nick" },
       
    30 	delay = { type = "attr", xmlns = "urn:xmpp:delay", tagname = "delay", attr = "stamp" },
       
    31 	replace = { type = "attr", xmlns = "urn:xmpp:message-correct:0", tagname = "replace", attr = "id" },
       
    32 
       
    33 	-- XEP-0045 MUC
       
    34 	-- TODO history, password, ???
       
    35 	join = { type = "bool_tag", xmlns = "http://jabber.org/protocol/muc", tagname = "x" },
       
    36 
       
    37 	-- XEP-0071
    26 	-- XEP-0071
    38 	html = {
    27 	html = {
    39 		type = "func", xmlns = "http://jabber.org/protocol/xhtml-im", tagname = "html",
    28 		type = "func", xmlns = "http://jabber.org/protocol/xhtml-im", tagname = "html",
    40 		st2json = function (s) --> json string
    29 		st2json = function (s) --> json string
    41 			return (tostring(s:get_child("body", "http://www.w3.org/1999/xhtml")):gsub(" xmlns='[^']*'", "", 1));
    30 			return (tostring(s:get_child("body", "http://www.w3.org/1999/xhtml")):gsub(" xmlns='[^']*'", "", 1));
    43 		json2st = function (s) --> xml
    32 		json2st = function (s) --> xml
    44 			if type(s) == "string" then
    33 			if type(s) == "string" then
    45 				return assert(xml.parse("<x:html xmlns:x='http://jabber.org/protocol/xhtml-im' xmlns='http://www.w3.org/1999/xhtml'>" .. s .. "</x:html>"));
    34 				return assert(xml.parse("<x:html xmlns:x='http://jabber.org/protocol/xhtml-im' xmlns='http://www.w3.org/1999/xhtml'>" .. s .. "</x:html>"));
    46 			end
    35 			end
    47 		end;
    36 		end;
    48 	};
       
    49 
       
    50 	-- XEP-0199: XMPP Ping
       
    51 	ping = { type = "bool_tag", xmlns = "urn:xmpp:ping", tagname = "ping" },
       
    52 
       
    53 	-- XEP-0092: Software Version
       
    54 	version = { type = "func", xmlns = "jabber:iq:version", tagname = "query",
       
    55 		st2json = function (s)
       
    56 			return {
       
    57 				name = s:get_child_text("name");
       
    58 				version = s:get_child_text("version");
       
    59 				os = s:get_child_text("os");
       
    60 			}
       
    61 		end,
       
    62 		json2st = function (s)
       
    63 			local v = st.stanza("query", { xmlns = "jabber:iq:version" });
       
    64 			if type(s) == "table" then
       
    65 				v:text_tag("name", s.name);
       
    66 				v:text_tag("version", s.version);
       
    67 				if s.os then
       
    68 					v:text_tag("os", s.os);
       
    69 				end
       
    70 			end
       
    71 			return v;
       
    72 		end
       
    73 	};
    37 	};
    74 
    38 
    75 	-- XEP-0030
    39 	-- XEP-0030
    76 	disco = {
    40 	disco = {
    77 		type = "func", xmlns = "http://jabber.org/protocol/disco#info", tagname = "query",
    41 		type = "func", xmlns = "http://jabber.org/protocol/disco#info", tagname = "query",
   226 			-- else .. missing required attribute
   190 			-- else .. missing required attribute
   227 		end;
   191 		end;
   228 	};
   192 	};
   229 
   193 
   230 	-- XEP-0066: Out of Band Data
   194 	-- XEP-0066: Out of Band Data
       
   195 	-- TODO Replace by oob.url in datamapper schema
   231 	oob_url = { type = "func", xmlns = "jabber:x:oob", tagname = "x",
   196 	oob_url = { type = "func", xmlns = "jabber:x:oob", tagname = "x",
   232 		-- XXX namespace depends on whether it's in an iq or message stanza
   197 		-- XXX namespace depends on whether it's in an iq or message stanza
   233 		st2json = function (s)
   198 		st2json = function (s)
   234 			return s:get_child_text("url");
   199 			return s:get_child_text("url");
   235 		end;
   200 		end;
   414 
   379 
   415 };
   380 };
   416 
   381 
   417 local byxmlname = {};
   382 local byxmlname = {};
   418 for k, spec in pairs(field_mappings) do
   383 for k, spec in pairs(field_mappings) do
       
   384 	for _, replace in pairs(schema.properties) do
       
   385 		replace.properties[k] = nil
       
   386 	end
       
   387 
   419 	if type(spec) == "table" then
   388 	if type(spec) == "table" then
   420 		spec.key = k;
   389 		spec.key = k;
   421 		if spec.xmlns and spec.tagname then
   390 		if spec.xmlns and spec.tagname then
   422 			byxmlname["{" .. spec.xmlns .. "}" .. spec.tagname] = spec;
   391 			byxmlname["{" .. spec.xmlns .. "}" .. spec.tagname] = spec;
   423 		elseif spec.type == "name" then
   392 		elseif spec.type == "name" then
   459 	subscribe = "presence", unsubscribe = "presence",
   428 	subscribe = "presence", unsubscribe = "presence",
   460 	subscribed = "presence", unsubscribed = "presence",
   429 	subscribed = "presence", unsubscribed = "presence",
   461 }
   430 }
   462 
   431 
   463 local function st2json(s)
   432 local function st2json(s)
   464 	local t = {
   433 	local t = map.parse(schema.properties[s.name], s);
   465 		kind = s.name,
   434 
   466 		type = s.attr.type,
       
   467 		to = s.attr.to,
       
   468 		from = s.attr.from,
       
   469 		id = s.attr.id,
       
   470 		lang = s.attr["xml:lang"],
       
   471 	};
       
   472 	if s.name == "presence" and not s.attr.type then
   435 	if s.name == "presence" and not s.attr.type then
   473 		t.type = "available";
   436 		t.type = "available";
   474 	end
   437 	end
   475 
   438 
   476 	if t.to then
   439 	if t.to then
   499 		local mapping = byxmlname[prefix .. tag.name];
   462 		local mapping = byxmlname[prefix .. tag.name];
   500 		if not mapping then
   463 		if not mapping then
   501 			mapping = byxmlname[prefix];
   464 			mapping = byxmlname[prefix];
   502 		end
   465 		end
   503 
   466 
   504 		if not mapping then -- luacheck: ignore 542
   467 		if mapping and mapping.type == "func" and mapping.st2json then
   505 			-- pass
       
   506 		elseif mapping.type == "text_tag" then
       
   507 			t[mapping.key] = tag:get_text();
       
   508 		elseif mapping.type == "name" then
       
   509 			t[mapping.key] = tag.name;
       
   510 		elseif mapping.type == "attr" then
       
   511 			t[mapping.key] = tag.attr[mapping.attr];
       
   512 		elseif mapping.type == "bool_tag" then
       
   513 			t[mapping.key] = true;
       
   514 		elseif mapping.type == "func" and mapping.st2json then
       
   515 			t[mapping.key] = mapping.st2json(tag);
   468 			t[mapping.key] = mapping.st2json(tag);
   516 		end
   469 		end
   517 	end
   470 	end
   518 
   471 
   519 	return t;
   472 	return t;
   545 				break
   498 				break
   546 			end
   499 			end
   547 		end
   500 		end
   548 	end
   501 	end
   549 
   502 
   550 	if t_type == "available" then
   503 	if kind == "presence" and t_type == "available" then
   551 		t_type = nil;
   504 		t_type = nil;
   552 	end
   505 	elseif kind == "iq" and not t_type then
   553 
   506 		t_type = "get";
   554 	local s = st.stanza(kind or "message", {
   507 	end
   555 		type = t_type;
   508 
   556 		to = str(t.to) and jid.prep(t.to);
   509 	local s = map.unparse(schema.properties[kind or "message"], t);
   557 		from = str(t.to) and jid.prep(t.from);
   510 
   558 		id = str(t.id),
   511 	s.attr.type = t_type;
   559 		["xml:lang"] = str(t.lang),
   512 	s.attr.to = str(t.to) and jid.prep(t.to);
   560 	});
   513 	s.attr.from = str(t.to) and jid.prep(t.from);
   561 
       
   562 	if t.to and not s.attr.to then
       
   563 		return nil, "invalid-jid-to";
       
   564 	end
       
   565 	if t.from and not s.attr.from then
       
   566 		return nil, "invalid-jid-from";
       
   567 	end
       
   568 	if kind == "iq" and not s.attr.type then
       
   569 		s.attr.type = "get";
       
   570 	end
       
   571 
   514 
   572 	if type(t.error) == "table" then
   515 	if type(t.error) == "table" then
   573 		return st.error_reply(st.reply(s), str(t.error.type), str(t.error.condition), str(t.error.text), str(t.error.by));
   516 		return st.error_reply(st.reply(s), str(t.error.type), str(t.error.condition), str(t.error.text), str(t.error.by));
   574 	elseif t.type == "error" then
   517 	elseif t.type == "error" then
   575 		s:text_tag("error", t.body, { code = t.error_code and tostring(t.error_code) });
   518 		s:text_tag("error", t.body, { code = t.error_code and tostring(t.error_code) });
   576 		return s;
   519 		return s;
   577 	end
   520 	end
   578 
   521 
   579 	for k, v in pairs(t) do
   522 	for k, v in pairs(t) do
   580 		local mapping = field_mappings[k];
   523 		local mapping = field_mappings[k];
   581 		if mapping then
   524 		if mapping and mapping.type == "func" and mapping.json2st then
   582 			if mapping == "text_tag" then
       
   583 				s:text_tag(k, v);
       
   584 			elseif mapping == "attr" then -- luacheck: ignore 542
       
   585 				-- handled already
       
   586 			elseif mapping.type == "text_tag" then
       
   587 				s:text_tag(mapping.tagname or k, v, mapping.xmlns and { xmlns = mapping.xmlns });
       
   588 			elseif mapping.type == "name" then
       
   589 				s:tag(v, { xmlns = mapping.xmlns }):up();
       
   590 			elseif mapping.type == "attr" then
       
   591 				s:tag(mapping.tagname or k, { xmlns = mapping.xmlns, [mapping.attr or k] = v }):up();
       
   592 			elseif mapping.type == "bool_tag" then
       
   593 				s:tag(mapping.tagname or k, { xmlns = mapping.xmlns }):up();
       
   594 			elseif mapping.type == "func" and mapping.json2st then
       
   595 				s:add_child(mapping.json2st(v)):up();
   525 				s:add_child(mapping.json2st(v)):up();
   596 			end
   526 			end
   597 		else
       
   598 			return nil, "unknown-field";
       
   599 		end
       
   600 	end
   527 	end
   601 
   528 
   602 	s:reset();
   529 	s:reset();
   603 
   530 
   604 	return s;
   531 	return s;