changeset 644:aae6d5a2b28d

Irccd: change how configuration is loaded
author David Demelier <markand@malikania.fr>
date Fri, 23 Mar 2018 14:00:03 +0100
parents eff84e503c26
children a63d73b456d5
files irccd-test/main.cpp irccdctl/main.cpp irccdctl/rule_add_cli.cpp irccdctl/rule_edit_cli.cpp irccdctl/rule_move_cli.cpp irccdctl/server_connect_cli.cpp libcommon/CMakeLists.txt libcommon/irccd/json_util.cpp libcommon/irccd/json_util.hpp libcommon/irccd/string_util.hpp libirccd-test/irccd/test/plugin_test.cpp libirccd/irccd/daemon/command/plugin_config_command.cpp libirccd/irccd/daemon/command/plugin_info_command.cpp libirccd/irccd/daemon/command/plugin_load_command.cpp libirccd/irccd/daemon/command/plugin_reload_command.cpp libirccd/irccd/daemon/command/plugin_unload_command.cpp libirccd/irccd/daemon/command/rule_add_command.cpp libirccd/irccd/daemon/command/rule_edit_command.cpp libirccd/irccd/daemon/command/rule_info_command.cpp libirccd/irccd/daemon/command/rule_move_command.cpp libirccd/irccd/daemon/command/rule_remove_command.cpp libirccd/irccd/daemon/command/server_connect_command.cpp libirccd/irccd/daemon/command/server_disconnect_command.cpp libirccd/irccd/daemon/command/server_info_command.cpp libirccd/irccd/daemon/command/server_invite_command.cpp libirccd/irccd/daemon/command/server_join_command.cpp libirccd/irccd/daemon/command/server_kick_command.cpp libirccd/irccd/daemon/command/server_me_command.cpp libirccd/irccd/daemon/command/server_message_command.cpp libirccd/irccd/daemon/command/server_mode_command.cpp libirccd/irccd/daemon/command/server_nick_command.cpp libirccd/irccd/daemon/command/server_notice_command.cpp libirccd/irccd/daemon/command/server_part_command.cpp libirccd/irccd/daemon/command/server_reconnect_command.cpp libirccd/irccd/daemon/command/server_topic_command.cpp libirccd/irccd/daemon/server.cpp libirccd/irccd/daemon/server.hpp libirccd/irccd/daemon/server_util.cpp libirccd/irccd/daemon/server_util.hpp libirccd/irccd/daemon/service/server_service.cpp libirccd/irccd/daemon/transport_util.cpp tests/src/libirccd/util/main.cpp
diffstat 42 files changed, 541 insertions(+), 690 deletions(-) [+]
line wrap: on
line diff
--- a/irccd-test/main.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/irccd-test/main.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -73,7 +73,7 @@
     auto s = daemon->servers().get(name);
 
     if (!s) {
-        s = std::make_shared<debug_server>(io, std::move(name));
+        s = std::make_shared<debug_server>(io, std::move(name), "localhost");
         daemon->servers().add(s);
     }
 
--- a/irccdctl/main.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/irccdctl/main.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -122,7 +122,6 @@
 {
     std::unique_ptr<connection> conn;
     std::string host;
-    std::uint16_t port;
     ini::section::const_iterator it;
 
     if ((it = sc.find("host")) == sc.end())
@@ -133,16 +132,19 @@
     if ((it = sc.find("port")) == sc.end())
         throw std::invalid_argument("missing port parameter");
 
-    port = string_util::to_uint<std::uint16_t>(it->value());
+    const auto port = string_util::to_uint<std::uint16_t>(it->value());
+
+    if (!port)
+        throw std::invalid_argument("invalid port parameter");
 
     if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value()))
 #if defined(HAVE_SSL)
-        conn = std::make_unique<tls_connection>(service, ctx, host, port);
+        conn = std::make_unique<tls_connection>(service, ctx, host, *port);
 #else
         throw std::runtime_error("SSL disabled");
 #endif
     else
-        conn = std::make_unique<ip_connection>(service, host, port);
+        conn = std::make_unique<ip_connection>(service, host, *port);
 
     return conn;
 }
@@ -305,9 +307,12 @@
     if ((it = options.find("-p")) == options.end() && (it = options.find("--port")) == options.end())
         throw std::invalid_argument("missing port argument (-p or --port)");
 
-    auto port = string_util::to_uint<std::uint16_t>(it->second);
+    const auto port = string_util::to_uint<std::uint16_t>(it->second);
 
