changeset 569:24b79bccc181

Irccd: initial support of error code responses Bring several new types for describing precise errors using: - irccd_error: for general errors including connecting/recv/send, - server_error: server and server_service, - rule_error: rule and rule_service, - plugin_error: plugin and plugin_service. No error string are sent to the client anymore.
author David Demelier <markand@malikania.fr>
date Tue, 28 Nov 2017 13:57:09 +0100
parents ed986ae52656
children 153e84e7b09b
files irccdctl/cli.cpp libcommon/CMakeLists.txt libcommon/irccd/network_errc.cpp libcommon/irccd/network_errc.hpp libcommon/irccd/network_stream.hpp libirccd/irccd/basic_transport_client.hpp libirccd/irccd/command.cpp libirccd/irccd/irccd.cpp libirccd/irccd/irccd.hpp libirccd/irccd/plugin.cpp libirccd/irccd/plugin.hpp libirccd/irccd/plugin_service.cpp libirccd/irccd/rule.cpp libirccd/irccd/rule.hpp libirccd/irccd/server.cpp libirccd/irccd/server.hpp libirccd/irccd/transport_client.cpp libirccd/irccd/transport_client.hpp libirccd/irccd/transport_server.cpp libirccd/irccd/transport_service.cpp libirccdctl/CMakeLists.txt libirccdctl/irccd/ctl/controller.cpp
diffstat 22 files changed, 576 insertions(+), 333 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/cli.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/irccdctl/cli.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -19,7 +19,6 @@
 #include <boost/system/system_error.hpp>
 
 #include <irccd/json_util.hpp>
-#include <irccd/network_errc.hpp>
 #include <irccd/options.hpp>
 #include <irccd/string_util.hpp>
 
@@ -37,11 +36,6 @@
         if (code)
             throw boost::system::system_error(code);
 
