Mercurial > prosody-modules
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; |