comparison mod_rest/jsonmap.lib.lua @ 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 42f43f1383db
children ea1fd703bb27
comparison
equal deleted inserted replaced
4517:d6a3201a65c0 4518: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;