-        if (message["error"].is_number_integer())
-            throw boost::system::system_error(static_cast<network_errc>(message["error"].template get<int>()));
-        if (message["error"].is_string())
-            throw std::runtime_error(message["error"].template get<std::string>());
-
         auto c = json_util::to_string(message["command"]);
 
         if (c != req["command"].get<std::string>()) {
--- a/libcommon/CMakeLists.txt	Tue Nov 28 12:20:58 2017 +0100
+++ b/libcommon/CMakeLists.txt	Tue Nov 28 13:57:09 2017 +0100
@@ -26,7 +26,6 @@
     ${libcommon_SOURCE_DIR}/irccd/ini.hpp
     ${libcommon_SOURCE_DIR}/irccd/json_util.hpp
     ${libcommon_SOURCE_DIR}/irccd/logger.hpp
-    ${libcommon_SOURCE_DIR}/irccd/network_errc.hpp
     ${libcommon_SOURCE_DIR}/irccd/network_stream.hpp
     ${libcommon_SOURCE_DIR}/irccd/options.hpp
     ${libcommon_SOURCE_DIR}/irccd/string_util.hpp
@@ -40,7 +39,6 @@
     ${libcommon_SOURCE_DIR}/irccd/ini.cpp
     ${libcommon_SOURCE_DIR}/irccd/json_util.cpp
     ${libcommon_SOURCE_DIR}/irccd/logger.cpp
-    ${libcommon_SOURCE_DIR}/irccd/network_errc.cpp
     ${libcommon_SOURCE_DIR}/irccd/options.cpp
     ${libcommon_SOURCE_DIR}/irccd/string_util.cpp
     ${libcommon_SOURCE_DIR}/irccd/system.cpp
--- a/libcommon/irccd/network_errc.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/*
- * network_errc.cpp -- describe some error codes
- *
- * 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 "network_errc.hpp"
-
-namespace irccd {
-
-const boost::system::error_category& network_category() noexcept
-{
-    static const class network_category : public boost::system::error_category {
-    public:
-        const char* name() const noexcept override
-        {
-            return "network_category";
-        }
-
-        std::string message(int code) const override
-        {
-            switch (static_cast<network_errc>(code)) {
-            case network_errc::no_error:
-                return "no error";
-            case network_errc::invalid_program:
-                return "invalid program";
-            case network_errc::invalid_version:
-                return "invalid version";
-            case network_errc::invalid_auth:
-                return "invalid authentication";
-            case network_errc::invalid_message:
-                return "invalid message";
-            case network_errc::corrupt_message:
-                return "corrupt message";
-            case network_errc::auth_required:
-                return "auth required";
-            default:
-                return "unknown error";
-            }
-        }
-    } category;
-
-    return category;
-}
-
-boost::system::error_code make_error_code(network_errc errc) noexcept
-{
-    return {static_cast<int>(errc), network_category()};
-}
-
-} // !irccd
--- a/libcommon/irccd/network_errc.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,74 +0,0 @@
-/*
- * network_errc.hpp -- describe some error codes
- *
- * 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.
- */
-
-#ifndef IRCCD_COMMON_NETWORK_ERRC_HPP
-#define IRCCD_COMMON_NETWORK_ERRC_HPP
-
-/**
- * \file network_errc.hpp
- * \brief Describe some error codes.
- */
-
-#include <boost/system/error_code.hpp>
-
-#include <type_traits>
-
-namespace irccd {
-
-/**
- * \brief Error code for transport/irccdctl
- */
-enum class network_errc {
-    no_error = 0,           //!< no error (default)
-    invalid_program,        //!< connected daemon is not irccd
-    invalid_version,        //!< irccd daemon is incompatible
-    invalid_auth,           //!< invalid credentials in auth command
-    invalid_message,        //!< the message was not JSON
-    corrupt_message,        //!< error occured while sending a message
-    auth_required           //!< authentication is required and was not issued
-};
-
-/**
- * Get the network category singleton.
- *
- * \return the category for network_errc enum
- */
-const boost::system::error_category& network_category() noexcept;
-
-/**
- * Construct an error_code from network_errc enum.
- *
- * \return the error code
- */
-boost::system::error_code make_error_code(network_errc errc) noexcept;
-
-} // !irccd
-
-namespace boost {
-
-namespace system {
-
-template <>
-struct is_error_code_enum<irccd::network_errc> : public std::true_type {
-};
-
-} // !system
-
-} // !boost
-
-#endif // !IRCCD_COMMON_NETWORK_ERRC_HPP
--- a/libcommon/irccd/network_stream.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libcommon/irccd/network_stream.hpp	Tue Nov 28 13:57:09 2017 +0100
@@ -39,8 +39,6 @@
 
 #include <json.hpp>
 
-#include "network_errc.hpp"
-
 namespace irccd {
 
 /**
@@ -167,10 +165,8 @@
 void network_stream<Socket>::do_recv(network_recv_handler handler)
 {
     boost::asio::async_read_until(socket_, rbuffer_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
-        if (code)
-            handler(std::move(code), nullptr);
-        else if (xfer == 0U)
-            handler(network_errc::corrupt_message, nullptr);
+        if (code || xfer == 0U)
+            handler(make_error_code(boost::system::errc::network_down), nullptr);
         else {
             std::string str(
                 boost::asio::buffers_begin(rbuffer_.data()),
@@ -188,9 +184,9 @@
             } catch (...) {}
 
             if (!message.is_object())
-                handler(network_errc::invalid_message, nullptr);
+                handler(make_error_code(boost::system::errc::invalid_argument), nullptr);
             else
-                handler(network_errc::no_error, std::move(message));
+                handler(code, std::move(message));
         }
     });
 }
@@ -199,12 +195,10 @@
 void network_stream<Socket>::do_send(const std::string& str, network_send_handler handler)
 {
     boost::asio::async_write(socket_, boost::asio::buffer(str), [handler] (auto code, auto xfer) {
-        if (code)
-            handler(std::move(code));
-        else if (xfer == 0U)
-            handler(network_errc::corrupt_message);
+        if (code || xfer == 0U)
+            handler(make_error_code(boost::system::errc::network_down));
         else
-            handler(network_errc::no_error);
+            handler(code);
     });
 }
 
--- a/libirccd/irccd/basic_transport_client.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/basic_transport_client.hpp	Tue Nov 28 13:57:09 2017 +0100
@@ -74,7 +74,13 @@
      */
     void do_recv(network_recv_handler handler) override
     {
-        stream_.recv(std::move(handler));
+        assert(handler);
+
+        auto self = shared_from_this();
+
+        stream_.recv([this, self, handler] (auto msg, auto code) {
+            handler(std::move(msg), std::move(code));
+        });
     }
 
     /**
@@ -82,7 +88,12 @@
      */
     void do_send(nlohmann::json json, network_send_handler handler) override
     {
-        stream_.send(std::move(json), std::move(handler));
+        auto self = shared_from_this();
+
+        stream_.send(std::move(json), [this, self, handler] (auto code) {
+            if (handler)
+                handler(std::move(code));
+        });
     }
 };
 
--- a/libirccd/irccd/command.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/command.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -39,16 +39,15 @@
     auto value = args.find("value");
 
     if (var == args.end() || !var->is_string())
-        client.error("plugin-config", "missing 'variable' property (string expected)");
-    else if (!value->is_string())
-        client.error("plugin-config", "invalid 'value' property (string expected)");
-    else {
-        auto config = plugin.config();
+        throw irccd_error(irccd_error::error::incomplete_message);
+    if (value == args.end() || !value->is_string())
+        throw irccd_error(irccd_error::error::incomplete_message);
 
-        config[*var] = *value;
-        plugin.set_config(config);
-        client.success("plugin-config");
-    }
+    auto config = plugin.config();
+
+    config[*var] = *value;
+    plugin.set_config(config);
+    client.success("plugin-config");
 }
 
 void exec_get(transport_client& client, plugin& plugin, const nlohmann::json& args)
