changeset 449:9968eac538e6

Irccdctl: implement rule-info
author David Demelier <markand@malikania.fr>
date Thu, 06 Jul 2017 12:48:58 +0200
parents 9be4f8a5cf1a
children c8c68d4bf555
files doc/html/CMakeLists.txt doc/html/irccdctl/command/index.md doc/html/irccdctl/command/rule-info.md irccd/main.cpp irccdctl/cli.cpp irccdctl/cli.hpp irccdctl/main.cpp libcommon/irccd/util.hpp libirccd/irccd/command.cpp libirccd/irccd/command.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp tests/CMakeLists.txt tests/cmd-rule-info/CMakeLists.txt tests/cmd-rule-info/main.cpp
diffstat 15 files changed, 278 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/doc/html/CMakeLists.txt	Wed Jul 05 18:20:26 2017 +0200
+++ b/doc/html/CMakeLists.txt	Thu Jul 06 12:48:58 2017 +0200
@@ -153,6 +153,7 @@
     ${html_SOURCE_DIR}/irccdctl/command/plugin-reload.md
     ${html_SOURCE_DIR}/irccdctl/command/plugin-unload.md
     ${html_SOURCE_DIR}/irccdctl/command/rule-list.md
+    ${html_SOURCE_DIR}/irccdctl/command/rule-info.md
     ${html_SOURCE_DIR}/irccdctl/command/server-cmode.md
     ${html_SOURCE_DIR}/irccdctl/command/server-cnotice.md
     ${html_SOURCE_DIR}/irccdctl/command/server-connect.md
--- a/doc/html/irccdctl/command/index.md	Wed Jul 05 18:20:26 2017 +0200
+++ b/doc/html/irccdctl/command/index.md	Thu Jul 06 12:48:58 2017 +0200
@@ -13,6 +13,7 @@
   - [plugin-reload](plugin-reload.html)
   - [plugin-unload](plugin-unload.html)
   - [rule-list](rule-list.html)
+  - [rule-info](rule-info.html)
   - [server-cmode](server-cmode.html)
   - [server-cnotice](server-cnotice.html)
   - [server-connect](server-connect.html)
Binary file doc/html/irccdctl/command/rule-info.md has changed
--- a/irccd/main.cpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/irccd/main.cpp	Thu Jul 06 12:48:58 2017 +0200
@@ -320,6 +320,7 @@
     instance->commands().add(std::make_unique<command::ServerPartCommand>());
     instance->commands().add(std::make_unique<command::ServerReconnectCommand>());
     instance->commands().add(std::make_unique<command::ServerTopicCommand>());
+    instance->commands().add(std::make_unique<command::RuleInfoCommand>());
     instance->commands().add(std::make_unique<command::RuleListCommand>());
 
     // Load Javascript API and plugin loader.
--- a/irccdctl/cli.cpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/irccdctl/cli.cpp	Thu Jul 06 12:48:58 2017 +0200
@@ -908,6 +908,44 @@
 }
 
 /*
+ * RuleInfoCli.
+ * ------------------------------------------------------------------
+ */
+
+RuleInfoCli::RuleInfoCli()
+    : Cli("rule-info",
+          "show a rule",
+          "rule-info index",
+          "Show a rule.\n\n"
+          "Example:\n"
+          "\tirccdctl rule-info 0\n"
+          "\tirccdctl rule-info 1")
+{
+}
+
+void RuleInfoCli::exec(Irccdctl &irccdctl, const std::vector<std::string> &args)
+{
+    if (args.size() < 1)
+        throw std::invalid_argument("rule-info requires 1 argument");
+
+    int index = 0;
+
+    try {
+        index = std::stoi(args[0]);
+    } catch (...) {
+        throw std::invalid_argument("invalid number '" + args[0] + "'");
+    }
+
+    auto result = request(irccdctl, {
+        { "command",    "rule-info" },
+        { "index",      index       }
+    });
+
+    check(result);
+    showRule(result, 0);
+}
+
+/*
  * WatchCli.
  * ------------------------------------------------------------------
  */
--- a/irccdctl/cli.hpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/irccdctl/cli.hpp	Thu Jul 06 12:48:58 2017 +0200
@@ -607,6 +607,27 @@
 };
 
 /*
+ * RuleInfoCli
+ * ------------------------------------------------------------------
+ */
+
+/**
+ * \brief Implementation of irccdctl rule-info.
+ */
+class RuleInfoCli : public Cli {
+public:
+    /**
+     * Default constructor.
+     */
+    RuleInfoCli();
+
+    /**
+     * \copydoc Cli::exec
+     */
+    void exec(Irccdctl &client, const std::vector<std::string> &args) override;
+};
+
+/*
  * WatchCli.
  * ------------------------------------------------------------------
  */