-    return std::make_unique<ip_connection>(service, host, port);
+    if (!port)
+        throw std::invalid_argument("invalid port argument");
+
+    return std::make_unique<ip_connection>(service, host, *port);
 }
 
 /*
--- a/irccdctl/rule_add_cli.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/irccdctl/rule_add_cli.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -21,6 +21,8 @@
 
 #include "rule_add_cli.hpp"
 
+using irccd::string_util::to_uint;
+
 namespace irccd {
 
 namespace ctl {
@@ -72,10 +74,15 @@
     }
 
     // Index.
-    if (result.count("-i") > 0)
-        json["index"] = string_util::to_uint<unsigned>(result.find("-i")->second);
-    if (result.count("--index") > 0)
-        json["index"] = string_util::to_uint<unsigned>(result.find("--index")->second);
+    boost::optional<unsigned> index;
+
+    if (result.count("-i") > 0 && !(index = to_uint(result.find("-i")->second)))
+        throw std::invalid_argument("invalid index argument");
+    if (result.count("--index") > 0 && !(index = to_uint(result.find("--index")->second)))
+        throw std::invalid_argument("invalid index argument");
+
+    if (index)
+        json["index"] = *index;
 
     // And action.
     if (copy[0] != "accept" && copy[0] != "drop")
--- a/irccdctl/rule_edit_cli.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/irccdctl/rule_edit_cli.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -94,7 +94,10 @@
     }
 
     // Index.
-    json["index"] = string_util::to_uint<unsigned>(copy[0]);
+    const auto index = string_util::to_uint(copy[0]);
+
+    if (!index)
+        throw std::invalid_argument("invalid index argument");
 
     request(ctl, json);
 }
--- a/irccdctl/rule_move_cli.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/irccdctl/rule_move_cli.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -34,13 +34,18 @@
     if (args.size() < 2)
         throw std::invalid_argument("rule-move requires 2 arguments");
 
-    int from = string_util::to_int<int>(args[0]);
-    int to = string_util::to_int<int>(args[1]);
+    const auto from = string_util::to_int<int>(args[0]);
+    const auto to = string_util::to_int<int>(args[1]);
+
+    if (!from)
+        throw std::invalid_argument("invalid source argument");
+    if (!to)
+        throw std::invalid_argument("invalid destination argument");
 
     request(ctl, {
         { "command",    "rule-move" },
-        { "from",       from        },
-        { "to",         to          }
+        { "from",       *from       },
+        { "to",         *to         }
     });
 }
 
--- a/irccdctl/server_connect_cli.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/irccdctl/server_connect_cli.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -69,8 +69,14 @@
         { "host", copy[1] }
     });
 
-    if (copy.size() == 3)
-        object["port"] = string_util::to_int(copy[2]);
+    if (copy.size() == 3) {
+        const auto port = string_util::to_int(copy[2]);
+
+        if (!port)
+            throw std::invalid_argument("invalid port given");
+
+        object["port"] = *port;
+    }
 
     if (result.count("-S") > 0 || result.count("--ssl-verify") > 0)
         object["sslVerify"] = true;
--- a/libcommon/CMakeLists.txt	Wed Mar 21 19:45:55 2018 +0100
+++ b/libcommon/CMakeLists.txt	Fri Mar 23 14:00:03 2018 +0100
@@ -37,7 +37,6 @@
     SOURCES
     ${libcommon_SOURCE_DIR}/irccd/config.cpp
     ${libcommon_SOURCE_DIR}/irccd/ini.cpp
-    ${libcommon_SOURCE_DIR}/irccd/json_util.cpp
     ${libcommon_SOURCE_DIR}/irccd/options.cpp
     ${libcommon_SOURCE_DIR}/irccd/string_util.cpp
     ${libcommon_SOURCE_DIR}/irccd/system.cpp
--- a/libcommon/irccd/json_util.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,160 +0,0 @@
-/*
- * json_util.cpp -- utilities for JSON
- *
- * Copyright (c) 2018 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 "json_util.hpp"
-#include "string_util.hpp"
-
-namespace irccd {
-
-namespace json_util {
-
-boost::optional<nlohmann::json> get(const nlohmann::json& json,
-                                    const nlohmann::json::json_pointer& key) noexcept
-{
-    // Unfortunately, there is no find using pointer yet.
-    try {
-        return json.at(key);
-    } catch (...) {
-        return boost::none;
-    }
-}
-
-boost::optional<bool> get_bool(const nlohmann::json& json,
-                               const nlohmann::json::json_pointer& key) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v || !v->is_boolean())
-        return boost::none;
-
-    return v->get<bool>();
-}
-
-boost::optional<std::uint64_t> get_int(const nlohmann::json& json,
-                                       const nlohmann::json::json_pointer& key) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v || !v->is_number_integer())
-        return boost::none;
-
-    return v->get<std::uint64_t>();
-}
-
-boost::optional<std::uint64_t> get_uint(const nlohmann::json& json,
-                                        const nlohmann::json::json_pointer& key) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v || !v->is_number_unsigned())
-        return boost::none;
-
-    return v->get<std::uint64_t>();
-}
-
-boost::optional<std::string> get_string(const nlohmann::json& json,
-                                        const nlohmann::json::json_pointer& key) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v || !v->is_string())
-        return boost::none;
-
-    return v->get<std::string>();
-}
-
-boost::optional<bool> optional_bool(const nlohmann::json& json,
-                                    const nlohmann::json::json_pointer& key,
-                                    bool def) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v)
-        return def;
-    if (!v->is_boolean())
-        return boost::none;
-
-    return v->get<bool>();
-}
-
-boost::optional<std::int64_t> optional_int(const nlohmann::json& json,
-                                           const nlohmann::json::json_pointer& key,
-                                           std::int64_t def) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v)
-        return def;
-    if (!v->is_number_integer())
-        return boost::none;
-
-    return v->get<std::int64_t>();
-}
-
-boost::optional<std::uint64_t> optional_uint(const nlohmann::json& json,
-                                             const nlohmann::json::json_pointer& key,
-                                             std::uint64_t def) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v)
-        return def;
-    if (!v->is_number_unsigned())
-        return boost::none;
-
-    return v->get<std::uint64_t>();
-}
-
-boost::optional<std::string> optional_string(const nlohmann::json& json,
-                                             const nlohmann::json::json_pointer& key,
-                                             const std::string& def) noexcept
-{
-    const auto v = get(json, key);
-
-    if (!v)
-        return def;
-    if (!v->is_string())
-        return boost::none;
-
-    return v->get<std::string>();
-}
-
-std::string pretty(const nlohmann::json& value)
-{
-    switch (value.type()) {
-    case nlohmann::json::value_t::boolean:
-        return value.get<bool>() ? "true" : "false";
-    case nlohmann::json::value_t::string:
-        return value.get<std::string>();
-    default:
-        return value.dump();
-    }
-}
-
-bool contains(const nlohmann::json& array, const nlohmann::json& value) noexcept
-{
-    for (const auto &v : array)
-        if (v == value)
-            return true;
-
-    return false;
-}
-
-} // !json_util
-
-} // !irccd
--- a/libcommon/irccd/json_util.hpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libcommon/irccd/json_util.hpp	Fri Mar 23 14:00:03 2018 +0100
@@ -24,8 +24,6 @@
  * \brief Utilities for JSON.
  */
 
-#include <irccd/sysconfig.hpp>
-
 #include <cstdint>
 #include <string>
 
@@ -47,53 +45,106 @@
  * \param key the pointer to the object
  * \return the value or boost::none if not found
  */
-IRCCD_EXPORT
-boost::optional<nlohmann::json> get(const nlohmann::json& json,
-                                    const nlohmann::json::json_pointer& key) noexcept;
+inline boost::optional<nlohmann::json> get(const nlohmann::json& json,
+                                           const nlohmann::json::json_pointer& key) noexcept
+{
+    // Unfortunately, there is no find using pointer yet.
+    try {
+        return json.at(key);
+    } catch (...) {
+        return boost::none;
+    }
+}
+
+/**
+ * Convenient overload with simple key.
+ *
+ * \param json the JSON object/array
+ * \param key the pointer to the object
+ * \return the value or boost::none if not found
+ */
+inline boost::optional<nlohmann::json> get(const nlohmann::json& json,
+                                           const std::string& key) noexcept
+{
+    const auto it = json.find(key);
+
+    if (it == json.end())
+        return boost::none;
+
+    return *it;
+}
 
 /**
  * Get a bool or null if not found or invalid.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \return the value or boost::none if not found or invalid
  */
-IRCCD_EXPORT
-boost::optional<bool> get_bool(const nlohmann::json& json,
-                               const nlohmann::json::json_pointer& key) noexcept;
+template <typename Key>
+inline boost::optional<bool> get_bool(const nlohmann::json& json, const Key& key) noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v || !v->is_boolean())
+        return boost::none;
+
+    return v->template get<bool>();
+}
 
 /**
  * Get a 64 bit signed integer or null if not found or invalid.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \return the value or boost::none if not found or invalid
  */
-IRCCD_EXPORT
-boost::optional<std::uint64_t> get_int(const nlohmann::json& json,
-                                       const nlohmann::json::json_pointer& key) noexcept;
+template <typename Key>
+inline boost::optional<std::int64_t> get_int(const nlohmann::json& json, const Key& key) noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v || !v->is_number_integer())
+        return boost::none;
+
+    return v->template get<std::int64_t>();
+}
 
 /**
  * Get a 64 bit unsigned integer or null if not found or invalid.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \return the value or boost::none if not found or invalid
  */
-IRCCD_EXPORT
-boost::optional<std::uint64_t> get_uint(const nlohmann::json& json,
-                                        const nlohmann::json::json_pointer& key) noexcept;
+template <typename Key>
+inline boost::optional<std::uint64_t> get_uint(const nlohmann::json& json, const Key& key) noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v || !v->is_number_unsigned())
+        return boost::none;
+
+    return v->template get<std::uint64_t>();
+}
 
 /**
  * Get a string or null if not found or invalid.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \return the value or boost::none if not found or invalid
  */
-IRCCD_EXPORT
-boost::optional<std::string> get_string(const nlohmann::json& json,
-                                        const nlohmann::json::json_pointer& key) noexcept;
+template <typename Key>
+inline boost::optional<std::string> get_string(const nlohmann::json& json, const Key& key) noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v || !v->is_string())
+        return boost::none;
+
+    return v->template get<std::string>();
+}
 
 /**
  * Get an optional bool.
@@ -102,14 +153,22 @@
  * a bool, return boost::none, otherwise return the value.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \param def the default value
  * \return the value, boost::none or def
  */