@@ -262,11 +261,10 @@
     auto server = server::from_json(irccd.service(), args);
 
     if (irccd.servers().has(server->name()))
-        client.error("server-connect", "server already exists");
-    else {
-        irccd.servers().add(std::move(server));
-        client.success("server-connect");
-    }
+        throw server_error(server_error::error::already_exists);
+
+    irccd.servers().add(std::move(server));
+    client.success("server-connect");
 }
 
 server_disconnect_command::server_disconnect_command()
@@ -280,8 +278,14 @@
 
     if (it == args.end())
         irccd.servers().clear();
-    else
-        irccd.servers().remove(*it);
+    else {
+        if (!it->is_string())
+            throw server_error(server_error::error::invalid_identifier);
+        if (!irccd.servers().has(it->get<std::string>()))
+            throw server_error(server_error::error::not_found);
+
+        irccd.servers().remove(it->get<std::string>());
+    }
 
     client.success("server-disconnect");
 }
@@ -523,19 +527,15 @@
     auto action = args.find("action");
 
     if (action != args.end()) {
-        if (!action->is_string()) {
-            client.error("rule-edit", "action must be \"accept\" or \"drop\"");
-            return;
-        }
+        if (!action->is_string())
+            throw rule_error(rule_error::error::invalid_action);
 
         if (action->get<std::string>() == "accept")
             rule.set_action(rule::action_type::accept);
         else if (action->get<std::string>() == "drop")
             rule.set_action(rule::action_type::drop);
-        else {
-            client.error("rule-edit", "invalid action '"s + action->get<std::string>() + "'");
-            return;
-        }
+        else
+            throw rule_error(rule_error::error::invalid_action);
     }
 
     // All done, sync the rule.
@@ -583,14 +583,11 @@
 {
     unsigned position = json_util::require_uint(args, "index");
 
-    if (irccd.rules().length() == 0)
-        client.error("rule-remove", "rule list is empty");
-    if (position >= irccd.rules().length())
-        client.error("rule-remove", "index is out of range");
-    else {
-        irccd.rules().remove(position);
-        client.success("rule-remove");
-    }
+    if (irccd.rules().length() == 0 || position >= irccd.rules().length())
+        throw rule_error(rule_error::error::invalid_index);
+
+    irccd.rules().remove(position);
+    client.success("rule-remove");
 }
 
 rule_move_command::rule_move_command()
@@ -633,18 +630,20 @@
      * After:  [1] [2] [0]
      */
 
-    // Ignore dump input.
-    if (from == to)
+    // Ignore dumb input.
+    if (from == to) {
         client.success("rule-move");
-    else if (from >= irccd.rules().length())
-        client.error("rule-move", "rule source index is out of range");
-    else {
-        auto save = irccd.rules().list()[from];
+        return;
+    }
+
+    if (from >= irccd.rules().length())
+        throw rule_error(rule_error::error::invalid_index);
 
-        irccd.rules().remove(from);
-        irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to);
-        client.success("rule-move");
-    }
+    auto save = irccd.rules().list()[from];
+
+    irccd.rules().remove(from);
+    irccd.rules().insert(save, to > irccd.rules().length() ? irccd.rules().length() : to);
+    client.success("rule-move");
 }
 
 rule_add_command::rule_add_command()
@@ -658,11 +657,10 @@
     auto rule = from_json(args);
 
     if (index > irccd.rules().length())
-        client.error("rule-add", "index is out of range");
-    else {
-        irccd.rules().insert(rule, index);
-        client.success("rule-add");
-    }
+        throw rule_error(rule_error::error::invalid_index);
+
+    irccd.rules().insert(rule, index);
+    client.success("rule-add");
 }
 
 } // !irccd
--- a/libirccd/irccd/irccd.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/irccd.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -38,4 +38,44 @@
 
 irccd::~irccd() = default;
 
