diff options
author | Sadie Powell <sadie@witchery.services> | 2025-02-23 02:34:05 +0000 |
---|---|---|
committer | Sadie Powell <sadie@witchery.services> | 2025-02-24 03:07:22 +0000 |
commit | 2ccd182d2e6689877eba911452eaa8a06e82ac0b (patch) | |
tree | d460bb3ea66ba981f2bd9092371d8a351ed9615a | |
parent | ee08b3e8804074c73245ca24c3663e43158eb790 (diff) |
Add support for RPC arrays, simplify the RPC objects.
-rw-r--r-- | include/modules/rpc.h | 120 | ||||
-rw-r--r-- | modules/extra/xmlrpc.cpp | 113 | ||||
-rw-r--r-- | modules/rpc/jsonrpc.cpp | 104 | ||||
-rw-r--r-- | modules/rpc/rpc_main.cpp | 72 |
4 files changed, 253 insertions, 156 deletions
diff --git a/include/modules/rpc.h b/include/modules/rpc.h index c9f9200a7..3af6e81c5 100644 --- a/include/modules/rpc.h +++ b/include/modules/rpc.h @@ -14,13 +14,15 @@ namespace RPC { + class Array; class Event; class Map; class Request; class ServiceInterface; + class Value; /** Represents possible types of RPC value. */ - using Value = std::variant<Map, Anope::string, std::nullptr_t, bool, double, int64_t, uint64_t>; + using ValueUnion = std::variant<Array, Map, Anope::string, std::nullptr_t, bool, double, int64_t, uint64_t>; /** Represents standard RPC errors from the JSON-RPC and XML-RPC specifications. */ enum Error @@ -35,57 +37,103 @@ namespace RPC }; } +class RPC::Array final +{ +private: + std::vector<Value> replies; + +public: + /** Retrieves the list of RPC replies. */ + inline const auto &GetReplies() const { return this->replies; } + + template <typename T> + inline Array &Reply(const T &t) + { + this->replies.emplace_back(RPC::Value(t)); + return *this; + } + + inline Array &ReplyArray(); + + inline Map &ReplyMap(); +}; + class RPC::Map { +private: + Anope::map<Value> replies; + public: /** Retrieves the list of RPC replies. */ inline const auto &GetReplies() const { return this->replies; } - template <typename Stringable> - inline Map &Reply(const Anope::string &key, const Stringable &value) + template <typename T> + inline Map &Reply(const Anope::string &key, const T &t) { - this->replies.emplace(key, Anope::ToString(value)); + this->replies.emplace(key, RPC::Value(t)); return *this; } - inline Map &ReplyMap(const Anope::string &key) + inline Array &ReplyArray(const Anope::string &key); + + inline Map &ReplyMap(const Anope::string &key); +}; + +class RPC::Value final +{ +private: + RPC::ValueUnion value; + +public: + explicit Value(const ValueUnion &v) + : value(v) { - auto it = this->replies.emplace(key, Map()); - return std::get<Map>(it.first->second); } - inline Map &ReplyBool(const Anope::string &key, bool value) + explicit Value(const Array &a) + : value(a) { - this->replies.emplace(key, value); - return *this; } - inline Map &ReplyFloat(const Anope::string &key, double value) + explicit Value(const Map &m) + : value(m) { - this->replies.emplace(key, value); - return *this; } - inline Map &ReplyInt(const Anope::string &key, int64_t value) + explicit Value(std::nullptr_t) + : value(nullptr) { - this->replies.emplace(key, value); - return *this; } - inline Map &ReplyNull(const Anope::string &key) + explicit Value(bool b) + : value(b) { - this->replies.emplace(key, nullptr); - return *this; } - inline Map &ReplyUInt(const Anope::string &key, uint64_t value) + Value(double d) + : value(d) { - this->replies.emplace(key, value); - return *this; } -private: - Anope::map<Value> replies; + Value(int64_t i) + : value(i) + { + } + + Value(uint64_t u) + : value(u) + { + } + + template <typename T> + Value(const T &t) + : value(Anope::ToString(t)) + { + } + + inline auto &Get() { return this->value; } + + inline const auto &Get() const { return this->value; } }; class RPC::Request final @@ -147,3 +195,27 @@ public: virtual void Reply(Request &request) = 0; }; + +inline RPC::Array &RPC::Array::ReplyArray() +{ + auto &reply = this->replies.emplace_back(RPC::Array()); + return std::get<RPC::Array>(reply.Get()); +} + +inline RPC::Map &RPC::Array::ReplyMap() +{ + auto &reply = this->replies.emplace_back(RPC::Map()); + return std::get<RPC::Map>(reply.Get()); +} + +inline RPC::Array &RPC::Map::ReplyArray(const Anope::string &key) +{ + auto it = this->replies.emplace(key, RPC::Array()); + return std::get<RPC::Array>(it.first->second.Get()); +} + +inline RPC::Map &RPC::Map::ReplyMap(const Anope::string &key) +{ + auto it = this->replies.emplace(key, RPC::Map()); + return std::get<RPC::Map>(it.first->second.Get()); +} diff --git a/modules/extra/xmlrpc.cpp b/modules/extra/xmlrpc.cpp index 86cdaf9f7..1d9ab7762 100644 --- a/modules/extra/xmlrpc.cpp +++ b/modules/extra/xmlrpc.cpp @@ -42,56 +42,23 @@ private: xmlrpc_env_clean(&env); } + static xmlrpc_value *SerializeElement(xmlrpc_env &env, const RPC::Value &value); + + static void SerializeArray(xmlrpc_env &env, xmlrpc_value *value, const RPC::Array &array) + { + for (const auto &elem : array.GetReplies()) + { + auto *obj = SerializeElement(env, elem); + xmlrpc_array_append_item(&env, value, obj); + } + } + static void SerializeMap(xmlrpc_env &env, xmlrpc_value *value, const RPC::Map &map) { for (const auto &[k, v] : map.GetReplies()) { - xmlrpc_value *elem; - std::visit(overloaded - { - [&env, &elem](const RPC::Map &m) - { - elem = xmlrpc_struct_new(&env); - SerializeMap(env, elem, m); - }, - [&env, &elem](const Anope::string &s) - { - elem = xmlrpc_string_new_lp(&env, s.length(), s.c_str()); - }, - [&env, &elem](std::nullptr_t) - { - elem = xmlrpc_nil_new(&env); - }, - [&env, &elem](bool b) - { - elem = xmlrpc_bool_new(&env, b); - }, - [&env, &elem](double d) - { - elem = xmlrpc_double_new(&env, d); - }, - [&env, &elem](int64_t i) - { - elem = xmlrpc_i8_new(&env, i); - }, - [&env, &elem](uint64_t u) - { - // XML-RPC does not support unsigned data types. - if (u > INT64_MAX) - { - // We need to convert this to a string. - auto s = Anope::ToString(u); - elem = xmlrpc_string_new_lp(&env, s.length(), s.c_str()); - } - else - { - // We can fit this into a i8. - elem = xmlrpc_i8_new(&env, u); - } - }, - }, v); - - xmlrpc_struct_set_value_n(&env, value, k.c_str(), k.length(), elem); + auto *obj = SerializeElement(env, v); + xmlrpc_struct_set_value_n(&env, value, k.c_str(), k.length(), obj); } } @@ -206,6 +173,60 @@ public: } }; +xmlrpc_value *MyXMLRPCServiceInterface::SerializeElement(xmlrpc_env &env, const RPC::Value &value) +{ + xmlrpc_value *elem; + std::visit(overloaded + { + [&env, &elem](const RPC::Array &a) + { + elem = xmlrpc_array_new(&env); + SerializeArray(env, elem, a); + }, + [&env, &elem](const RPC::Map &m) + { + elem = xmlrpc_struct_new(&env); + SerializeMap(env, elem, m); + }, + [&env, &elem](const Anope::string &s) + { + elem = xmlrpc_string_new_lp(&env, s.length(), s.c_str()); + }, + [&env, &elem](std::nullptr_t) + { + elem = xmlrpc_nil_new(&env); + }, + [&env, &elem](bool b) + { + elem = xmlrpc_bool_new(&env, b); + }, + [&env, &elem](double d) + { + elem = xmlrpc_double_new(&env, d); + }, + [&env, &elem](int64_t i) + { + elem = xmlrpc_i8_new(&env, i); + }, + [&env, &elem](uint64_t u) + { + // XML-RPC does not support unsigned data types. + if (u > INT64_MAX) + { + // We need to convert this to a string. + auto s = Anope::ToString(u); + elem = xmlrpc_string_new_lp(&env, s.length(), s.c_str()); + } + else + { + // We can fit this into a i8. + elem = xmlrpc_i8_new(&env, u); + } + }, + }, value.Get()); + return elem; +} + class ModuleXMLRPC final : public Module { diff --git a/modules/rpc/jsonrpc.cpp b/modules/rpc/jsonrpc.cpp index b7c1f4042..ad46bcb13 100644 --- a/modules/rpc/jsonrpc.cpp +++ b/modules/rpc/jsonrpc.cpp @@ -59,46 +59,24 @@ private: yyjson_mut_doc_free(doc); } - static void SerializeMap(yyjson_mut_doc *doc, yyjson_mut_val *root, const char *key, const RPC::Map &map) + static yyjson_mut_val *SerializeElement(yyjson_mut_doc *doc, const RPC::Value &value); + + static void SerializeArray(yyjson_mut_doc *doc, yyjson_mut_val *value, const RPC::Array &array) { - auto *result = yyjson_mut_obj(doc); - for (const auto &reply : map.GetReplies()) - { - // Captured structured bindings are a C++20 extension. - const auto &k = reply.first; - std::visit(overloaded - { - [&doc, &result, &k](const RPC::Map &m) - { - SerializeMap(doc, result, k.c_str(), m); - }, - [&doc, &result, &k](const Anope::string &s) - { - yyjson_mut_obj_add_strn(doc, result, k.c_str(), s.c_str(), s.length()); - }, - [&doc, &result, &k](std::nullptr_t) - { - yyjson_mut_obj_add_null(doc, result, k.c_str()); - }, - [&doc, &result, &k](bool b) - { - yyjson_mut_obj_add_bool(doc, result, k.c_str(), b); - }, - [&doc, &result, &k](double d) - { - yyjson_mut_obj_add_real(doc, result, k.c_str(), d); - }, - [&doc, &result, &k](int64_t i) - { - yyjson_mut_obj_add_int(doc, result, k.c_str(), i); - }, - [&doc, &result, &k](uint64_t u) - { - yyjson_mut_obj_add_uint(doc, result, k.c_str(), u); - }, - }, reply.second); + for (const auto &elem : array.GetReplies()) + { + auto *obj = SerializeElement(doc, elem); + yyjson_mut_arr_add_val(value, obj); + } + } + + static void SerializeMap(yyjson_mut_doc *doc, yyjson_mut_val *value, const RPC::Map &map) + { + for (const auto &[k, v] : map.GetReplies()) + { + auto *obj = SerializeElement(doc, v); + yyjson_mut_obj_add_val(doc, value, k.c_str(), obj); } - yyjson_mut_obj_add_val(doc, root, key, result); } public: @@ -194,7 +172,11 @@ public: yyjson_mut_obj_add_strn(doc, root, "id", request.id.c_str(), request.id.length()); if (!request.GetReplies().empty()) - SerializeMap(doc, root, "result", request); + { + auto *result = yyjson_mut_obj(doc); + SerializeMap(doc, result, request); + yyjson_mut_obj_add_val(doc, root, "result", result); + } yyjson_mut_obj_add_str(doc, root, "jsonrpc", "2.0"); @@ -208,6 +190,50 @@ public: } }; +yyjson_mut_val *MyJSONRPCServiceInterface::SerializeElement(yyjson_mut_doc *doc, const RPC::Value &value) +{ + yyjson_mut_val *elem; + std::visit(overloaded + { + [&doc, &elem](const RPC::Array &a) + { + elem = yyjson_mut_arr(doc); + SerializeArray(doc, elem, a); + }, + [&doc, &elem](const RPC::Map &m) + { + elem = yyjson_mut_obj(doc); + SerializeMap(doc, elem, m); + }, + [&doc, &elem](const Anope::string &s) + { + elem = yyjson_mut_strn(doc, s.c_str(), s.length()); + }, + [&doc, &elem](std::nullptr_t) + { + elem = yyjson_mut_null(doc); + }, + [&doc, &elem](bool b) + { + elem = yyjson_mut_bool(doc, b); + }, + [&doc, &elem](double d) + { + elem = yyjson_mut_real(doc, d); + }, + [&doc, &elem](int64_t i) + { + elem = yyjson_mut_int(doc, i); + }, + [&doc, &elem](uint64_t u) + { + elem = yyjson_mut_uint(doc, u); + }, + }, value.Get()); + return elem; +} + + class ModuleJSONRPC final : public Module { diff --git a/modules/rpc/rpc_main.cpp b/modules/rpc/rpc_main.cpp index 7be09be22..c1e53e8dd 100644 --- a/modules/rpc/rpc_main.cpp +++ b/modules/rpc/rpc_main.cpp @@ -152,19 +152,16 @@ public: bool Run(RPC::ServiceInterface *iface, HTTPClient *client, RPC::Request &request) override { - request.ReplyInt("uptime", Anope::CurTime - Anope::StartTime); + request.Reply("uptime", Anope::CurTime - Anope::StartTime); request.Reply("uplinkname", Me->GetLinks().front()->GetName()); { - Anope::string buf; + auto &uplinkcapab = request.ReplyArray("uplinkcapab"); for (const auto &capab : Servers::Capab) - buf += " " + capab; - if (!buf.empty()) - buf.erase(buf.begin()); - request.Reply("uplinkcapab", buf); + uplinkcapab.Reply(capab); } - request.ReplyUInt("usercount", UserListByNick.size()); - request.ReplyUInt("maxusercount", MaxUserCount); - request.ReplyUInt("channelcount", ChannelList.size()); + request.Reply("usercount", UserListByNick.size()); + request.Reply("maxusercount", MaxUserCount); + request.Reply("channelcount", ChannelList.size()); return true; } }; @@ -192,35 +189,24 @@ public: if (c) { - auto &bans = request.ReplyMap("bans"); - bans.ReplyUInt("count", c->HasMode("BAN")); - int count = 0; + request.Reply("bancount", c->HasMode("BAN")); + auto &bans = request.ReplyArray("bans"); for (auto &ban : c->GetModeList("BAN")) - bans.Reply(Anope::ToString(++count), ban); + bans.Reply(ban); - auto &excepts = request.ReplyMap("excepts"); - excepts.ReplyUInt("count", c->HasMode("EXCEPT")); - count = 0; + request.Reply("exceptcount", c->HasMode("EXCEPT")); + auto &excepts = request.ReplyArray("excepts"); for (auto &except : c->GetModeList("EXCEPT")) - excepts.Reply(Anope::ToString(++count), except); + excepts.Reply(except); - auto &invites = request.ReplyMap("invites"); - invites.ReplyUInt("count", c->HasMode("INVITEOVERRIDE")); - count = 0; + request.Reply("invitecount", c->HasMode("INVITEOVERRIDE")); + auto &invites = request.ReplyArray("invites"); for (auto &invite : c->GetModeList("INVITEOVERRIDE")) - invites.Reply(Anope::ToString(++count), invite); + invites.Reply(invite); - Anope::string users; - for (Channel::ChanUserList::const_iterator it = c->users.begin(); it != c->users.end(); ++it) - { - ChanUserContainer *uc = it->second; - users += uc->status.BuildModePrefixList() + uc->user->nick + " "; - } - if (!users.empty()) - { - users.erase(users.length() - 1); - request.Reply("users", users); - } + auto &users = request.ReplyArray("users"); + for (const auto &[_, uc] : c->users) + users.Reply(uc->status.BuildModePrefixList() + uc->user->nick); if (!c->topic.empty()) request.Reply("topic", c->topic); @@ -228,8 +214,8 @@ public: if (!c->topic_setter.empty()) request.Reply("topicsetter", c->topic_setter); - request.ReplyInt("topictime", c->topic_time); - request.ReplyInt("topicts", c->topic_ts); + request.Reply("topictime", c->topic_time); + request.Reply("topicts", c->topic_ts); } return true; } @@ -266,8 +252,8 @@ public: if (!u->chost.empty()) request.Reply("chost", u->chost); request.Reply("ip", u->ip.addr()); - request.ReplyUInt("timestamp", u->timestamp); - request.ReplyUInt("signon", u->signon); + request.Reply("timestamp", u->timestamp); + request.Reply("signon", u->signon); if (u->IsIdentified()) { request.Reply("account", u->Account()->display); @@ -275,17 +261,9 @@ public: request.Reply("opertype", u->Account()->o->ot->GetName()); } - Anope::string channels; - for (User::ChanUserList::const_iterator it = u->chans.begin(); it != u->chans.end(); ++it) - { - ChanUserContainer *cc = it->second; - channels += cc->status.BuildModePrefixList() + cc->chan->name + " "; - } - if (!channels.empty()) - { - channels.erase(channels.length() - 1); - request.Reply("channels", channels); - } + auto &channels = request.ReplyArray("channels"); + for (const auto &[_, cc] : u->chans) + channels.Reply(cc->status.BuildModePrefixList() + cc->chan->name); } return true; } |