-IRCCD_EXPORT
-boost::optional<bool> optional_bool(const nlohmann::json& json,
-                                    const nlohmann::json::json_pointer& key,
-                                    bool def = false) noexcept;
+template <typename Key>
+inline boost::optional<bool> optional_bool(const nlohmann::json& json, const Key& key, bool def = false) noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v)
+        return def;
+    if (!v->is_boolean())
+        return boost::none;
+
+    return v->template get<bool>();
+}
 
 /**
  * Get an optional integer.
@@ -118,14 +177,24 @@
  * an integer, return boost::none, otherwise return the value.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \param def the default value
  * \return the value, boost::none or def
  */
-IRCCD_EXPORT
-boost::optional<std::int64_t> optional_int(const nlohmann::json& json,
-                                           const nlohmann::json::json_pointer& key,
-                                           std::int64_t def = 0) noexcept;
+template <typename Key>
+inline boost::optional<std::int64_t> optional_int(const nlohmann::json& json,
+                                                  const Key& key,
+                                                  std::int64_t def = 0) noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v)
+        return def;
+    if (!v->is_number_integer())
+        return boost::none;
+
+    return v->template get<std::int64_t>();
+}
 
 /**
  * Get an optional unsigned integer.
@@ -134,14 +203,24 @@
  * an unsigned integer, return boost::none, otherwise return the value.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \param def the default value
  * \return the value, boost::none or def
  */
-IRCCD_EXPORT
-boost::optional<std::uint64_t> optional_uint(const nlohmann::json& json,
-                                             const nlohmann::json::json_pointer& key,
-                                             std::uint64_t def = 0) noexcept;
+template <typename Key>
+inline boost::optional<std::uint64_t> optional_uint(const nlohmann::json& json,
+                                                    const Key& key,
+                                                    std::uint64_t def = 0) noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v)
+        return def;
+    if (!v->is_number_unsigned())
+        return boost::none;
+
+    return v->template get<std::uint64_t>();
+}
 
 /**
  * Get an optional string.
@@ -150,36 +229,73 @@
  * a string, return boost::none, otherwise return the value.
  *
  * \param json the JSON object/array
- * \param key the pointer to the object
+ * \param key the pointer or property key
  * \param def the default value
  * \return the value, boost::none or def
  */
-IRCCD_EXPORT
-boost::optional<std::string> optional_string(const nlohmann::json& json,
-                                             const nlohmann::json::json_pointer& key,
-                                             const std::string& def = "") noexcept;
+template <typename Key>
+inline boost::optional<std::string> optional_string(const nlohmann::json& json,
+                                                    const Key& key,
+                                                    const std::string& def = "") noexcept
+{
+    const auto v = get(json, key);
+
+    if (!v)
+        return def;
+    if (!v->is_string())
+        return boost::none;
+
+    return v->template get<std::string>();
+}
 
 /**
  * Print the value as human readable.
  *
+ * \note This only works on flat objects.
  * \param value the value
+ * \param indent the optional indent for objects/arrays
  * \return the string
  */
-IRCCD_EXPORT
-std::string pretty(const nlohmann::json& value);
+inline std::string pretty(const nlohmann::json& value, int indent = 4)
+{
+    switch (value.type()) {
+    case nlohmann::json::value_t::null:
+        return "null";
+    case nlohmann::json::value_t::string:
+        return value.get<std::string>();
+    case nlohmann::json::value_t::boolean:
+        return value.get<bool>() ? "true" : "false";
+    case nlohmann::json::value_t::number_integer:
+        return std::to_string(value.get<std::int64_t>());
+    case nlohmann::json::value_t::number_unsigned:
+        return std::to_string(value.get<std::uint64_t>());
+    case nlohmann::json::value_t::number_float:
+        return std::to_string(value.get<double>());
+    default:
+        return value.dump(indent);
+    }
+}
 
 /**
- * Check if the array contains the given value.
+ * Check if a JSON array contains a specific value in any order.
  *
- * \param array the array
- * \param value the JSON value to check
- * \return true if present
+ * \param array the JSON array
+ * \param value the JSON value
+ * \return true if value is present
  */
-IRCCD_EXPORT
-bool contains(const nlohmann::json& array, const nlohmann::json& value) noexcept;
+inline bool contains(const nlohmann::json& array, const nlohmann::json& value) noexcept
+{
+    for (const auto& v : array)
+        if (v == value)
+            return true;
+
+    return false;
+}
+
+
 
 } // !json_util
 
 } // !irccd
 
-#endif // !JSON_UTIL_HPP
+#endif // !IRCCD_JSON_UTIL_HPP
--- a/libcommon/irccd/string_util.hpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libcommon/irccd/string_util.hpp	Fri Mar 23 14:00:03 2018 +0100
@@ -38,6 +38,7 @@
 #include <unordered_map>
 
 #include <boost/format.hpp>
+#include <boost/optional.hpp>
 
 namespace irccd {
 
@@ -383,99 +384,52 @@
 }
 
 /**
- * \cond HIDDEN_SYMBOLS
- */
-
-namespace detail {
-
-inline std::invalid_argument make_invalid_argument(const std::string& str)
-{
-    std::ostringstream oss;
-
-    oss << "invalid number '" << str << "'";
-
-    return std::invalid_argument(oss.str());
-}
-
-template <typename T>
-inline std::out_of_range make_out_of_range(const std::string& str, T min, T max)
-{
-    std::ostringstream oss;
-
-    oss << "number '" << str << "' is out of range ";
-    oss << min << ".." << max;
-
-    return std::out_of_range(oss.str());
-}
-
-} // !detail
-
-/**
- * \endcond
- */
-
-/**
  * Convert the given string into a signed integer.
  *
  * \param str the string to convert
  * \param min the minimum value allowed
  * \param max the maximum value allowed
- * \throw std::invalid_argument if the number was not parsed
- * \throw std::out_or_range if the argument is out of the specified range
+ * \return the value or boost::none if not convertible
  */
 template <typename T = int>
-T to_int(const std::string& str, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max())
+boost::optional<T> to_int(const std::string& str,
+                          T min = std::numeric_limits<T>::min(),
+                          T max = std::numeric_limits<T>::max()) noexcept
 {
     static_assert(std::is_signed<T>::value, "must be signed");
 
     char* end;
     auto v = std::strtoll(str.c_str(), &end, 10);
 
-    if (*end != '\0')
-        throw detail::make_invalid_argument(str);
-    if (v < min || v > max)
-        throw detail::make_out_of_range(str, min, max);
+    if (*end != '\0' || v < min || v > max)
+        return boost::none;
 
     return static_cast<T>(v);
 }
 
 /**
- * Convert the given string into an unsigned integer.
- *
- * In contrast to the [std::strtoull][strtoull] function, this functions
- * verifies if the string starts with minus sign and throws an exception if any.
+ * Convert the given string into a unsigned integer.
  *
- * Note, for this you need to have a trimmed string which contains no leading
- * whitespaces.
- *
- * \pre string must be trimmed
+ * \note invalid numbers are valid as well
  * \param str the string to convert
  * \param min the minimum value allowed
  * \param max the maximum value allowed
- * \throw std::invalid_argument if the number was not parsed
- * \throw std::out_or_range if the argument is out of the specified range
- *
- * [strtoull]: http://en.cppreference.com/w/cpp/string/byte/strtoul
+ * \return the value or boost::none if not convertible
  */
 template <typename T = unsigned>