+const boost::system::error_category& irccd_category()
+{
+    static const class category : public boost::system::error_category {
+    public:
+        const char* name() const noexcept override
+        {
+            return "irccd";
+        }
+
+        std::string message(int e) const override
+        {
+            switch (static_cast<irccd_error::error>(e)) {
+            case irccd_error::error::not_irccd:
+                return "daemon is not irccd instance";
+            case irccd_error::error::incompatible_version:
+                return "major version is incompatible";
+            case irccd_error::error::auth_required:
+                return "authentication is required";
+            case irccd_error::error::invalid_auth:
+                return "invalid authentication";
+            case irccd_error::error::invalid_message:
+                return "invalid message";
+            case irccd_error::error::invalid_command:
+                return "invalid command";
+            case irccd_error::error::incomplete_message:
+                return "command requires more arguments";
+            default:
+                return "no error";
+            }
+        }
+    } category;
+
+    return category;
+}
+
+boost::system::error_code make_error_code(irccd_error::error e)
+{
+    return {static_cast<int>(e), irccd_category()};
+}
+
 } // !irccd
--- a/libirccd/irccd/irccd.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/irccd.hpp	Tue Nov 28 13:57:09 2017 +0100
@@ -173,6 +173,72 @@
     }
 };
 
+/**
+ * \brief Irccd error.
+ */
+class irccd_error : public boost::system::system_error {
+public:
+    /**
+     * \brief Irccd related errors (1..999)
+     */
+    enum error {
+        //!< No error.
+        no_error = 0,
+
+        //!< The connected peer is not irccd.
+        not_irccd,
+
+        //!< The irccd version is too different.
+        incompatible_version,
+
+        //!< Authentication was required but not issued.
+        auth_required,
+
+        //!< Authentication was invalid.
+        invalid_auth,
+
+        //!< The message was not a valid JSON object.
+        invalid_message,
+
+        //!< The specified command does not exist.
+        invalid_command,
+
+        //!< The specified command requires more arguments.
+        incomplete_message,
+    };
+
+    /**
+     * Inherited constructors.
+     */
+    using system_error::system_error;
+};
+
+/**
+ * Get the irccd error category singleton.
+ *
+ * \return the singleton
+ */
+const boost::system::error_category& irccd_category();
+
+/**
+ * Create a boost::system::error_code from irccd_error::error enum.
+ *
+ * \param e the error code
+ */
+boost::system::error_code make_error_code(irccd_error::error e);
+
 } // !irccd
 
+namespace boost {
+
+namespace system {
+
+template <>
+struct is_error_code_enum<irccd::irccd_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
 #endif // !IRCCD_HPP
--- a/libirccd/irccd/plugin.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/plugin.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -68,4 +68,36 @@
     return plugin;
 }
 
+const boost::system::error_category& plugin_category()
+{
+    static const class category : public boost::system::error_category {
+    public:
+        const char* name() const noexcept override
+        {
+            return "plugin";
+        }
+
+        std::string message(int e) const override
+        {
+            switch (static_cast<plugin_error::error>(e)) {
+            case plugin_error::not_found:
+                return "plugin not found";
+            case plugin_error::exec_error:
+                return "plugin exec error";
+            case plugin_error::already_exists:
+                return "plugin already exists";
+            default:
+                return "no error";
+            }
+        }
+    } category;
+
+    return category;
+}
+
+boost::system::error_code make_error_code(plugin_error::error e)
+{
+    return {static_cast<int>(e), plugin_category()};
+}
+
 } // !irccd
--- a/libirccd/irccd/plugin.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/plugin.hpp	Tue Nov 28 13:57:09 2017 +0100
@@ -559,6 +559,60 @@
     virtual std::shared_ptr<plugin> find(const std::string& id) noexcept;
 };
 
+/**
+ * \brief Plugin error.
+ */
+class plugin_error : public boost::system::system_error {
+public:
+    /**
+     * \brief Server related errors (3000..3999)
+     */
+    enum error {
+        //!< No error.
+        no_error = 0,
+
+        //!< The specified plugin is not found.
+        not_found = 2000,
+
+        //!< The plugin was unable to run the function.
+        exec_error,
+
+        //!< The plugin is already loaded.
+        already_exists,
+    };
+
+    /**
+     * Inherited constructors.
+     */
+    using system_error::system_error;
+};
+
+/**
+ * Get the plugin error category singleton.
+ *
+ * \return the singleton
+ */
+const boost::system::error_category& server_category();
+
+/**
+ * Create a boost::system::error_code from plugin_error::error enum.
+ *
+ * \param e the error code
+ */
+boost::system::error_code make_error_code(plugin_error::error e);
+
 } // !irccd
 
