Software / code / 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 |
| parent | 4501:42f43f1383db |
| child | 4519: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; |