-T to_uint(const std::string& str, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max())
+boost::optional<T> to_uint(const std::string& str,
+                           T min = std::numeric_limits<T>::min(),
+                           T max = std::numeric_limits<T>::max()) noexcept
 {
     static_assert(std::is_unsigned<T>::value, "must be unsigned");
 
-    assert(str.empty() || !std::isspace(str[0]));
-
-    if (str.size() > 0U && str[0] == '-')
-        throw detail::make_out_of_range(str, min, max);
-
     char* end;
     auto v = std::strtoull(str.c_str(), &end, 10);
 
-    if (*end != '\0')
-        throw detail::make_invalid_argument(str);
-    if (v < min || v > max)
-        throw detail::make_out_of_range(str, min, max);
+    if (*end != '\0' || v < min || v > max)
+        return boost::none;
 
-    return v;
+    return static_cast<T>(v);
 }
 
 } // !string_util
--- a/libirccd-test/irccd/test/plugin_test.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd-test/irccd/test/plugin_test.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -41,7 +41,7 @@
 namespace irccd {
 
 plugin_test::plugin_test(std::string name, std::string path)
-    : server_(std::make_shared<journal_server>(service_, "test"))
+    : server_(std::make_shared<journal_server>(service_, "test", "local"))
 {
     server_->set_nickname("irccd");
     plugin_ = std::make_unique<js_plugin>(std::move(name), std::move(path));
--- a/libirccd/irccd/daemon/command/plugin_config_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/plugin_config_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -81,7 +81,7 @@
 
 void plugin_config_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = json_util::get_string(args, "/plugin"_json_pointer);
+    const auto id = json_util::get_string(args, "plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_info_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/plugin_info_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -35,7 +35,7 @@
 
 void plugin_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = json_util::get_string(args, "/plugin"_json_pointer);
+    const auto id = json_util::get_string(args, "plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_load_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/plugin_load_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -35,7 +35,7 @@
 
 void plugin_load_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = json_util::get_string(args, "/plugin"_json_pointer);
+    const auto id = json_util::get_string(args, "plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_reload_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/plugin_reload_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -35,7 +35,7 @@
 
 void plugin_reload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = json_util::get_string(args, "/plugin"_json_pointer);
+    const auto id = json_util::get_string(args, "plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_unload_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/plugin_unload_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -35,7 +35,7 @@
 
 void plugin_unload_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = json_util::get_string(args, "/plugin"_json_pointer);
+    const auto id = json_util::get_string(args, "plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/rule_add_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/rule_add_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -35,7 +35,7 @@
 
 void rule_add_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    auto index = json_util::get_uint(args, "/index"_json_pointer);
+    auto index = json_util::get_uint(args, "index");
 
     if (!index)
         index = irccd.rules().length();
--- a/libirccd/irccd/daemon/command/rule_edit_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/rule_edit_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -49,7 +49,7 @@
     };
 
     // Create a copy to avoid incomplete edition in case of errors.
-    const auto index = json_util::get_uint(args, "/index"_json_pointer);
+    const auto index = json_util::get_uint(args, "index");
 
     if (!index)
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/rule_info_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/rule_info_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -35,7 +35,7 @@
 
 void rule_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto index = json_util::get_uint(args, "/index"_json_pointer);
+    const auto index = json_util::get_uint(args, "index");
 
     if (!index)
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/rule_move_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/rule_move_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -35,8 +35,8 @@
 
 void rule_move_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto from = json_util::get_uint(args, "/from"_json_pointer);
-    const auto to = json_util::get_uint(args, "/to"_json_pointer);
+    const auto from = json_util::get_uint(args, "from");
+    const auto to = json_util::get_uint(args, "to");
 
     if (!from || !to)
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/rule_remove_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/rule_remove_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -34,7 +34,7 @@
 
 void rule_remove_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto index = json_util::get_uint(args, "/index"_json_pointer);
+    const auto index = json_util::get_uint(args, "index");
 
     if (!index || *index >= irccd.rules().length())
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/server_connect_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_connect_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -36,7 +36,7 @@
     auto server = server_util::from_json(irccd.service(), args);
 
     if (irccd.servers().has(server->name()))
-        throw server_error(server->name(), server_error::already_exists);
+        throw server_error(server_error::already_exists);
 
     irccd.servers().add(std::move(server));
     client.success("server-connect");
--- a/libirccd/irccd/daemon/command/server_disconnect_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_disconnect_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -41,7 +41,7 @@
         irccd.servers().clear();
     else {
         if (!it->is_string() || !string_util::is_identifier(it->get<std::string>()))
-            throw server_error("", server_error::invalid_identifier);
+            throw server_error(server_error::invalid_identifier);
 
         const auto name = it->get<std::string>();
 
--- a/libirccd/irccd/daemon/command/server_info_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_info_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -16,6 +16,9 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
+
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
@@ -33,9 +36,14 @@
 
 void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
+    const auto id = json_util::get_string(args, "server");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
+
+    const auto server = irccd.servers().require(*id);
+
+    // Construct the JSON response.
     auto response = nlohmann::json::object();
 
     // General stuff.
--- a/libirccd/irccd/daemon/command/server_invite_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_invite_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,9 +17,9 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
@@ -35,17 +35,18 @@
 
 void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto target = json_util::get_string(args, "/target"_json_pointer);
-    const auto channel = json_util::get_string(args, "/channel"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto target = json_util::get_string(args, "target");
+    const auto channel = json_util::get_string(args, "channel");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!target || target->empty())
-        throw server_error(server->name(), server_error::invalid_nickname);
+        throw server_error(server_error::invalid_nickname);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
 
-    server->invite(*target, *channel);
+    irccd.servers().require(*id)->invite(*target, *channel);
     client.success("server-invite");
 }
 
--- a/libirccd/irccd/daemon/command/server_join_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_join_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,10 +17,10 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/transport_client.hpp>
-#include <irccd/daemon/server_util.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
 
@@ -35,15 +35,16 @@
 
 void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto channel = json_util::get_string(args, "/channel"_json_pointer);
-    const auto password = json_util::get_string(args, "/password"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto channel = json_util::get_string(args, "channel");
+    const auto password = json_util::get_string(args, "password");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
 
-    server->join(*channel, password ? *password : "");
+    irccd.servers().require(*id)->join(*channel, password ? *password : "");
     client.success("server-join");
 }
 
--- a/libirccd/irccd/daemon/command/server_kick_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_kick_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,10 +17,10 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/transport_client.hpp>
-#include <irccd/daemon/server_util.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
 
@@ -35,18 +35,19 @@
 
 void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto target = json_util::get_string(args, "/target"_json_pointer);
-    const auto channel = json_util::get_string(args, "/channel"_json_pointer);
-    const auto reason = json_util::get_string(args, "/reason"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto target = json_util::get_string(args, "target");
+    const auto channel = json_util::get_string(args, "channel");
+    const auto reason = json_util::get_string(args, "reason");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!target || target->empty())
-        throw server_error(server->name(), server_error::invalid_nickname);
+        throw server_error(server_error::invalid_nickname);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
 
-    server->kick(*target, *channel, reason ? *reason : "");
+    irccd.servers().require(*id)->kick(*target, *channel, reason ? *reason : "");
     client.success("server-kick");
 }
 
--- a/libirccd/irccd/daemon/command/server_me_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_me_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,10 +17,10 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
 #include <irccd/daemon/transport_client.hpp>
-#include <irccd/daemon/server_util.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
 
@@ -35,15 +35,18 @@
 
 void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto channel = json_util::get_string(args, "/target"_json_pointer);
-    const auto message = json_util::get_string(args, "/message"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto channel = json_util::get_string(args, "target");
+    const auto message = json_util::optional_string(args, "message", "");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
+    if (!message)
+        throw server_error(server_error::invalid_message);
 
-    server->me(*channel, message ? *message : "");
+    irccd.servers().require(*id)->me(*channel, *message);
     client.success("server-me");
 }
 
--- a/libirccd/irccd/daemon/command/server_message_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_message_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,9 +17,9 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
@@ -35,15 +35,18 @@
 
 void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto channel = json_util::get_string(args, "/target"_json_pointer);
-    const auto message = json_util::get_string(args, "/message"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto channel = json_util::get_string(args, "target");
+    const auto message = json_util::optional_string(args, "message", "");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
+    if (!message)
+        throw server_error(server_error::invalid_message);
 
-    server->message(*channel, message ? *message : "");
+    irccd.servers().require(*id)->message(*channel, *message);
     client.success("server-message");
 }
 
--- a/libirccd/irccd/daemon/command/server_mode_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_mode_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,9 +17,9 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
@@ -35,28 +35,23 @@
 
 void server_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto channel = json_util::get_string(args, "/channel"_json_pointer);
-    const auto mode = json_util::get_string(args, "/mode"_json_pointer);
-
-    if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
-    if (!mode || mode->empty())
-        throw server_error(server->name(), server_error::invalid_mode);
+    const auto id = json_util::get_string(args, "server");
+    const auto channel = json_util::get_string(args, "channel");
+    const auto mode = json_util::get_string(args, "mode");
+    const auto limit = json_util::optional_string(args, "limit", "");
+    const auto user = json_util::optional_string(args, "user", "");
+    const auto mask = json_util::optional_string(args, "mask", "");
 
-    auto limit = json_util::get_string(args, "/limit"_json_pointer);
-    auto user = json_util::get_string(args, "/user"_json_pointer);
-    auto mask = json_util::get_string(args, "/mask"_json_pointer);
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
+    if (!channel || channel->empty())
+        throw server_error(server_error::invalid_channel);
+    if (!mode || mode->empty())
+        throw server_error(server_error::invalid_mode);
+    if (!limit || !user || !mask)
+        throw server_error(server_error::invalid_mode);
 
-    if (!limit)
-        limit = "";
-    if (!user)
-        user = "";
-    if (!mask)
-        mask = "";
-
-    server->mode(*channel, *mode, *limit, *user, *mask);
+    irccd.servers().require(*id)->mode(*channel, *mode, *limit, *user, *mask);
     client.success("server-mode");
 }
 
--- a/libirccd/irccd/daemon/command/server_nick_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_nick_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,9 +17,9 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
@@ -35,14 +35,15 @@
 
 void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto nick = json_util::get_string(args, "/nickname"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto nick = json_util::get_string(args, "nickname");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!nick || nick->empty())
-        throw server_error(server->name(), server_error::invalid_nickname);
+        throw server_error(server_error::invalid_nickname);
 
-    server->set_nickname(*nick);
+    irccd.servers().require(*id)->set_nickname(*nick);
     client.success("server-nick");
 }
 
--- a/libirccd/irccd/daemon/command/server_notice_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_notice_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,9 +17,9 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
@@ -35,15 +35,18 @@
 
 void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto channel = json_util::get_string(args, "/target"_json_pointer);
-    const auto message = json_util::get_string(args, "/message"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto channel = json_util::get_string(args, "target");
+    const auto message = json_util::optional_string(args, "message", "");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
+    if (!message)
+        throw server_error(server_error::invalid_message);
 
-    server->notice(*channel, message ? *message : "");
+    irccd.servers().require(*id)->notice(*channel, *message);
     client.success("server-notice");
 }
 
--- a/libirccd/irccd/daemon/command/server_part_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_part_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,9 +17,9 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
@@ -35,15 +35,18 @@
 
 void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto channel = json_util::get_string(args, "/channel"_json_pointer);
-    const auto reason = json_util::get_string(args, "/reason"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto channel = json_util::get_string(args, "channel");
+    const auto reason = json_util::optional_string(args, "reason", "");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
+    if (!reason)
+        throw server_error(server_error::invalid_message);
 
-    server->part(*channel, reason ? *reason : "");
+    irccd.servers().require(*id)->part(*channel, *reason);
     client.success("server-part");
 }
 
--- a/libirccd/irccd/daemon/command/server_reconnect_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_reconnect_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -42,7 +42,7 @@
             server->reconnect();
     } else {
         if (!it->is_string() || !string_util::is_identifier(it->get<std::string>()))
-            throw server_error("", server_error::invalid_identifier);
+            throw server_error(server_error::invalid_identifier);
 
         irccd.servers().require(it->get<std::string>())->reconnect();
     }
--- a/libirccd/irccd/daemon/command/server_topic_command.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/command/server_topic_command.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -17,9 +17,9 @@
  */
 
 #include <irccd/json_util.hpp>
+#include <irccd/string_util.hpp>
 
 #include <irccd/daemon/irccd.hpp>
-#include <irccd/daemon/server_util.hpp>
 #include <irccd/daemon/transport_client.hpp>
 
 #include <irccd/daemon/service/server_service.hpp>
@@ -35,15 +35,18 @@
 
 void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = server_util::get_identifier(args);
-    const auto server = irccd.servers().require(id);
-    const auto channel = json_util::get_string(args, "/channel"_json_pointer);
-    const auto topic = json_util::get_string(args, "/topic"_json_pointer);
+    const auto id = json_util::get_string(args, "server");
+    const auto channel = json_util::get_string(args, "channel");
+    const auto topic = json_util::optional_string(args, "topic", "");
 
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
     if (!channel || channel->empty())
-        throw server_error(server->name(), server_error::invalid_channel);
+        throw server_error(server_error::invalid_channel);
+    if (!topic)
+        throw server_error(server_error::invalid_message);
 
-    server->topic(*channel, topic ? *topic : "");
+    irccd.servers().require(*id)->topic(*channel, *topic);
     client.success("server-topic");
 }
 
--- a/libirccd/irccd/daemon/server.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/server.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -121,11 +121,14 @@
     return {value, ""};
 }
 