+namespace boost {
+
+namespace system {
+
+template <>
+struct is_error_code_enum<irccd::plugin_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
 #endif // !IRCCD_PLUGIN_HPP
--- a/libirccd/irccd/plugin_service.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/plugin_service.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -75,7 +75,7 @@
     auto plugin = get(name);
 
     if (!plugin)
-        throw std::invalid_argument(string_util::sprintf("plugin %s not found", name));
+        throw plugin_error(plugin_error::not_found);
 
     return plugin;
 }
@@ -154,25 +154,22 @@
     if (has(name))
         return;
 
-    try {
-        std::shared_ptr<plugin> plugin;
+    std::shared_ptr<plugin> plugin;
 
-        if (path.empty())
-            plugin = find(name);
-        else
-            plugin = open(name, std::move(path));
+    if (path.empty())
+        plugin = find(name);
+    else
+        plugin = open(name, std::move(path));
 
-        if (plugin) {
-            plugin->set_config(config(name));
-            plugin->set_formats(formats(name));
-            plugin->set_paths(paths(name));
-            plugin->on_load(irccd_);
+    if (!plugin)
+        throw plugin_error(plugin_error::not_found);
 
-            add(std::move(plugin));
-        }
-    } catch (const std::exception& ex) {
-        log::warning(string_util::sprintf("plugin %s: %s", name, ex.what()));
-    }
+    plugin->set_config(config(name));
+    plugin->set_formats(formats(name));
+    plugin->set_paths(paths(name));
+    plugin->on_load(irccd_);
+
+    add(std::move(plugin));
 }
 
 void plugin_service::reload(const std::string& name)
--- a/libirccd/irccd/rule.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/rule.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -56,4 +56,32 @@
            match_set(events_, event);
 }
 
+const boost::system::error_category& rule_category()
+{
+    static const class category : public boost::system::error_category {
+    public:
+        const char* name() const noexcept override
+        {
+            return "rule";
+        }
+
+        std::string message(int e) const override
+        {
+            switch (static_cast<rule_error::error>(e)) {
+            case rule_error::error::invalid_action:
+                return "invalid action given";
+            default:
+                return "no error";
+            }
+        }
+    } category;
+
+    return category;
+}
+
+boost::system::error_code make_error_code(rule_error::error e)
+{
+    return {static_cast<int>(e), rule_category()};
+}
+
 } // !irccd
--- a/libirccd/irccd/rule.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/rule.hpp	Tue Nov 28 13:57:09 2017 +0100
@@ -24,11 +24,13 @@
  * \brief Rule description
  */
 
+#include "sysconfig.hpp"
+
 #include <cassert>
 #include <string>
 #include <unordered_set>
 
-#include "sysconfig.hpp"
+#include <boost/system/system_error.hpp>
 
 namespace irccd {
 
@@ -212,6 +214,57 @@
     }
 };
 
+/**
+ * \brief Rule error.
+ */
+class rule_error : public boost::system::system_error {
+public:
+    /**
+     * \brief Rule related errors (4000..4999)
+     */
+    enum class error {
+        //!< No error.
+        no_error = 0,
+
+        //!< Invalid action given.
+        invalid_action = 4000,
+
+        //!< Invalid rule index.
+        invalid_index,
+    };
+
+    /**
+     * Inherited constructors.
+     */
+    using system_error::system_error;
+};
+
+/**
+ * Get the rule error category singleton.
+ *
+ * \return the singleton
+ */
+const boost::system::error_category& rule_category();
+
+/**
+ * Create a boost::system::error_code from rule_error::error enum.
+ *
+ * \param e the error code
+ */
+boost::system::error_code make_error_code(rule_error::error e);
+
 } // !irccd
 
+namespace boost {
+
+namespace system {
+
+template <>
+struct is_error_code_enum<irccd::rule_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
 #endif // !IRCCD_RULE_HPP
--- a/libirccd/irccd/server.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/server.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -707,4 +707,48 @@
     send(string_util::sprintf("WHOIS %s %s", target, target));
 }
 
+const boost::system::error_category& server_category()
+{
+    static const class category : public boost::system::error_category {
+    public:
+        const char* name() const noexcept override
+        {
+            return "server";
+        }
+
+        std::string message(int e) const override
+        {
+            switch (static_cast<server_error::error>(e)) {
+            case server_error::not_found:
+                return "server not found";
+            case server_error::error::invalid_identifier:
+                return "invalid identifier";
+            case server_error::error::not_connected:
+                return "server is not connected";
+            case server_error::error::already_connected:
+                return "server is already connected";
+            case server_error::error::invalid_port_number:
+                return "invalid port number specified";
+            case server_error::error::invalid_reconnect_tries_number:
+                return "invalid number of reconnection tries";
+            case server_error::error::invalid_reconnect_timeout_number:
+                return "invalid reconnect timeout number";
+            case server_error::error::invalid_host:
+                return "invalid hostname";
+            case server_error::error::ssl_disabled:
+                return "ssl is not enabled";
+            default:
+                return "no error";
+            }
+        }
+    } category;
+
+    return category;
+}
+
+boost::system::error_code make_error_code(server_error::error e)
+{
+    return {static_cast<int>(e), server_category()};
+}
+
 } // !irccd