--- a/irccdctl/main.cpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/irccdctl/main.cpp	Thu Jul 06 12:48:58 2017 +0200
@@ -523,6 +523,7 @@
     commands.push_back(std::make_unique<cli::ServerReconnectCli>());
     commands.push_back(std::make_unique<cli::ServerTopicCli>());
     commands.push_back(std::make_unique<cli::RuleListCli>());
+    commands.push_back(std::make_unique<cli::RuleInfoCli>());
     commands.push_back(std::make_unique<cli::WatchCli>());
 }
 
--- a/libcommon/irccd/util.hpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/libcommon/irccd/util.hpp	Thu Jul 06 12:48:58 2017 +0200
@@ -399,7 +399,17 @@
  */
 inline std::int64_t requireInt(const nlohmann::json &json, const std::string &key)
 {
-    return require(json, key, nlohmann::json::value_t::number_integer);
+    auto it = json.find(key);
+
+    if (it == json.end())
+        throw std::runtime_error(fmt::format("missing '{}' property", key));
+    if (it->is_number_integer())
+        return it->get<int>();
+    if (it->is_number_unsigned() && it->get<unsigned>() <= INT_MAX)
+        return static_cast<int>(it->get<unsigned>());
+
+    throw std::runtime_error(fmt::format("invalid '{}' property ({} expected, got {})",
+        key, it->type_name(), nlohmann::json(0).type_name()));
 }
 
 /**
@@ -412,7 +422,17 @@
  */
 inline std::uint64_t requireUint(const nlohmann::json &json, const std::string &key)
 {
-    return require(json, key, nlohmann::json::value_t::number_unsigned);
+    auto it = json.find(key);
+
+    if (it == json.end())
+        throw std::runtime_error(fmt::format("missing '{}' property", key));
+    if (it->is_number_unsigned())
+        return it->get<unsigned>();
+    if (it->is_number_integer() && it->get<int>() >= 0)
+        return static_cast<unsigned>(it->get<int>());
+
+    throw std::runtime_error(fmt::format("invalid '{}' property ({} expected, got {})",
+        key, it->type_name(), nlohmann::json(0U).type_name()));
 }
 
 /**
--- a/libirccd/irccd/command.cpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/libirccd/irccd/command.cpp	Thu Jul 06 12:48:58 2017 +0200
@@ -464,6 +464,16 @@
     client.success("rule-list", {{ "list", std::move(array) }});
 }
 
+RuleInfoCommand::RuleInfoCommand()
+    : Command("rule-info")
+{
+}
+
+void RuleInfoCommand::exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args)
+{
+    client.success("rule-info", toJson(irccd.rules().require(util::json::requireUint(args, "index"))));
+}
+
 } // !command
 
 } // !irccd
--- a/libirccd/irccd/command.hpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/libirccd/irccd/command.hpp	Thu Jul 06 12:48:58 2017 +0200
@@ -474,6 +474,22 @@
     void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
 };
 
+/**
+ * \brief Implementation of rule-info transport command.
+ */
+class IRCCD_EXPORT RuleInfoCommand : public Command {
+public:
+    /**
+     * Constructor.
+     */
+    RuleInfoCommand();
+
+    /**
+     * \copydoc Command::exec
+     */
+    void exec(Irccd &irccd, TransportClient &client, const nlohmann::json &args) override;
+};
+
 } // !command
 
 } // !irccd
--- a/libirccd/irccd/service.cpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/libirccd/irccd/service.cpp	Thu Jul 06 12:48:58 2017 +0200
@@ -296,6 +296,14 @@
     m_rules.erase(m_rules.begin() + position);
 }
 