-server::server(boost::asio::io_service& service, std::string name)
+server::server(boost::asio::io_service& service, std::string name, std::string host)
     : name_(std::move(name))
+    , host_(std::move(host))
     , service_(service)
     , timer_(service)
 {
+    assert(!host_.empty());
+
     // Initialize nickname and username.
     auto user = sys::username();
 
@@ -668,9 +671,8 @@
     send(string_util::sprintf("WHOIS %s %s", target, target));
 }
 
-server_error::server_error(std::string name, error code) noexcept
+server_error::server_error(error code) noexcept
     : system_error(make_error_code(code))
-    , name_(std::move(name))
 {
 }
 
@@ -720,6 +722,8 @@
                 return "invalid CTCP VERSION";
             case server_error::invalid_command_char:
                 return "invalid character command";
+            case server_error::invalid_message:
+                return "invalid message";
             case server_error::ssl_disabled:
                 return "ssl is not enabled";
             default:
--- a/libirccd/irccd/daemon/server.hpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/server.hpp	Fri Mar 23 14:00:03 2018 +0100
@@ -474,10 +474,14 @@
     /**
      * Construct a server.
      *
+     * \pre !host.empty()
      * \param service the service
      * \param name the identifier
+     * \param host the hostname
      */
-    server(boost::asio::io_service& service, std::string name);
+    server(boost::asio::io_service& service,
+           std::string name,
+           std::string host = "localhost");
 
     /**
      * Destructor. Close the connection if needed.
@@ -962,31 +966,20 @@
         //!< Invalid command character.
         invalid_command_char,
 
+        //!< Message (PRIVMSG) was invalid
+        invalid_message,
+
         //!< SSL was requested but is disabled.
         ssl_disabled,
     };
 
-private:
-    std::string name_;
-
 public:
     /**
      * Constructor.
      *
-     * \param name the server name
      * \param code the error code
      */