--- a/libirccd/irccd/server.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/server.hpp	Tue Nov 28 13:57:09 2017 +0100
@@ -932,6 +932,81 @@
     virtual void whois(std::string target);
 };
 
+/**
+ * \brief Server error.
+ */
+class server_error : public boost::system::system_error {
+public:
+    /**
+     * \brief Server related errors (1000..1999)
+     */
+    enum error {
+        //!< No error.
+        no_error = 0,
+
+        //!< The specified server was not found.
+        not_found = 1000,
+
+        //!< The specified identifier is invalid.
+        invalid_identifier,
+
+        //!< The server is not connected.
+        not_connected,
+
+        //!< The server is already connected.
+        already_connected,
+
+        //!< Server with same name already exists.
+        already_exists,
+
+        //!< The specified port number is invalid.
+        invalid_port_number,
+
+        //!< The specified reconnect tries number is invalid.
+        invalid_reconnect_tries_number,
+
+        //!< The specified reconnect reconnect number is invalid.
+        invalid_reconnect_timeout_number,
+
+        //!< The specified host was invalid.
+        invalid_host,
+
+        //!< SSL was requested but is disabled.
+        ssl_disabled,
+    };
+
+    /**
+     * Inherited constructors.
+     */
+    using system_error::system_error;
+};
+
+/**
+ * Get the server error category singleton.
+ *
+ * \return the singleton
+ */
+const boost::system::error_category& server_category();
+
+/**
+ * Create a boost::system::error_code from server_error::error enum.
+ *
+ * \param e the error code
+ */
+boost::system::error_code make_error_code(server_error::error e);
+
 } // !irccd
 
+namespace boost {
+
+namespace system {
+
+template <>
+struct is_error_code_enum<irccd::server_error::error> : public std::true_type {
+};
+
+} // !system
+
+} // !boost
+
 #endif // !IRCCD_SERVER_HPP
--- a/libirccd/irccd/transport_client.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/transport_client.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -23,12 +23,6 @@
 
 namespace irccd {
 
-void transport_client::close()
-{
-    state_ = state_t::closing;
-    parent_.clients().erase(shared_from_this());
-}
-
 void transport_client::recv(network_recv_handler handler)
 {
     if (state_ != state_t::closing)
@@ -48,45 +42,32 @@
     send({{ "command", cname }}, std::move(handler));
 }
 
-void transport_client::error(const nlohmann::json& data, network_send_handler handler)
-{
-    send(std::move(data), std::move(handler));
-    set_state(state_t::closing);
-}
-
-void transport_client::error(const std::string& cname, const std::string& reason, network_send_handler handler)
+void transport_client::error(boost::system::error_code code, network_send_handler handler)
 {
-    assert(!cname.empty());
-    assert(!reason.empty());
-
-    error({
-        { "command",    cname   },
-        { "error",      reason  }
-    }, std::move(handler));
+    error(std::move(code), "", std::move(handler));
 }
 
-void transport_client::error(const std::string& reason, network_send_handler handler)
+void transport_client::error(boost::system::error_code code,
+                             std::string cname,
+                             network_send_handler handler)
 {
-    assert(!reason.empty());
+    assert(code);
 
-    error({{ "error", reason }}, std::move(handler));
-}
-
-void transport_client::error(const std::string& cname, network_errc reason, network_send_handler handler)
-{
-    assert(!cname.empty());
+    auto json = nlohmann::json::object({
+        { "error", code.value() }
+    });
 
-    error({
-        { "command",    cname                       },
-        { "error",      static_cast<int>(reason)    }
-    }, std::move(handler));
-}
+    if (!cname.empty())
+        json["command"] = std::move(cname);
 
-void transport_client::error(network_errc reason, network_send_handler handler)
-{
-    assert(reason != network_errc::no_error);
+    send(std::move(json), [this, handler] (auto code) {
+        if (handler)
+            handler(code);
 
-    error({{ "error", static_cast<int>(reason) }}, std::move(handler));
+        parent_.clients().erase(shared_from_this());
+    });
+
+    state_ = state_t::closing;
 }
 
 } // !irccd
--- a/libirccd/irccd/transport_client.hpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/transport_client.hpp	Tue Nov 28 13:57:09 2017 +0100
@@ -45,8 +45,6 @@
     state_t state_{state_t::authenticating};
     transport_server& parent_;
 