+const Rule &RuleService::require(unsigned position) const
+{
+    if (position >= m_rules.size())
+        throw std::out_of_range("rule " + std::to_string(position) + " does not exist");
+
+    return m_rules[position];
+}
+
 bool RuleService::solve(const std::string &server,
                         const std::string &channel,
                         const std::string &origin,
--- a/libirccd/irccd/service.hpp	Wed Jul 05 18:20:26 2017 +0200
+++ b/libirccd/irccd/service.hpp	Thu Jul 06 12:48:58 2017 +0200
@@ -347,6 +347,15 @@
     IRCCD_EXPORT void remove(unsigned position);
 
     /**
+     * Get a rule at the specified index or throw an exception if not found.
+     *
+     * \param position the position
+     * \return the rule
+     * \throw std::out_of_range if position is invalid
+     */
+    IRCCD_EXPORT const Rule &require(unsigned position) const;
+
+    /**
      * Resolve the action to execute with the specified list of rules.
      *
      * \param server the server name
--- a/tests/CMakeLists.txt	Wed Jul 05 18:20:26 2017 +0200
+++ b/tests/CMakeLists.txt	Thu Jul 06 12:48:58 2017 +0200
@@ -27,6 +27,7 @@
     add_subdirectory(cmd-plugin-reload)
     add_subdirectory(cmd-plugin-unload)
     add_subdirectory(cmd-rule-list)
+    add_subdirectory(cmd-rule-info)
     add_subdirectory(cmd-server-cmode)
     add_subdirectory(cmd-server-cnotice)
     add_subdirectory(cmd-server-connect)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-info/CMakeLists.txt	Thu Jul 06 12:48:58 2017 +0200
@@ -0,0 +1,23 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+#
+
+irccd_define_test(
+    NAME cmd-rule-info
+    SOURCES main.cpp
+    LIBRARIES libirccd libirccdctl
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/cmd-rule-info/main.cpp	Thu Jul 06 12:48:58 2017 +0200
@@ -0,0 +1,126 @@
+/*
+ * main.cpp -- test rule-info remote command
+ *
+ * Copyright (c) 2013-2017 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <command.hpp>
+#include <command-tester.hpp>
+#include <service.hpp>
+
+using namespace irccd;
+using namespace irccd::command;
+
+class RuleInfoCommandTest : public CommandTester {
+protected:
+    nlohmann::json m_result;
+
+    /*
+     * Rule sets are unordered so use this function to search a string in
+     * the JSON array.
+     */
+    inline bool contains(const nlohmann::json &array, const std::string &str)
+    {
+        for (const auto &v : array)
+            if (v.is_string() && v == str)
+                return true;
+
+        return false;
+    }
+
+public:
+    RuleInfoCommandTest()
+        : CommandTester(std::make_unique<RuleInfoCommand>())
+    {
+        m_irccd.rules().add(Rule(
+            { "s1", "s2" },
+            { "c1", "c2" },
+            { "o1", "o2" },
+            { "p1", "p2" },
+            { "onMessage", "onCommand" },
+            RuleAction::Drop
+        ));
+        m_irccd.rules().add(Rule(
+            { "s1", },
+            { "c1", },
+            { "o1", },
+            { "p1", },
+            { "onMessage", },
+            RuleAction::Accept
+        ));
+        m_irccdctl.client().onMessage.connect([&] (auto result) {
+            m_result = result;
+        });
+    }
+};
+
+TEST_F(RuleInfoCommandTest, basic)
+{
+    m_irccdctl.client().request({
+        { "command",    "rule-info" },
+        { "index",      0           }
+    });
+
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+
+        auto servers = m_result["servers"];
+        auto channels = m_result["channels"];
+        auto plugins = m_result["plugins"];
+        auto events = m_result["events"];
+
+        ASSERT_TRUE(contains(servers, "s1"));
+        ASSERT_TRUE(contains(servers, "s2"));
+        ASSERT_TRUE(contains(channels, "c1"));
+        ASSERT_TRUE(contains(channels, "c2"));
+        ASSERT_TRUE(contains(plugins, "p1"));
+        ASSERT_TRUE(contains(plugins, "p2"));
+        ASSERT_TRUE(contains(events, "onMessage"));
+        ASSERT_TRUE(contains(events, "onCommand"));
+        ASSERT_EQ("drop", m_result["action"]);
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+TEST_F(RuleInfoCommandTest, outOfBounds)
+{
+    m_irccdctl.client().request({
+        { "command",    "rule-info" },
+        { "index",      123         }
+    });
+
+    try {
+        poll([&] () {
+            return m_result.is_object();
+        });
+
+        ASSERT_TRUE(m_result.is_object());
+        ASSERT_FALSE(m_result["status"]);
+    } catch (const std::exception &ex) {
+        FAIL() << ex.what();
+    }
+}
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}