-    server_error(std::string name, error code) noexcept;
-
-    /**
-     * Get the server that triggered the error.
-     *
-     * \return the name
-     */
-    inline const std::string& name() const noexcept
-    {
-        return name_;
-    }
+    server_error(error code) noexcept;
 };
 
 /**
--- a/libirccd/irccd/daemon/server_util.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/server_util.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -25,185 +25,203 @@
 
 #include "server_util.hpp"
 
-using nlohmann::json;
-
-using std::uint16_t;
-using std::string;
-using std::forward;
-
 namespace irccd {
 
 namespace server_util {
 
 namespace {
 
-template <typename... Args>
-std::string require_conf_host(const ini::section& sc, Args&&... args)
+// TODO: ini_util
+std::string optional_string(const ini::section& sc,
+                            const std::string& name,
+                            const std::string& def)
 {
-    auto value = sc.get("host");
+    const auto it = sc.find(name);
+
+    if (it == sc.end())
+        return def;
+
+    return it->value();
+}
 
-    if (value.empty())
-        throw server_error(forward<Args>(args)...);
+// TODO: ini_util
+template <typename Int>
+boost::optional<Int> optional_uint(const ini::section& sc,
+                                   const std::string& name,
+                                   Int def)
+{
+    const auto it = sc.find(name);
 
-    return value.value();
+    if (it == sc.end())
+        return def;
+
+    return string_util::to_uint<Int>(it->value());
 }
 
-template <typename... Args>
-std::string require_conf_id(const ini::section& sc, Args&&... args)
+void from_config_load_identity(server& sv, const ini::section& sc)
 {
-    auto id = sc.get("name");
+    const auto username = optional_string(sc, "username", sv.username());
+    const auto realname = optional_string(sc, "realname", sv.realname());
+    const auto nickname = optional_string(sc, "nickname", sv.nickname());
+    const auto ctcp_version = optional_string(sc, "ctcp-version", sv.ctcp_version());
 
-    if (!string_util::is_identifier(id.value()))
-        throw server_error(forward<Args>(args)...);
+    if (username.empty())
+        throw server_error(server_error::invalid_username);
+    if (realname.empty())
+        throw server_error(server_error::invalid_realname);
+    if (nickname.empty())
+        throw server_error(server_error::invalid_nickname);
+    if (ctcp_version.empty())
+        throw server_error(server_error::invalid_ctcp_version);
 
-    return id.value();
+    sv.set_username(username);
+    sv.set_realname(realname);
+    sv.set_nickname(nickname);
+    sv.set_ctcp_version(ctcp_version);
 }
 
-template <typename Int, typename... Args>
-Int optional_conf_int(const string& value, Args&&... args)
+void from_config_load_channels(server& sv, const ini::section& sc)
 {
-    try {
-        return string_util::to_int<Int>(value);
-    } catch (...) {
-        throw server_error(std::forward<Args>(args)...);
-    }
-}
+    for (const auto& s : sc.get("channels")) {
+        channel channel;
 
-template <typename Int, typename... Args>
-Int optional_conf_uint(const string& value, Args&&... args)
-{
-    try {
-        return string_util::to_uint<Int>(value);
-    } catch (...) {
-        throw server_error(std::forward<Args>(args)...);
+        if (auto pos = s.find(":") != std::string::npos) {
+            channel.name = s.substr(0, pos);
+            channel.password = s.substr(pos + 1);
+        } else
+            channel.name = s;
+
+        sv.join(channel.name, channel.password);
     }
 }
 
-template <typename Int, typename... Args>
-Int optional_json_uint(const json& json, const json::json_pointer& key, Int def, Args&&... args)
+void from_config_load_flags(server& sv, const ini::section& sc)
 {
-    const auto v = json_util::optional_uint(json, key, def);
+    const auto ipv6 = sc.get("ipv6");
+    const auto ssl = sc.get("ssl");
+    const auto ssl_verify = sc.get("ssl-verify");
+    const auto auto_rejoin = sc.get("auto-rejoin");
+    const auto join_invite = sc.get("join-invite");
 
-    if (!v || *v > std::numeric_limits<Int>::max())
-        throw server_error(forward<Args>(args)...);
-
-    return *v;
+    if (string_util::is_boolean(ipv6.value()))
+        sv.set_flags(sv.flags() | server::ipv6);
+    if (string_util::is_boolean(ssl.value()))
+        sv.set_flags(sv.flags() | server::ssl);
+    if (string_util::is_boolean(ssl_verify.value()))
+        sv.set_flags(sv.flags() | server::ssl_verify);
+    if (string_util::is_boolean(auto_rejoin.value()))
+        sv.set_flags(sv.flags() | server::auto_rejoin);
+    if (string_util::is_boolean(join_invite.value()))
+        sv.set_flags(sv.flags() | server::join_invite);
 }
 
-bool optional_json_bool(const json& json, const json::json_pointer& key, bool def = false)
+void from_config_load_numeric_parameters(server& sv, const ini::section& sc)
 {
-    const auto v = json_util::optional_bool(json, key, def);
+    const auto port = optional_uint<std::uint16_t>(sc, "port", sv.port());
+    const auto ping_timeout = optional_uint<uint16_t>(sc, "ping-timeout", sv.ping_timeout());
+    const auto reco_tries = optional_uint<uint8_t>(sc, "reconnect-tries", sv.reconnect_tries());
+    const auto reco_timeout = optional_uint<uint16_t>(sc, "reconnect-delay", sv.reconnect_delay());
 
-    if (!v)
-        return def;
+    if (!port)
+        throw server_error(server_error::invalid_port);
+    if (!ping_timeout)
+        throw server_error(server_error::invalid_ping_timeout);
+    if (!reco_tries)
+        throw server_error(server_error::invalid_reconnect_tries);
+    if (!reco_timeout)
+        throw server_error(server_error::invalid_reconnect_timeout);
 
-    return *v;
+    sv.set_port(*port);
+    sv.set_ping_timeout(*ping_timeout);
+    sv.set_reconnect_tries(*reco_tries);
+    sv.set_reconnect_delay(*reco_timeout);
 }
 
-template <typename... Args>
-std::string optional_json_string(const json& json,
-                                 const json::json_pointer& key,
-                                 const string& def,
-                                 Args&&... args)
+void from_config_load_options(server& sv, const ini::section& sc)
 {
-    const auto v = json_util::optional_string(json, key, def);
+    const auto password = optional_string(sc, "password", "");
+    const auto command_char = optional_string(sc, "command-char", sv.command_char());
 
-    if (!v)
-        throw server_error(forward<Args>(args)...);
-
-    return *v;
+    sv.set_password(password);
+    sv.set_command_char(command_char);
 }
 
-template <typename... Args>
-std::string require_json_id(const nlohmann::json& json, Args&&... args)
+void from_json_load_options(server& sv, const nlohmann::json& object)
 {
-    const auto id = json_util::get_string(json, "/name"_json_pointer);
-
-    if (!id || !string_util::is_identifier(*id))
-        throw server_error(forward<Args>(args)...);
+    const auto port = json_util::optional_uint(object, "port", sv.port());
+    const auto nickname = json_util::optional_string(object, "nickname", sv.nickname());
+    const auto realname = json_util::optional_string(object, "realname", sv.realname());
+    const auto username = json_util::optional_string(object, "username", sv.username());
+    const auto ctcp_version = json_util::optional_string(object, "ctcpVersion", sv.ctcp_version());
+    const auto command = json_util::optional_string(object, "commandChar", sv.command_char());
+    const auto password = json_util::optional_string(object, "password", sv.password());
 
-    return *id;
-}
+    if (!port || *port > std::numeric_limits<std::uint16_t>::max())
+        throw server_error(server_error::invalid_port);
+    if (!nickname)
+        throw server_error(server_error::invalid_nickname);
+    if (!realname)
+        throw server_error(server_error::invalid_realname);
+    if (!username)
+        throw server_error(server_error::invalid_username);
+    if (!ctcp_version)
+        throw server_error(server_error::invalid_ctcp_version);
+    if (!command)
+        throw server_error(server_error::invalid_command_char);
+    if (!password)
+        throw server_error(server_error::invalid_password);
 
-template <typename... Args>
-std::string require_json_host(const nlohmann::json& object, Args&&... args)
-{
-    const auto value = json_util::get_string(object, "/host"_json_pointer);
-
-    if (!value || value->empty())
-        throw server_error(forward<Args>(args)...);
-
-    return *value;
+    sv.set_port(*port);
+    sv.set_nickname(*nickname);
+    sv.set_realname(*realname);
+    sv.set_username(*username);
+    sv.set_ctcp_version(*ctcp_version);
+    sv.set_command_char(*command);
+    sv.set_password(*password);
 }
 
-void load_identity(server& server, const config& cfg, const std::string& identity)
+void from_json_load_flags(server& sv, nlohmann::json object)
 {
-    auto sc = std::find_if(cfg.doc().begin(), cfg.doc().end(), [&] (const auto& sc) {
-        if (sc.key() != "identity")
-            return false;
-
-        auto name = sc.find("name");
-
-        return name != sc.end() && name->value() == identity;
-    });
+    const auto ipv6 = object["ipv6"];
+    const auto ssl = object["ssl"];
+    const auto ssl_verify = object["sslVerify"];
+    const auto auto_rejoin = object["autoRejoin"];
+    const auto join_invite = object["joinInvite"];
 
-    if (sc == cfg.doc().end())
-        return;
-
-    ini::section::const_iterator it;
+    if (ipv6.is_boolean() && ipv6.get<bool>())
+        sv.set_flags(sv.flags() | server::ipv6);
+    if (ssl.is_boolean() && ssl.get<bool>())
+        sv.set_flags(sv.flags() | server::ssl);
+    if (ssl_verify.is_boolean() && ssl_verify.get<bool>())
+        sv.set_flags(sv.flags() | server::ssl_verify);
+    if (auto_rejoin.is_boolean() && auto_rejoin.get<bool>())
+        sv.set_flags(sv.flags() | server::auto_rejoin);
+    if (join_invite.is_boolean() && join_invite.get<bool>())
+        sv.set_flags(sv.flags() | server::join_invite);
 
-    if ((it = sc->find("username")) != sc->end())
-        server.set_username(it->value());
-    if ((it = sc->find("realname")) != sc->end())
-        server.set_realname(it->value());
-    if ((it = sc->find("nickname")) != sc->end())
-        server.set_nickname(it->value());
-    if ((it = sc->find("ctcp-version")) != sc->end())
-        server.set_ctcp_version(it->value());
+#if !defined(HAVE_SSL)
+    if (sv.flags() & server::ssl)
+        throw server_error(server_error::ssl_disabled);
+#endif
 }
 
 } // !namespace
 
 std::shared_ptr<server> from_json(boost::asio::io_service& service, const nlohmann::json& object)
 {
-    const auto id = require_json_id(object, "", server_error::invalid_identifier);
-    const auto sv = std::make_shared<server>(service, id);
-
-    // Mandatory fields.
-    sv->set_host(require_json_host(object, sv->name(), server_error::invalid_hostname));
+    // Mandatory parameters.
+    const auto id = json_util::get_string(object, "name");
+    const auto host = json_util::get_string(object, "host");
 
-    // Optional fields.
-    sv->set_port(optional_json_uint<uint16_t>(object, "/port"_json_pointer, sv->port(),
-        sv->name(), server_error::invalid_port));
-    sv->set_password(optional_json_string(object, "/password"_json_pointer, sv->password(),
-        sv->name(), server_error::invalid_password));
-    sv->set_nickname(optional_json_string(object, "/nickname"_json_pointer, sv->nickname(),
-        sv->name(), server_error::invalid_nickname));
-    sv->set_realname(optional_json_string(object, "/realname"_json_pointer, sv->realname(),
-        sv->name(), server_error::invalid_realname));
-    sv->set_username(optional_json_string(object, "/username"_json_pointer, sv->username(),
-        sv->name(), server_error::invalid_username));
-    sv->set_ctcp_version(optional_json_string(object, "/ctcpVersion"_json_pointer, sv->ctcp_version(),
-        sv->name(), server_error::invalid_ctcp_version));
-    sv->set_command_char(optional_json_string(object, "/commandChar"_json_pointer, sv->command_char(),
-        sv->name(), server_error::invalid_command_char));
+    if (!id || !string_util::is_identifier(*id))
+        throw server_error(server_error::invalid_identifier);
+    if (!host || host->empty())
+        throw server_error(server_error::invalid_hostname);
 
-    // Boolean does not throw options though.
-    if (optional_json_bool(object, "/ipv6"_json_pointer))
-        sv->set_flags(sv->flags() | server::ipv6);
-    if (optional_json_bool(object, "/sslVerify"_json_pointer))
-        sv->set_flags(sv->flags() | server::ssl_verify);
-    if (optional_json_bool(object, "/autoRejoin"_json_pointer))
-        sv->set_flags(sv->flags() | server::auto_rejoin);
-    if (optional_json_bool(object, "/joinInvite"_json_pointer))
-        sv->set_flags(sv->flags() | server::join_invite);
+    const auto sv = std::make_shared<server>(service, *id, *host);
 
-    if (optional_json_bool(object, "/ssl"_json_pointer))
-#if defined(HAVE_SSL)
-        sv->set_flags(sv->flags() | server::ssl);
-#else
-        throw server_error(sv->name(), server_error::ssl_disabled);
-#endif
+    from_json_load_options(*sv, object);
+    from_json_load_flags(*sv, object);
 
     return sv;
 }
@@ -212,94 +230,37 @@
                                     const config& cfg,
                                     const ini::section& sc)
 {
-    assert(sc.key() == "server");
-
-    const auto id = require_conf_id(sc, "", server_error::invalid_identifier);
-    const auto sv = std::make_shared<server>(service, id);
+    // Mandatory parameters.
+    const auto id = sc.get("name");
+    const auto host = sc.get("hostname");
 
-    // Mandatory fields.
-    sv->set_host(require_conf_host(sc, sv->name(), server_error::invalid_hostname));
+    if (!string_util::is_identifier(id.value()))
+        throw server_error(server_error::invalid_identifier);
+    if (host.value().empty())
+        throw server_error(server_error::invalid_hostname);
 
-    // Optional fields.
-    ini::section::const_iterator it;
+    const auto sv = std::make_shared<server>(service, id.value(), host.value());
 
-    if ((it = sc.find("password")) != sc.end())
-        sv->set_password(it->value());
+    from_config_load_channels(*sv, sc);
+    from_config_load_flags(*sv, sc);
+    from_config_load_numeric_parameters(*sv, sc);
+    from_config_load_options(*sv, sc);
 
-    // Optional flags
-    if ((it = sc.find("ipv6")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::ipv6);
+    // Identity is in a separate section
+    const auto identity = sc.get("identity");
 
-    if ((it = sc.find("ssl")) != sc.end() && string_util::is_boolean(it->value())) {
-#if defined(HAVE_SSL)
-        sv->set_flags(sv->flags() | server::ssl);
-#else
-        throw server_error(sv->name(), server_error::ssl_disabled);
-#endif
+    if (identity.value().size() > 0) {
+        const auto it = std::find_if(cfg.doc().begin(), cfg.doc().end(), [&] (const auto& i) {
+            return i.get("name").value() == identity.value();
+        });
+
+        if (it != cfg.doc().end())
+            from_config_load_identity(*sv, sc);
     }
 
-    if ((it = sc.find("ssl-verify")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::ssl_verify);
-
-    // Optional identity
-    if ((it = sc.find("identity")) != sc.end())
-        load_identity(*sv, cfg, it->value());
-
-    // Options
-    if ((it = sc.find("auto-rejoin")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::auto_rejoin);
-    if ((it = sc.find("join-invite")) != sc.end() && string_util::is_boolean(it->value()))
-        sv->set_flags(sv->flags() | server::join_invite);
-
-    // Channels
-    if ((it = sc.find("channels")) != sc.end()) {
-        for (const auto& s : *it) {
-            channel channel;
-
-            if (auto pos = s.find(":") != std::string::npos) {
-                channel.name = s.substr(0, pos);
-                channel.password = s.substr(pos + 1);
-            } else
-                channel.name = s;
-
-            sv->join(channel.name, channel.password);
-        }
-    }
-    if ((it = sc.find("command-char")) != sc.end())
-        sv->set_command_char(it->value());
-
-    // Reconnect and ping timeout
-    if ((it = sc.find("port")) != sc.end())
-        sv->set_port(optional_conf_uint<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_port));
-
-    if ((it = sc.find("reconnect-tries")) != sc.end())
-        sv->set_reconnect_tries(optional_conf_int<std::int8_t>(it->value(),
-            sv->name(), server_error::invalid_reconnect_tries));
-
-    if ((it = sc.find("reconnect-timeout")) != sc.end())
-        sv->set_reconnect_delay(optional_conf_uint<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_reconnect_timeout));
-
-    if ((it = sc.find("ping-timeout")) != sc.end())
-        sv->set_ping_timeout(optional_conf_uint<std::uint16_t>(it->value(),
-            sv->name(), server_error::invalid_ping_timeout));
-
     return sv;
 }
 
-std::string get_identifier(const nlohmann::json& json)
-{
-    const auto v = json_util::get_string(json, "/server"_json_pointer);
-
-    if (!v)
-        throw server_error("", server_error::invalid_identifier);
-    if (!string_util::is_identifier(*v))
-        throw server_error(*v, server_error::invalid_identifier);
-
-    return *v;
-}
-
 } // !server_util
 
 } // !irccd
--- a/libirccd/irccd/daemon/server_util.hpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/server_util.hpp	Fri Mar 23 14:00:03 2018 +0100
@@ -71,16 +71,6 @@
                                     const config& cfg,
                                     const ini::section& sc);
 
-/**
- * Get a server identifier from the JSON object.
- *
- * This searches for the `server` property.
- *
- * \param json the JSON object
- * \throw server_error on errors
- */
-std::string get_identifier(const nlohmann::json& json);
-
 } // !server_util
 
 } // !irccd