-    void close();
-
 protected:
     /**
      * Request a receive operation.
@@ -81,6 +79,11 @@
     }
 
     /**
+     * Virtual destructor defaulted.
+     */
+    virtual ~transport_client() = default;
+
+    /**
      * Get the transport server parent.
      *
      * \return the parent
@@ -123,6 +126,11 @@
     /**
      * Start receiving if not closed.
      *
+     * Possible error codes:
+     *
+     *   - boost::system::errc::network_down in case of errors,
+     *   - boost::system::errc::invalid_argument if the JSON message is invalid.
+     *
      * \pre handler != nullptr
      * \param handler the handler
      */
@@ -131,6 +139,10 @@
     /**
      * Start sending if not closed.
      *
+     * Possible error codes:
+     *
+     *   - boost::system::errc::network_down in case of errors,
+     *
      * \param json the json message
      * \param handler the optional handler
      */
@@ -145,58 +157,24 @@
     void success(const std::string& cname, network_send_handler handler = nullptr);
 
     /**
-     * Send a error message, the state is set to closing.
-     *
-     * The invocation is similar to:
-     *
-     * ````cpp
-     * set_state(state_t::closing);
-     * send(message, handler);
-     * ````
+     * Send an error code to the client.
      *
-     * \pre message is not null
-     * \pre data.is_object()
-     * \param message the error message
-     * \param handler the handler
-     */
-    void error(const nlohmann::json& data, network_send_handler handler = nullptr);
-
-    /**
-     * Convenient error overload.
-     *
-     * \param cname the command name
-     * \pre !reason.empty()
-     * \param reason the reason string
+     * \pre code is not 0
+     * \param code the error code
      * \param handler the optional handler
      */
-    void error(const std::string& cname, const std::string& reason, network_send_handler handler = nullptr);
-
-    /**
-     * Convenient error overload.
-     *
-     * \pre !reason.empty()
-     * \param reason the reason string
-     * \param handler the handler
-     */
-    void error(const std::string& reason, network_send_handler handler = nullptr);
+    void error(boost::system::error_code code, network_send_handler handler = nullptr);
 
     /**
-     * Convenient error overload.
+     * Send an error code to the client.
      *
-     * \param cname the command name
-     * \param reason the error code
+     * \pre code is not 0
+     * \param code the error code
      * \param handler the optional handler
      */
-    void error(const std::string& cname, network_errc reason, network_send_handler handler = nullptr);
-
-    /**
-     * Convenient error overload.
-     *
-     * \pre reason != network_errc::no_error
-     * \param reason the reason string
-     * \param handler the handler
-     */
-    void error(network_errc reason, network_send_handler handler = nullptr);
+    void error(boost::system::error_code code,
+               std::string cname,
+               network_send_handler handler = nullptr);
 };
 
 } // !irccd
--- a/libirccd/irccd/transport_server.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/transport_server.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -20,6 +20,7 @@
 
 #include <cassert>
 
+#include "irccd.hpp"
 #include "json_util.hpp"
 #include "transport_server.hpp"
 
@@ -40,15 +41,15 @@
         auto password = json_util::to_string(message["password"]);
 
         if (command != "auth") {
-            client->error(network_errc::auth_required);
-            code = network_errc::auth_required;
+            client->error(irccd_error::auth_required);
+            code = irccd_error::auth_required;
         } else if (password != password_) {
-            client->error(network_errc::invalid_auth);
-            code = network_errc::invalid_auth;
+            client->error(irccd_error::invalid_auth);
+            code = irccd_error::invalid_auth;
         } else {
             client->set_state(transport_client::state_t::ready);
             client->success("auth");
-            code = network_errc::no_error;
+            code = irccd_error::no_error;
         }
 
         handler(std::move(code), std::move(client));
@@ -78,8 +79,10 @@
             handler(std::move(code), std::move(client));
         else if (!password_.empty())
             do_auth(std::move(client), std::move(handler));
-        else
+        else {
+            client->set_state(transport_client::state_t::ready);
             handler(std::move(code), std::move(client));
+        }
     });
 }
 
--- a/libirccd/irccd/transport_service.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccd/irccd/transport_service.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -32,18 +32,24 @@
     assert(object.is_object());
 
     auto name = object.find("command");
-    if (name == object.end() || !name->is_string())
+
+    if (name == object.end() || !name->is_string()) {
+        tc->error(irccd_error::invalid_message);
         return;
+    }
 
     auto cmd = irccd_.commands().find(*name);
 
     if (!cmd)