--- a/libirccd/irccd/daemon/service/server_service.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/service/server_service.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -472,12 +472,12 @@
 std::shared_ptr<server> server_service::require(const std::string& name) const
 {
     if (!string_util::is_identifier(name))
-        throw server_error(name, server_error::invalid_identifier);
+        throw server_error(server_error::invalid_identifier);
 
     const auto s = get(name);
 
     if (!s)
-        throw server_error(name, server_error::not_found);
+        throw server_error(server_error::not_found);
 
     return s;
 }
--- a/libirccd/irccd/daemon/transport_util.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/libirccd/irccd/daemon/transport_util.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -55,6 +55,9 @@
 
     auto port = string_util::to_uint<std::uint16_t>(it->value());
 
+    if (!port)
+        throw std::invalid_argument("invalid port number");
+
     // Address.
     std::string address = "*";
 
@@ -108,8 +111,8 @@
     }
 
     auto endpoint = (address == "*")
-        ? boost::asio::ip::tcp::endpoint(protocol, port)
-        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), port);
+        ? boost::asio::ip::tcp::endpoint(protocol, *port)
+        : boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(address), *port);
 
     boost::asio::ip::tcp::acceptor acceptor(service, endpoint, true);
 
--- a/tests/src/libirccd/util/main.cpp	Wed Mar 21 19:45:55 2018 +0100
+++ b/tests/src/libirccd/util/main.cpp	Fri Mar 23 14:00:03 2018 +0100
@@ -416,63 +416,6 @@
 BOOST_AUTO_TEST_SUITE_END()
 
 /*
- * string_util::to_int function
- * ------------------------------------------------------------------
- */
-
-BOOST_AUTO_TEST_SUITE(to_int)
-
-BOOST_AUTO_TEST_CASE(signed_to_int)
-{
-    BOOST_TEST(string_util::to_int("10")                     == 10);
-    BOOST_TEST(string_util::to_int<std::int8_t>("-10")       == -10);
-    BOOST_TEST(string_util::to_int<std::int8_t>("10")        == 10);
-    BOOST_TEST(string_util::to_int<std::int16_t>("-1000")    == -1000);
-    BOOST_TEST(string_util::to_int<std::int16_t>("1000")     == 1000);
-    BOOST_TEST(string_util::to_int<std::int32_t>("-1000")    == -1000);
-    BOOST_TEST(string_util::to_int<std::int32_t>("1000")     == 1000);
-}
-
-BOOST_AUTO_TEST_CASE(signed_to_int64)
-{
-    BOOST_TEST(string_util::to_int<std::int64_t>("-9223372036854775807") == -9223372036854775807LL);
-    BOOST_TEST(string_util::to_int<std::int64_t>("9223372036854775807") == 9223372036854775807LL);
-}
-
-BOOST_AUTO_TEST_CASE(unsigned_to_uint)
-{
-    BOOST_TEST(string_util::to_uint("10")                    == 10U);
-    BOOST_TEST(string_util::to_uint<std::uint8_t>("10")       == 10U);
-    BOOST_TEST(string_util::to_uint<std::uint16_t>("1000")    == 1000U);
-    BOOST_TEST(string_util::to_uint<std::uint32_t>("1000")    == 1000U);
-}
-
-BOOST_AUTO_TEST_CASE(unsigned_to_uint64)
-{
-    BOOST_TEST(string_util::to_uint<std::uint64_t>("18446744073709551615") == 18446744073709551615ULL);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-BOOST_AUTO_TEST_SUITE(errors)
-
-BOOST_AUTO_TEST_CASE(invalid_argument)
-{
-    BOOST_REQUIRE_THROW(string_util::to_int("plopation"), std::invalid_argument);
-    BOOST_REQUIRE_THROW(string_util::to_uint("plopation"), std::invalid_argument);
-}
-
-BOOST_AUTO_TEST_CASE(out_of_range)
-{
-    BOOST_REQUIRE_THROW(string_util::to_int<std::int8_t>("1000"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_int<std::int8_t>("-1000"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_uint<std::uint8_t>("1000"), std::out_of_range);
-    BOOST_REQUIRE_THROW(string_util::to_uint<std::uint8_t>("-1000"), std::out_of_range);
-}
-
-BOOST_AUTO_TEST_SUITE_END()
-
-/*
  * fs_util::find function (name)
  * ------------------------------------------------------------------
  */