-        tc->error(*name, "command does not exist");
+        tc->error(irccd_error::invalid_command, name->get<std::string>());
     else {
         try {
             cmd->exec(irccd_, *tc, object);
+        } catch (const boost::system::system_error& ex) {
+            tc->error(ex.code(), cmd->name());
         } catch (const std::exception& ex) {
-            tc->error(cmd->name(), ex.what());
+            log::warning() << "transport: unknown error not reported" << std::endl;
+            log::warning() << "transport: " << ex.what() << std::endl;
         }
     }
 }
@@ -51,11 +57,20 @@
 void transport_service::do_recv(std::shared_ptr<transport_client> tc)
 {
     tc->recv([this, tc] (auto code, auto json) {
-        if (code)
-            log::warning() << "transport: " << code.message() << std::endl;
-        else {
-            do_recv(tc);
-            handle_command(std::move(tc), json);
+        switch (code.value()) {
+        case boost::system::errc::network_down:
+            log::warning("transport: client disconnected");
+            break;
+            case boost::system::errc::invalid_argument:
+            tc->error(irccd_error::invalid_message);
+            break;
+        default:
+            handle_command(tc, json);
+
+            if (tc->state() == transport_client::state_t::ready)
+                do_recv(std::move(tc));
+
+            break;
         }
     });
 }
@@ -64,7 +79,7 @@
 {
     ts.accept([this, &ts] (auto code, auto client) {
         if (code)
-            log::warning() << "transport: " << code.message() << std::endl;
+            log::warning() << "transport: new client error: " << code.message() << std::endl;
         else {
             do_accept(ts);
             do_recv(std::move(client));
--- a/libirccdctl/CMakeLists.txt	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccdctl/CMakeLists.txt	Tue Nov 28 13:57:09 2017 +0100
@@ -47,6 +47,7 @@
     LIBRARIES
         ${LIBRARIES}
         libcommon
+        libirccd
     PUBLIC_INCLUDES
         $<BUILD_INTERFACE:${libirccdctl_SOURCE_DIR}>
 )
--- a/libirccdctl/irccd/ctl/controller.cpp	Tue Nov 28 12:20:58 2017 +0100
+++ b/libirccdctl/irccd/ctl/controller.cpp	Tue Nov 28 13:57:09 2017 +0100
@@ -18,10 +18,14 @@
 
 #include <cassert>
 
-#include <irccd/network_errc.hpp>
 #include <irccd/sysconfig.hpp>
 #include <irccd/json_util.hpp>
 
+#include <irccd/irccd.hpp>
+#include <irccd/server.hpp>
+#include <irccd/plugin.hpp>
+#include <irccd/rule.hpp>
+
 #include "controller.hpp"
 #include "connection.hpp"
 
@@ -42,12 +46,7 @@
             return;
         }
 
-        recv([handler, info, this] (auto code, auto message) {
-            if (message["error"].is_number_integer())
-                code = static_cast<network_errc>(message["error"].template get<int>());
-            if (message["error"].is_string())
-                code = network_errc::invalid_auth;
-
+        recv([handler, info] (auto code, auto) {
             handler(std::move(code), std::move(info));
         });
     });
@@ -62,9 +61,9 @@
         }
 
         if (json_util::to_string(message["program"]) != "irccd")
-            handler(network_errc::invalid_program, std::move(message));
+            handler(irccd_error::not_irccd, std::move(message));
         else if (json_util::to_int(message["major"]) != IRCCD_VERSION_MAJOR)
-            handler(network_errc::invalid_version, std::move(message));
+            handler(irccd_error::incompatible_version, std::move(message));
         else {
             if (!password_.empty())
                 authenticate(std::move(handler), message);
@@ -92,7 +91,26 @@
 
     // TODO: ensure connected.
 
-    conn_.recv(std::move(handler));
+    conn_.recv([handler] (auto code, auto msg) {
+        if (code) {
+            handler(std::move(code), std::move(msg));
+            return;
+        }
+
+        auto e = json_util::to_int(msg["error"]);
+
+        // TODO: maybe better to pass category instead of using static ranges.
+        if (e > 0 && e < 1000)
+            code = make_error_code(static_cast<irccd_error::error>(e));
+        else if (e >= 1000 && e < 2000)
+            code = make_error_code(static_cast<server_error::error>(e));
+        else if (e >= 2000 && e < 3000)
+            code = make_error_code(static_cast<plugin_error::error>(e));
+        else if (e >= 4000 && e < 4000)
+            code = make_error_code(static_cast<rule_error::error>(e));
+
+        handler(std::move(code), std::move(msg));
+    });
 }
 
 void controller::send(nlohmann::json message, network_send_handler handler)