changeset 224:01fc0e526a9d

Irccd: cleanup server stuff, Closes T1
author David Demelier <markand@malikania.fr>
date Thu, 14 Jul 2016 10:47:20 +0200
parents 2dc0355c3417
children 277d92d29a3d
files extern/json/json.hpp lib/irccd/cmd-server-connect.cpp lib/irccd/cmd-server-info.cpp lib/irccd/config.cpp lib/irccd/mod-server.cpp lib/irccd/server-state-connected.cpp lib/irccd/server-state-connected.hpp lib/irccd/server-state-connecting.cpp lib/irccd/server-state-connecting.hpp lib/irccd/server-state-disconnected.cpp lib/irccd/server-state-disconnected.hpp lib/irccd/server-state.hpp lib/irccd/server.cpp lib/irccd/server.hpp lib/irccd/service-server.cpp lib/irccd/util.hpp
diffstat 16 files changed, 595 insertions(+), 458 deletions(-) [+]
line wrap: on
line diff
--- a/extern/json/json.hpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/extern/json/json.hpp	Thu Jul 14 10:47:20 2016 +0200
@@ -5788,7 +5788,6 @@
     /// @}
 
 
-  private:
     ///////////////////////////
     // convenience functions //
     ///////////////////////////
@@ -5815,6 +5814,7 @@
         }
     }
 
+private:
     /*!
     @brief calculates the extra space to escape a JSON string
 
--- a/lib/irccd/cmd-server-connect.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/cmd-server-connect.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -34,91 +34,6 @@
 
 namespace command {
 
-namespace {
-
-std::string readInfoName(const json &object)
-{
-    std::string name = object["name"];
-
-    if (!util::isIdentifierValid(name))
-        throw PropertyError("name", "invalid identifier");
-
-    return name;
-}
-
-std::string readInfoHost(const json &object)
-{
-    std::string host = object["host"];
-
-    if (host.empty())
-        throw PropertyError("host", "empty hostname");
-
-    return host;
-}
-
-std::uint16_t readInfoPort(const json &object)
-{
-    json::const_iterator it = object.find("port");
-
-    if (it == object.end())
-        return 6667;
-
-    if (!it->is_number())
-        throw InvalidPropertyError("port", json::value_t::number_unsigned, it->type());
-    if (!util::isBound(it->get<int>(), 0, UINT16_MAX))
-        throw PropertyRangeError("port", 0, UINT16_MAX, it->get<int>());
-
-    return static_cast<std::uint16_t>(it->get<int>());
-}
-
-ServerInfo readInfo(const json &object)
-{
-    ServerInfo info;
-
-    info.host = readInfoHost(object);
-    info.port = readInfoPort(object);
-
-    json::const_iterator it;
-
-    if ((it = object.find("ssl")) != object.end() && it->is_boolean() && *it)
-        info.flags |= ServerInfo::Ssl;
-    if ((it = object.find("sslVerify")) != object.end() && it->is_boolean() && *it)
-        info.flags |= ServerInfo::SslVerify;
-
-    return info;
-}
-
-void readIdentity(Server &server, const json &object)
-{
-    json::const_iterator it;
-
-    if ((it = object.find("nickname")) != object.end() && it->is_string())
-        server.setNickname(*it);
-    if ((it = object.find("realname")) != object.end() && it->is_string())
-        server.setRealname(*it);
-    if ((it = object.find("username")) != object.end() && it->is_string())
-        server.setUsername(*it);
-    if ((it = object.find("ctcpVersion")) != object.end() && it->is_string())
-        server.setCtcpVersion(*it);
-}
-
-ServerSettings readSettings(const json &object)
-{
-    ServerSettings settings;
-    json::const_iterator it;
-
-    if ((it = object.find("commandChar")) != object.end() && it->is_string())
-        settings.command = *it;
-    if ((it = object.find("reconnectTries")) != object.end() && it->is_number_integer())
-        settings.reconnectTries = *it;
-    if ((it = object.find("reconnectTimeout")) != object.end() && it->is_number_integer())
-        settings.reconnectDelay = *it;
-
-    return settings;
-}
-
-} // !namespace
-
 ServerConnect::ServerConnect()
     : Command("server-connect", "Server")
 {
@@ -160,9 +75,7 @@
 
 json ServerConnect::exec(Irccd &irccd, const json &request) const
 {
-    auto server = std::make_shared<Server>(readInfoName(request), readInfo(request), readSettings(request));
-
-    readIdentity(*server, request);
+    auto server = Server::fromJson(request);
 
     if (irccd.serverService().has(server->name()))
         throw std::invalid_argument("server '{}' already exists"_format(server->name()));
--- a/lib/irccd/cmd-server-info.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/cmd-server-info.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -63,24 +63,24 @@
 
     // General stuff.
     response.push_back({"name", server->name()});
-    response.push_back({"host", server->info().host});
-    response.push_back({"port", server->info().port});
+    response.push_back({"host", server->host()});
+    response.push_back({"port", server->port()});
     response.push_back({"nickname", server->nickname()});
     response.push_back({"username", server->username()});
     response.push_back({"realname", server->realname()});
 
     // Optional stuff.
-    if (server->info().flags & irccd::ServerInfo::Ipv6)
+    if (server->flags() & Server::Ipv6)
         response.push_back({"ipv6", true});
-    if (server->info().flags & irccd::ServerInfo::Ssl)
+    if (server->flags() & Server::Ssl)
         response.push_back({"ssl", true});
-    if (server->info().flags & irccd::ServerInfo::SslVerify)
+    if (server->flags() & Server::SslVerify)
         response.push_back({"sslVerify", true});
 
     // Channel list.
     auto channels = nlohmann::json::array();
 
-    for (const auto &c : server->settings().channels)
+    for (const auto &c : server->channels())
         channels.push_back(c.name);
 
     response.push_back({"channels", std::move(channels)});
--- a/lib/irccd/config.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/config.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -270,10 +270,6 @@
 {
     assert(sc.key() == "server");
 
-    std::string name;
-    ServerInfo info;
-    ServerSettings settings;
-
     // Name.
     ini::Section::const_iterator it;
 
@@ -282,49 +278,40 @@
     else if (!util::isIdentifierValid(it->value()))
         throw std::invalid_argument("server: invalid identifier: {}"_format(it->value()));
 
-    name = it->value();
+    auto server = std::make_shared<Server>(it->value());
 
     // Host
     if ((it = sc.find("host")) == sc.end())
-        throw std::invalid_argument("server {}: missing host"_format(name));
-
-    info.host = it->value();
+        throw std::invalid_argument("server {}: missing host"_format(server->name()));
 
-    // Optional port
-    if ((it = sc.find("port")) != sc.end()) {
-        try {
-            info.port = util::toNumber<std::uint16_t>(it->value());
-        } catch (const std::exception &) {
-            throw std::invalid_argument("server {}: invalid number for {}: {}"_format(name, it->key(), it->value()));
-        }
-    }
+    server->setHost(it->value());
 
     // Optional password
     if ((it = sc.find("password")) != sc.end())
-        info.password = it->value();
+        server->setPassword(it->value());
 
     // Optional flags
     if ((it = sc.find("ipv6")) != sc.end() && util::isBoolean(it->value()))
-        info.flags |= ServerInfo::Ipv6;
-    if ((it = sc.find("ssl")) != sc.end()) {
-        if (util::isBoolean(it->value()))
-            info.flags |= ServerInfo::Ssl;
-    }
-    if ((it = sc.find("ssl-verify")) != sc.end()) {
-        if (util::isBoolean(it->value()))
-            info.flags |= ServerInfo::SslVerify;
-    }
+        server->setFlags(server->flags() | Server::Ipv6);
+    if ((it = sc.find("ssl")) != sc.end() && util::isBoolean(it->value()))
+        server->setFlags(server->flags() | Server::Ssl);
+    if ((it = sc.find("ssl-verify")) != sc.end() && util::isBoolean(it->value()))
+        server->setFlags(server->flags() | Server::SslVerify);
+
+    // Optional identity
+    if ((it = sc.find("identity")) != sc.end())
+        config.loadServerIdentity(*server, it->value());
 
     // Options
     if ((it = sc.find("auto-rejoin")) != sc.end() && util::isBoolean(it->value()))
-        settings.flags |= ServerSettings::AutoRejoin;
+        server->setFlags(server->flags() | Server::AutoRejoin);
     if ((it = sc.find("join-invite")) != sc.end() && util::isBoolean(it->value()))
-        settings.flags |= ServerSettings::JoinInvite;
+        server->setFlags(server->flags() | Server::JoinInvite);
 
     // Channels
     if ((it = sc.find("channels")) != sc.end()) {
         for (const std::string &s : *it) {
-            ServerChannel channel;
+            Channel channel;
 
             if (auto pos = s.find(":") != std::string::npos) {
                 channel.name = s.substr(0, pos);
@@ -332,30 +319,28 @@
             } else
                 channel.name = s;
 
-            settings.channels.push_back(std::move(channel));
+            //server.channels.push_back(std::move(channel));
+            //server->join()
+            server->join(channel.name, channel.password);
         }
     }
     if ((it = sc.find("command-char")) != sc.end())
-        settings.command = it->value();
+        server->setCommandCharacter(it->value());
 
     // Reconnect and ping timeout
     try {
+        if ((it = sc.find("port")) != sc.end())
+            server->setPort(util::toNumber<std::uint16_t>(it->value()));
         if ((it = sc.find("reconnect-tries")) != sc.end())
-            settings.reconnectTries = util::toNumber<std::int8_t>(it->value());
+            server->setReconnectTries(util::toNumber<std::int8_t>(it->value()));
         if ((it = sc.find("reconnect-timeout")) != sc.end())
-            settings.reconnectDelay = util::toNumber<std::uint16_t>(it->value());
+            server->setReconnectDelay(util::toNumber<std::uint16_t>(it->value()));
         if ((it = sc.find("ping-timeout")) != sc.end())
-            settings.pingTimeout = util::toNumber<std::uint16_t>(it->value());
+            server->setPingTimeout(util::toNumber<std::uint16_t>(it->value()));
     } catch (const std::exception &) {
-        log::warning("server {}: invalid number for {}: {}"_format(name, it->key(), it->value()));
+        log::warning("server {}: invalid number for {}: {}"_format(server->name(), it->key(), it->value()));
     }
 
-    auto server = std::make_shared<Server>(std::move(name), std::move(info), std::move(settings));
-
-    // Optional identity
-    if ((it = sc.find("identity")) != sc.end())
-        config.loadServerIdentity(*server, it->value());
-
     return server;
 }
 
--- a/lib/irccd/mod-server.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/mod-server.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -49,109 +49,6 @@
     return *static_cast<std::shared_ptr<Server> *>(ptr);
 }
 
-std::string readName(duk_context *ctx)
-{
-    duk_get_prop_string(ctx, 0, "name");
-    auto name = dukx_get_std_string(ctx, -1);
-    duk_pop(ctx);
-
-    if (!util::isIdentifierValid(name))
-        duk_error(ctx, DUK_ERR_ERROR, "invalid 'name' property");
-
-    return name;
-}
-
-ServerInfo readInfo(duk_context *ctx)
-{
-    ServerInfo info;
-
-    // 'host' property.
-    duk_get_prop_string(ctx, 0, "host");
-    info.host = duk_is_string(ctx, -1) ? dukx_get_std_string(ctx, -1) : info.host;
-    duk_pop(ctx);
-
-    // 'port' property.
-    duk_get_prop_string(ctx, 0, "port");
-    info.port = duk_is_number(ctx, -1) ? duk_get_int(ctx, -1) : info.port;
-    duk_pop(ctx);
-
-    // 'password' property.
-    duk_get_prop_string(ctx, 0, "password");
-    info.password = duk_is_string(ctx, -1) ? dukx_get_std_string(ctx, -1) : info.password;
-    duk_pop(ctx);
-
-    // 'ipv6' property.
-    duk_get_prop_string(ctx, 0, "ipv6");
-    if (duk_get_boolean(ctx, -1))
-        info.flags |= ServerInfo::Ipv6;
-    duk_pop(ctx);
-
-    return info;
-}
-
-void readIdentity(Server &server, duk_context *ctx)
-{
-    // 'nickname' property.
-    duk_get_prop_string(ctx, 0, "nickname");
-    if (duk_is_string(ctx, -1))
-        server.setNickname(dukx_get_std_string(ctx, -1));
-    duk_pop(ctx);
-
-    // 'username' property.
-    duk_get_prop_string(ctx, 0, "username");
-    if (duk_is_string(ctx, -1))
-        server.setUsername(dukx_get_std_string(ctx, -1));
-    duk_pop(ctx);
-
-    // 'realname' property.
-    duk_get_prop_string(ctx, 0, "realname");
-    if (duk_is_string(ctx, -1))
-        server.setRealname(dukx_get_std_string(ctx, -1));
-    duk_pop(ctx);
-
-    // 'ctcpversion' property.
-    duk_get_prop_string(ctx, 0, "version");
-    if (duk_is_string(ctx, -1))
-        server.setCtcpVersion(dukx_get_std_string(ctx, -1));
-    duk_pop(ctx);
-}
-
-ServerSettings readSettings(duk_context *ctx)
-{
-    ServerSettings settings;
-
-    // 'channels' property.
-    duk_get_prop_string(ctx, 0, "channels");
-    settings.channels = dukx_get_array(ctx, -1, [] (auto ctx, auto) {
-        return Server::splitChannel(dukx_get_std_string(ctx, -1));
-    });
-    duk_pop(ctx);
-
-    // 'recoTries' property.
-    duk_get_prop_string(ctx, 0, "recoTries");
-    settings.reconnectTries = duk_is_number(ctx, -1) ? duk_get_int(ctx, -1) : settings.reconnectTries;
-    duk_pop(ctx);
-
-    // 'recoTimeout' property.
-    duk_get_prop_string(ctx, 0, "recoTimeout");
-    settings.reconnectDelay = duk_is_number(ctx, -1) ? duk_get_int(ctx, -1) : settings.reconnectDelay;
-    duk_pop(ctx);
-
-    // 'joinInvite' property.
-    duk_get_prop_string(ctx, 0, "joinInvite");
-    if (duk_get_boolean(ctx, -1))
-        settings.flags |= ServerSettings::JoinInvite;
-    duk_pop(ctx);
-
-    // 'autoRejoin' property.
-    duk_get_prop_string(ctx, 0, "autoRejoin");
-    if (duk_get_boolean(ctx, -1))
-        settings.flags |= ServerSettings::AutoRejoin;
-    duk_pop(ctx);
-
-    return settings;
-}
-
 /*
  * Method: Server.cmode(channel, mode)
  * ------------------------------------------------------------------
@@ -206,15 +103,15 @@
     duk_push_object(ctx);
     dukx_push_std_string(ctx, server->name());
     duk_put_prop_string(ctx, -2, "name");
-    dukx_push_std_string(ctx, server->info().host);
+    dukx_push_std_string(ctx, server->host());
     duk_put_prop_string(ctx, -2, "host");
-    duk_push_int(ctx, server->info().port);
+    duk_push_int(ctx, server->port());
     duk_put_prop_string(ctx, -2, "port");
-    duk_push_boolean(ctx, server->info().flags & ServerInfo::Ssl);
+    duk_push_boolean(ctx, server->flags() & Server::Ssl);
     duk_put_prop_string(ctx, -2, "ssl");
-    duk_push_boolean(ctx, server->info().flags & ServerInfo::SslVerify);
+    duk_push_boolean(ctx, server->flags() & Server::SslVerify);
     duk_put_prop_string(ctx, -2, "sslVerify");
-    dukx_push_std_string(ctx, server->settings().command);
+    dukx_push_std_string(ctx, server->commandCharacter());
     duk_put_prop_string(ctx, -2, "commandChar");
     dukx_push_std_string(ctx, server->realname());
     duk_put_prop_string(ctx, -2, "realname");
@@ -222,7 +119,7 @@
     duk_put_prop_string(ctx, -2, "nickname");
     dukx_push_std_string(ctx, server->username());
     duk_put_prop_string(ctx, -2, "username");
-    dukx_push_array(ctx, server->settings().channels, [] (auto ctx, auto channel) {
+    dukx_push_array(ctx, server->channels(), [] (auto ctx, auto channel) {
         dukx_push_std_string(ctx, channel.name);
     });
     duk_put_prop_string(ctx, -2, "channels");
@@ -490,10 +387,11 @@
     if (!duk_is_constructor_call(ctx))
         return 0;
 
+    duk_check_type(ctx, 0, DUK_TYPE_OBJECT);
+
     try {
-        auto s = std::make_shared<Server>(readName(ctx), readInfo(ctx), readSettings(ctx));
-
-        readIdentity(*s, ctx);
+        auto json = duk_json_encode(ctx, 0);
+        auto s = Server::fromJson(nlohmann::json::parse(json));
 
         duk_push_this(ctx);
         duk_push_pointer(ctx, new std::shared_ptr<Server>(std::move(s)));
--- a/lib/irccd/server-state-connected.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server-state-connected.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -27,33 +27,26 @@
 
 namespace irccd {
 
-namespace state {
-
-void Connected::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd)
+void ConnectedState::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd)
 {
-    const ServerSettings &settings = server.settings();
-
     if (!irc_is_connected(server.session())) {
         log::warning() << "server " << server.name() << ": disconnected" << std::endl;
 
-        if (settings.reconnectDelay > 0)
-            log::warning("server {}: retrying in {} seconds"_format(server.name(), settings.reconnectDelay));
+        if (server.reconnectDelay() > 0)
+            log::warning("server {}: retrying in {} seconds"_format(server.name(), server.reconnectDelay()));
 
-        server.next(std::make_unique<state::Disconnected>());
-    } else if (server.cache().pingTimer.elapsed() >= settings.pingTimeout * 1000) {
+        server.next(std::make_unique<DisconnectedState>());
+    } else if (server.cache().pingTimer.elapsed() >= server.pingTimeout() * 1000) {
         log::warning() << "server " << server.name() << ": ping timeout after "
                    << (server.cache().pingTimer.elapsed() / 1000) << " seconds" << std::endl;
-        server.next(std::make_unique<state::Disconnected>());
-    } else {
+        server.next(std::make_unique<DisconnectedState>());
+    } else
         irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd));
-    }
 }
 
-std::string Connected::ident() const
+std::string ConnectedState::ident() const
 {
     return "Connected";
 }
 
-} // !state
-
 } // !irccd
--- a/lib/irccd/server-state-connected.hpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server-state-connected.hpp	Thu Jul 14 10:47:20 2016 +0200
@@ -28,13 +28,11 @@
 
 namespace irccd {
 
-namespace state {
-
 /**
  * \brief Connected state.
  * \ingroup states
  */
-class Connected : public ServerState {
+class ConnectedState : public State {
 public:
     /**
      * \copydoc ServerState::prepare
@@ -47,8 +45,6 @@
     IRCCD_EXPORT std::string ident() const override;
 };
 
-} // !state
-
 } // !irccd
 
 #endif // !IRCCD_SERVER_STATE_CONNECTED_HPP
--- a/lib/irccd/server-state-connecting.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server-state-connecting.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -36,32 +36,29 @@
 
 namespace irccd {
 
-namespace state {
-
 namespace {
 
 bool connect(Server &server)
 {
-    const ServerInfo &info = server.info();
-    const char *password = info.password.empty() ? nullptr : info.password.c_str();
-    std::string host = info.host;
+    const char *password = server.password().empty() ? nullptr : server.password().c_str();
+    std::string host = server.host();
     int code;
 
     // libircclient requires # for SSL connection.
 #if defined(WITH_SSL)
-    if (info.flags & ServerInfo::Ssl)
+    if (server.flags() & Server::Ssl)
         host.insert(0, 1, '#');
-    if (!(info.flags & ServerInfo::SslVerify))
+    if (!(server.flags() & Server::SslVerify))
         irc_option_set(server.session(), LIBIRC_OPTION_SSL_NO_VERIFY);
 #endif
 
-    if (info.flags & ServerInfo::Ipv6) {
-        code = irc_connect6(server.session(), host.c_str(), info.port, password,
+    if (server.flags() & Server::Ipv6) {
+        code = irc_connect6(server.session(), host.c_str(), server.port(), password,
                             server.nickname().c_str(),
                             server.username().c_str(),
                             server.realname().c_str());
     } else {
-        code = irc_connect(server.session(), host.c_str(), info.port, password,
+        code = irc_connect(server.session(), host.c_str(), server.port(), password,
                            server.nickname().c_str(),
                            server.username().c_str(),
                            server.realname().c_str());
@@ -72,7 +69,7 @@
 
 } // !namespace
 
-void Connecting::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd)
+void ConnectingState::prepare(Server &server, fd_set &setinput, fd_set &setoutput, net::Handle &maxfd)
 {
     /*
      * The connect function will either fail if the hostname wasn't resolved or if any of the internal functions
@@ -85,22 +82,18 @@
      *
      * Otherwise, the libircclient event_connect will change the state.
      */
-    const ServerInfo &info = server.info();
-
     if (m_started) {
-        const ServerSettings &settings = server.settings();
-
-        if (m_timer.elapsed() > static_cast<unsigned>(settings.reconnectDelay * 1000)) {
+        if (m_timer.elapsed() > static_cast<unsigned>(server.reconnectDelay() * 1000)) {
             log::warning() << "server " << server.name() << ": timeout while connecting" << std::endl;
-            server.next(std::make_unique<state::Disconnected>());
+            server.next(std::make_unique<DisconnectedState>());
         } else if (!irc_is_connected(server.session())) {
             log::warning() << "server " << server.name() << ": error while connecting: ";
             log::warning() << irc_strerror(irc_errno(server.session())) << std::endl;
 
-            if (settings.reconnectTries != 0)
-                log::warning("server {}: retrying in {} seconds"_format(server.name(), settings.reconnectDelay));
+            if (server.reconnectTries() != 0)
+                log::warning("server {}: retrying in {} seconds"_format(server.name(), server.reconnectDelay()));
 
-            server.next(std::make_unique<state::Disconnected>());
+            server.next(std::make_unique<DisconnectedState>());
         } else
             irc_add_select_descriptors(server.session(), &setinput, &setoutput, reinterpret_cast<int *>(&maxfd));
     } else {
@@ -112,12 +105,12 @@
 #if !defined(IRCCD_SYSTEM_WINDOWS)
         (void)res_init();
 #endif
-        log::info("server {}: trying to connect to {}, port {}"_format(server.name(), info.host, info.port));
+        log::info("server {}: trying to connect to {}, port {}"_format(server.name(), server.host(), server.port()));
 
         if (!connect(server)) {
             log::warning() << "server " << server.name() << ": disconnected while connecting: ";
             log::warning() << irc_strerror(irc_errno(server.session())) << std::endl;
-            server.next(std::make_unique<state::Disconnected>());
+            server.next(std::make_unique<DisconnectedState>());
         } else {
             m_started = true;
 
@@ -127,11 +120,9 @@
     }
 }
 
-std::string Connecting::ident() const
+std::string ConnectingState::ident() const
 {
     return "Connecting";
 }
 
-} // !state
-
 } // !irccd
--- a/lib/irccd/server-state-connecting.hpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server-state-connecting.hpp	Thu Jul 14 10:47:20 2016 +0200
@@ -29,13 +29,11 @@
 
 namespace irccd {
 
-namespace state {
-
 /**
  * \brief Connecting state.
  * \ingroup states
  */
-class Connecting : public ServerState {
+class ConnectingState : public State {
 private:
     bool m_started{false};
     ElapsedTimer m_timer;
@@ -52,8 +50,6 @@
     IRCCD_EXPORT std::string ident() const override;
 };
 
-} // !state
-
 } // !irccd
 
 #endif // !IRCCD_SERVER_STATE_CONNECTING_HPP
--- a/lib/irccd/server-state-disconnected.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server-state-disconnected.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -23,34 +23,29 @@
 
 namespace irccd {
 
-namespace state {
-
-void Disconnected::prepare(Server &server, fd_set &, fd_set &, net::Handle &)
+void DisconnectedState::prepare(Server &server, fd_set &, fd_set &, net::Handle &)
 {
-    ServerSettings &settings = server.settings();
-    ServerCache &cache = server.cache();
+    auto &cache = server.cache();
 
-    if (settings.reconnectTries == 0) {
+    if (server.reconnectTries() == 0) {
         log::warning() << "server " << server.name() << ": reconnection disabled, skipping" << std::endl;
         server.onDie();
-    } else if (settings.reconnectTries > 0 && cache.reconnectCurrent > settings.reconnectTries) {
+    } else if (server.reconnectTries() > 0 && cache.reconnectCurrent > server.reconnectTries()) {
         log::warning() << "server " << server.name() << ": giving up" << std::endl;
         server.onDie();
     } else {
-        if (m_timer.elapsed() > static_cast<unsigned>(settings.reconnectDelay * 1000)) {
+        if (m_timer.elapsed() > static_cast<unsigned>(server.reconnectDelay() * 1000)) {
             irc_disconnect(server.session());
 
             server.cache().reconnectCurrent ++;
-            server.next(std::make_unique<state::Connecting>());
+            server.next(std::make_unique<ConnectingState>());
         }
     }
 }
 
-std::string Disconnected::ident() const
+std::string DisconnectedState::ident() const
 {
     return "Disconnected";
 }
 
-} // !state
-
 } // !irccd
--- a/lib/irccd/server-state-disconnected.hpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server-state-disconnected.hpp	Thu Jul 14 10:47:20 2016 +0200
@@ -29,13 +29,11 @@
 
 namespace irccd {
 
-namespace state {
-
 /**
  * \brief Disconnected state.
  * \ingroup states
  */
-class Disconnected : public ServerState {
+class DisconnectedState : public State {
 private:
     ElapsedTimer m_timer;
 
@@ -51,8 +49,6 @@
     IRCCD_EXPORT std::string ident() const override;
 };
 
-} // !state
-
 } // !irccd
 
 #endif // !IRCCD_SERVER_STATE_DISCONNECTED_HPP
--- a/lib/irccd/server-state.hpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server-state.hpp	Thu Jul 14 10:47:20 2016 +0200
@@ -38,26 +38,20 @@
 class Server;
 
 /**
- * \brief Namespace for server states.
- */
-namespace state {
-}
-
-/**
  * \class ServerState
  * \brief Server current state.
  */
-class ServerState {
+class State {
 public:
     /**
      * Default constructor.
      */
-    ServerState() = default;
+    State() = default;
 
     /**
      * Virtual default destructor.
      */
-    virtual ~ServerState() = default;
+    virtual ~State() = default;
 
     /**
      * Prepare the state.
--- a/lib/irccd/server.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -55,12 +55,12 @@
  * Remove the user prefix only if it is present in the mode table, for example removes @ from @irccd if and only if
  * @ is a character mode (e.g. operator).
  */
-std::string cleanPrefix(const ServerInfo &info, std::string nickname)
+std::string cleanPrefix(const std::map<ChannelMode, char> &modes, std::string nickname)
 {
     if (nickname.length() == 0)
         return nickname;
 
-    for (const auto &pair : info.modes)
+    for (const auto &pair : modes)
         if (nickname[0] == pair.second)
             nickname.erase(0, 1);
 
@@ -73,11 +73,11 @@
  *
  * Read modes from the IRC event numeric.
  */
-std::map<ServerChanMode, char> extractPrefixes(const std::string &line)
+std::map<ChannelMode, char> extractPrefixes(const std::string &line)
 {
     std::pair<char, char> table[16];
     std::string buf = line.substr(7);
-    std::map<ServerChanMode, char> modes;
+    std::map<ChannelMode, char> modes;
 
     for (int i = 0; i < 16; ++i)
         table[i] = std::make_pair(-1, -1);
@@ -101,7 +101,7 @@
 
     // Put these as a map of mode to prefix.
     for (int i = 0; i < 16; ++i) {
-        auto key = static_cast<ServerChanMode>(table[i].first);
+        auto key = static_cast<ChannelMode>(table[i].first);
         auto value = table[i].second;
 
         modes.emplace(key, value);
@@ -121,11 +121,11 @@
     m_cache.pingTimer.reset();
 
     // Don't forget to change state and notify.
-    next(std::make_unique<state::Connected>());
+    next(std::make_unique<ConnectedState>());
     onConnect(ConnectEvent{shared_from_this()});
 
     // Auto join listed channels.
-    for (const ServerChannel &channel : m_settings.channels) {
+    for (const Channel &channel : m_channels) {
         log::info() << "server " << m_name << ": auto joining " << channel.name << std::endl;
         join(channel.name, channel.password);
     }
@@ -154,7 +154,7 @@
 void Server::handleInvite(const char *orig, const char **params) noexcept
 {
     // If joininvite is set, join the channel.
-    if ((m_settings.flags & ServerSettings::JoinInvite) && isSelf(strify(params[0])))
+    if ((m_flags & JoinInvite) && isSelf(strify(params[0])))
         join(strify(params[1]));
 
     /*
@@ -173,7 +173,7 @@
 void Server::handleKick(const char *orig, const char **params) noexcept
 {
     // Rejoin the channel if the option has been set and I was kicked.
-    if ((m_settings.flags & ServerSettings::AutoRejoin) && isSelf(strify(params[1])))
+    if ((m_flags & AutoRejoin) && isSelf(strify(params[1])))
         join(strify(params[0]));
 
     onKick(KickEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1]), strify(params[2])});
@@ -219,7 +219,7 @@
 
         // The listing may add some prefixes, remove them if needed.
         for (std::string u : users)
-            m_cache.namesMap[params[2]].insert(cleanPrefix(m_info, u));
+            m_cache.namesMap[params[2]].insert(cleanPrefix(m_modes, u));
     } else if (event == LIBIRC_RFC_RPL_ENDOFNAMES) {
         /*
          * Called when end of name listing has finished on a channel.
@@ -252,7 +252,7 @@
         if (c < 6 || !params[1] || !params[2] || !params[3] || !params[5])
             return;
 
-        ServerWhois info;
+        Whois info;
 
         info.nick = strify(params[1]);
         info.user = strify(params[2]);
@@ -277,7 +277,7 @@
 
             // Clean their prefixes.
             for (auto &s : channels)
-                s = cleanPrefix(m_info, s);
+                s = cleanPrefix(m_modes, s);
 
             it->second.channels = std::move(channels);
         }
@@ -302,7 +302,7 @@
          */
         for (unsigned int i = 0; i < c; ++i) {
             if (strncmp(params[i], "PREFIX", 6) == 0) {
-                m_info.modes = extractPrefixes(params[i]);
+                m_modes = extractPrefixes(params[i]);
                 break;
             }
         }
@@ -333,22 +333,48 @@
     onTopic(TopicEvent{shared_from_this(), strify(orig), strify(params[0]), strify(params[1])});
 }
 
-ServerChannel Server::splitChannel(const std::string &value)
+std::shared_ptr<Server> Server::fromJson(const nlohmann::json &object)
+{
+    auto server = std::make_shared<Server>(util::json::requireIdentifier(object, "name"));
+
+    server->setHost(util::json::requireString(object, "host"));
+    server->setPassword(util::json::getString(object, "password"));
+    server->setNickname(util::json::getString(object, "nickname", server->nickname()));
+    server->setRealname(util::json::getString(object, "realname", server->realname()));
+    server->setUsername(util::json::getString(object, "username", server->username()));
+    server->setCtcpVersion(util::json::getString(object, "ctcpVersion", server->ctcpVersion()));
+    server->setCommandCharacter(util::json::getString(object, "commandChar", server->commandCharacter()));
+
+    if (object.find("port") != object.end())
+        server->setPort(util::json::getUintRange<std::uint16_t>(object, "port"));
+    if (util::json::getBool(object, "ipv6"))
+        server->setFlags(server->flags() | Server::Ipv6);
+    if (util::json::getBool(object, "ssl"))
+        server->setFlags(server->flags() | Server::Ssl);
+    if (util::json::getBool(object, "sslVerify"))
+        server->setFlags(server->flags() | Server::SslVerify);
+    if (util::json::getBool(object, "autoRejoin"))
+        server->setFlags(server->flags() | Server::AutoRejoin);
+    if (util::json::getBool(object, "joinInvite"))
+        server->setFlags(server->flags() | Server::JoinInvite);
+
+    return server;
+}
+
+Channel Server::splitChannel(const std::string &value)
 {
     auto pos = value.find(':');
 
     if (pos != std::string::npos)
-        return ServerChannel{value.substr(0, pos), value.substr(pos + 1)};
+        return { value.substr(0, pos), value.substr(pos + 1) };
 
-    return ServerChannel{value, ""};
+    return { value, "" };
 }
 
-Server::Server(std::string name, ServerInfo info, ServerSettings settings)
+Server::Server(std::string name)
     : m_name(std::move(name))
-    , m_info(std::move(info))
-    , m_settings(std::move(settings))
     , m_session(std::make_unique<Session>())
-    , m_state(std::make_unique<state::Connecting>())
+    , m_state(std::make_unique<ConnectingState>())
 {
     irc_callbacks_t callbacks;
 
@@ -462,7 +488,7 @@
 void Server::reconnect() noexcept
 {
     irc_disconnect(*m_session);
-    next(std::make_unique<state::Connecting>());
+    next(std::make_unique<ConnectingState>());
 }
 
 void Server::sync(fd_set &setinput, fd_set &setoutput)
@@ -518,11 +544,14 @@
 
 void Server::join(std::string channel, std::string password)
 {
-    m_queue.push([=] () {
-        const char *ptr = password.empty() ? nullptr : password.c_str();
+    if (m_session->isConnected())
+        m_queue.push([=] () {
+            const char *ptr = password.empty() ? nullptr : password.c_str();
 
-        return irc_cmd_join(*m_session, channel.c_str(), ptr) == 0;
-    });
+            return irc_cmd_join(*m_session, channel.c_str(), ptr) == 0;
+        });
+    else
+        m_channels.push_back({ std::move(channel), std::move(password) });
 }
 
 void Server::kick(std::string target, std::string channel, std::string reason)
--- a/lib/irccd/server.hpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/server.hpp	Thu Jul 14 10:47:20 2016 +0200
@@ -35,6 +35,8 @@
 #include <utility>
 #include <vector>
 
+#include <json.hpp>
+
 #include "elapsed-timer.hpp"
 #include "server-state.hpp"
 #include "signals.hpp"
@@ -42,19 +44,12 @@
 
 namespace irccd {
 
-/**
- * \brief A channel to join with an optional password.
- */
-class ServerChannel {
-public:
-    std::string name;                               //!< the channel to join
-    std::string password;                           //!< the optional password
-};
+class Server;
 
 /**
  * \brief Prefixes for nicknames.
  */
-enum class ServerChanMode {
+enum class ChannelMode {
     Creator         = 'O',                          //!< Channel creator
     HalfOperator    = 'h',                          //!< Half operator
     Operator        = 'o',                          //!< Channel operator
@@ -63,9 +58,18 @@
 };
 
 /**
+ * \brief A channel to join with an optional password.
+ */
+class Channel {
+public:
+    std::string name;                               //!< the channel to join
+    std::string password;                           //!< the optional password
+};
+
+/**
  * \brief Describe a whois information.
  */
-class ServerWhois {
+class Whois {
 public:
     std::string nick;                               //!< user's nickname
     std::string user;                               //!< user's user
@@ -75,49 +79,9 @@
 };
 
 /**
- * \brief Server information
- *
- * This class contains everything needed to connect to a server.
- */
-class ServerInfo {
-public:
-    enum {
-        Ipv6        = (1 << 0),                     //!< Connect using IPv6
-        Ssl         = (1 << 1),                     //!< Use SSL
-        SslVerify   = (1 << 2)                      //!< Verify SSL
-    };
-
-    std::string host;                               //!< Hostname
-    std::string password;                           //!< Optional server password
-    std::uint16_t port{6667};                       //!< Server's port
-    std::uint8_t flags{0};                          //!< Optional flags
-    std::map<ServerChanMode, char> modes;           //!< IRC modes (e.g. @~)
-};
-
-/**
- * \brief Contains settings to tweak the server
- *
- * This class contains additional settings that tweaks the server operations.
- */
-class ServerSettings {
-public:
-    enum {
-        AutoRejoin  = (1 << 0),                     //!< Auto rejoin a channel after being kicked
-        JoinInvite  = (1 << 1)                      //!< Join a channel on invitation
-    };
-
-    std::vector<ServerChannel> channels;            //!< List of channel to join
-    std::string command{"!"};                       //!< The command character to trigger plugin command
-    std::int8_t reconnectTries{-1};                 //!< Number of tries to reconnect before giving up
-    std::uint16_t reconnectDelay{30};               //!< Number of seconds to wait before trying to connect
-    std::uint8_t flags{0};                          //!< Optional flags
-    std::uint16_t pingTimeout{300};                 //!< Time in seconds before ping timeout is announced
-};
-
-/**
  * \brief Some variables that are needed in many places internally.
  */
-class ServerCache {
+class Cache {
 public:
     ElapsedTimer pingTimer;                         //!< Track elapsed time for ping timeout.
     std::int8_t reconnectCurrent{1};                //!< Number of reconnection already tested.
@@ -130,7 +94,7 @@
     /**
      * Map of whois being build by nicknames.
      */
-    std::map<std::string, ServerWhois> whoisMap;
+    std::map<std::string, Whois> whoisMap;
 };
 
 /**
@@ -297,7 +261,7 @@
 class WhoisEvent {
 public:
     std::shared_ptr<Server> server;         //!< The server.
-    ServerWhois whois;                      //!< The whois information.
+    Whois whois;                            //!< The whois information.
 };
 
 /**
@@ -321,6 +285,17 @@
     class Session;
 
     /**
+     * \brief Various options for server.
+     */
+    enum {
+        Ipv6        = (1 << 0),                     //!< Connect using IPv6
+        Ssl         = (1 << 1),                     //!< Use SSL
+        SslVerify   = (1 << 2),                     //!< Verify SSL
+        AutoRejoin  = (1 << 3),                     //!< Auto rejoin a channel after being kicked
+        JoinInvite  = (1 << 4)                      //!< Join a channel on invitation
+    };
+
+    /**
      * Signal: onChannelMode
      * ----------------------------------------------------------
      *
@@ -459,19 +434,35 @@
     Signal<WhoisEvent> onWhois;
 
 private:
+    // Misc
+    std::map<ChannelMode, char> m_modes;
+
     // Identifier.
     std::string m_name;
 
+    // Connection information
+    std::string m_host;
+    std::string m_password;
+    std::uint16_t m_port{6667};
+    std::uint8_t m_flags{0};
+
     // Identity.
     std::string m_nickname{"irccd"};
     std::string m_username{"irccd"};
     std::string m_realname{"IRC Client Daemon"};
     std::string m_ctcpversion{"IRC Client Daemon"};
 
+    // Settings.
+    std::string m_commandCharacter{"!"};
+    std::int8_t m_reconnectTries{-1};
+    std::uint16_t m_reconnectDelay{30};
+    std::uint16_t m_pingTimeout{300};
+
     // Various settings.
-    ServerInfo m_info;
-    ServerSettings m_settings;
-    ServerCache m_cache;
+    std::vector<Channel> m_channels;
+
+    // TODO: find another way.
+    Cache m_cache;
 
     // Queue of requests to send.
     std::queue<std::function<bool ()>> m_queue;
@@ -480,8 +471,8 @@
     std::unique_ptr<Session> m_session;
 
     // States.
-    std::unique_ptr<ServerState> m_state;
-    std::unique_ptr<ServerState> m_stateNext;
+    std::unique_ptr<State> m_state;
+    std::unique_ptr<State> m_stateNext;
 
     // Handle libircclient callbacks.
     void handleChannel(const char *, const char **) noexcept;
@@ -503,21 +494,30 @@
 
 public:
     /**
+     * Convert a JSON object as a server.
+     *
+     * Used in JavaScript API and transport commands.
+     *
+     * \param object the object
+     * \return the server
+     * \throw std::exception on failures
+     */
+    IRCCD_EXPORT static std::shared_ptr<Server> fromJson(const nlohmann::json &object);
+
+    /**
      * Split a channel from the form channel:password into a ServerChannel object.
      *
      * \param value the value
      * \return a channel
      */
-    IRCCD_EXPORT static ServerChannel splitChannel(const std::string &value);
+    IRCCD_EXPORT static Channel splitChannel(const std::string &value);
 
     /**
      * Construct a server.
      *
      * \param name the identifier
-     * \param info the information
-     * \param settings the settings
      */
-    IRCCD_EXPORT Server(std::string name, ServerInfo info, ServerSettings settings = {});
+    IRCCD_EXPORT Server(std::string name);
 
     /**
      * Destructor. Close the connection if needed.
@@ -535,6 +535,91 @@
     }
 
     /**
+     * Get the hostname.
+     *
+     * \return the hostname
+     */
+    inline const std::string &host() const noexcept
+    {
+        return m_host;
+    }
+
+    /**
+     * Set the hostname.
+     *
+     * \param host the hostname
+     */
+    inline void setHost(std::string host) noexcept
+    {
+        m_host = std::move(host);
+    }
+
+    /**
+     * Get the password.
+     *
+     * \return the password
+     */
+    inline const std::string &password() const noexcept
+    {
+        return m_password;
+    }
+
+    /**
+     * Set the password.
+     *
+     * An empty password means no password.
+     *
+     * \param password the password
+     */
+    inline void setPassword(std::string password) noexcept
+    {
+        m_password = std::move(password);
+    }
+
+    /**
+     * Get the port.
+     *
+     * \return the port
+     */
+    inline std::uint16_t port() const noexcept
+    {
+        return m_port;
+    }
+
+    /**
+     * Set the port.
+     *
+     * \param port the port
+     */
+    inline void setPort(std::uint16_t port) noexcept
+    {
+        m_port = port;
+    }
+
+    /**
+     * Get the flags.
+     *
+     * \return the flags
+     */
+    inline std::uint8_t flags() const noexcept
+    {
+        return m_flags;
+    }
+
+    /**
+     * Set the flags.
+     *
+     * \pre flags must be valid
+     * \param flags the flags
+     */
+    inline void setFlags(std::uint8_t flags) noexcept
+    {
+        assert(flags <= 0x1f);
+
+        m_flags = flags;
+    }
+
+    /**
      * Get the nickname.
      *
      * \return the nickname
@@ -554,23 +639,6 @@
     IRCCD_EXPORT void setNickname(std::string nickname);
 
     /**
-     * Get the CTCP version.
-     *
-     * \return the CTCP version
-     */
-    inline const std::string &ctcpVersion() const noexcept
-    {
-        return m_ctcpversion;
-    }
-
-    /**
-     * Set the CTCP version.
-     *
-     * \param ctcpversion the version
-     */
-    IRCCD_EXPORT void setCtcpVersion(std::string ctcpversion);
-
-    /**
      * Get the username.
      *
      * \return the username
@@ -613,34 +681,115 @@
     }
 
     /**
-     * Get the server information.
+     * Get the CTCP version.
      *
-     * \return the server information
+     * \return the CTCP version
+     */
+    inline const std::string &ctcpVersion() const noexcept
+    {
+        return m_ctcpversion;
+    }
+
+    /**
+     * Set the CTCP version.
+     *
+     * \param ctcpversion the version
+     */
+    IRCCD_EXPORT void setCtcpVersion(std::string ctcpversion);
+
+    /**
+     * Get the command character.
+     *
+     * \return the character
      */
-    inline const ServerInfo &info() const noexcept
+    inline const std::string &commandCharacter() const noexcept
+    {
+        return m_commandCharacter;
+    }
+
+    /**
+     * Set the command character.
+     *
+     * \pre !commandCharacter.empty()
+     * \param commandCharacter the command character
+     */
+    inline void setCommandCharacter(std::string commandCharacter) noexcept
     {
-        return m_info;
+        assert(!commandCharacter.empty());
+
+        m_commandCharacter = std::move(commandCharacter);
+    }
+
+    /**
+     * Get the number of reconnections before giving up.
+     *
+     * \return the number of reconnections
+     */
+    inline std::int8_t reconnectTries() const noexcept
+    {
+        return m_reconnectTries;
     }
 
     /**
-     * Get the server settings.
+     * Set the number of reconnections to test before giving up.
+     *
+     * A value less than 0 means infinite.
      *
-     * \note some settings will be used only after the next reconnection
-     * \return the settings
+     * \param reconnectTries the number of reconnections
      */
-    inline ServerSettings &settings() noexcept
+    inline void setReconnectTries(std::int8_t reconnectTries) noexcept
     {
-        return m_settings;
+        m_reconnectTries = reconnectTries;
+    }
+
+    /**
+     * Get the reconnection delay before retrying.
+     *
+     * \return the number of seconds
+     */
+    inline std::uint16_t reconnectDelay() const noexcept
+    {
+        return m_reconnectDelay;
     }
 
     /**
-     * Get the server settings.
+     * Set the number of seconds before retrying.
      *
-     * \return the settings
+     * \param reconnectDelay the number of seconds
+     */
+    inline void setReconnectDelay(std::uint16_t reconnectDelay) noexcept
+    {
+        m_reconnectDelay = reconnectDelay;
+    }
+
+    /**
+     * Get the ping timeout.
+     *
+     * \return the ping timeout
      */
-    inline const ServerSettings &settings() const noexcept
+    inline std::uint16_t pingTimeout() const noexcept
+    {
+        return m_pingTimeout;
+    }
+
+    /**
+     * Set the ping timeout before considering a server as dead.
+     *
+     * \param pingTimeout the delay in seconds
+     */
+    inline void setPingTimeout(std::uint16_t pingTimeout) noexcept
     {
-        return m_settings;
+        m_pingTimeout = pingTimeout;
+    }
+
+    /**
+     * Get the list of channels joined.
+     *
+     * \return the channels
+     */
+    inline const std::vector<Channel> &channels() const noexcept
+    {
+        return m_channels;
     }
 
     /**
@@ -649,7 +798,7 @@
      * \return the cache
      * \warning use with care
      */
-    inline ServerCache &cache() noexcept
+    inline Cache &cache() noexcept
     {
         return m_cache;
     }
@@ -669,7 +818,7 @@
      *
      * \param state the new state
      */
-    inline void next(std::unique_ptr<ServerState> state) noexcept
+    inline void next(std::unique_ptr<State> state) noexcept
     {
         m_stateNext = std::move(state);
     }
--- a/lib/irccd/service-server.cpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/service-server.cpp	Thu Jul 14 10:47:20 2016 +0200
@@ -236,11 +236,11 @@
 
     m_irccd.post(EventHandler{ev.server->name(), ev.origin, ev.channel,
         [=] (Plugin &plugin) -> std::string {
-            return util::parseMessage(ev.message, ev.server->settings().command, plugin.name()).second == util::MessageType::Command ? "onCommand" : "onMessage";
+            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onCommand" : "onMessage";
         },
         [=] (Plugin &plugin) mutable {
             auto copy = ev;
-            auto pack = util::parseMessage(copy.message, copy.server->settings().command, plugin.name());
+            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
 
             copy.message = pack.first;
 
@@ -414,11 +414,11 @@
 
     m_irccd.post(EventHandler{ev.server->name(), ev.origin, /* channel */ "",
         [=] (Plugin &plugin) -> std::string {
-            return util::parseMessage(ev.message, ev.server->settings().command, plugin.name()).second == util::MessageType::Command ? "onQueryCommand" : "onQuery";
+            return util::parseMessage(ev.message, ev.server->commandCharacter(), plugin.name()).second == util::MessageType::Command ? "onQueryCommand" : "onQuery";
         },
         [=] (Plugin &plugin) mutable {
             auto copy = ev;
-            auto pack = util::parseMessage(copy.message, copy.server->settings().command, plugin.name());
+            auto pack = util::parseMessage(copy.message, copy.server->commandCharacter(), plugin.name());
 
             copy.message = pack.first;
 
--- a/lib/irccd/util.hpp	Tue Jul 12 12:50:44 2016 +0200
+++ b/lib/irccd/util.hpp	Thu Jul 14 10:47:20 2016 +0200
@@ -35,6 +35,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <format.h>
 #include <json.hpp>
 
 #include "sysconfig.hpp"
@@ -199,6 +200,20 @@
 }
 
 /**
+ * Clamp the value between low and high.
+ *
+ * \param value the value
+ * \param low the minimum value
+ * \param high the maximum value
+ * \return the value between minimum and maximum
+ */
+template <typename T>
+constexpr T clamp(T value, T low, T high) noexcept
+{
+    return (value < high) ? std::max(value, low) : std::min(value, high);
+}
+
+/**
  * Parse IRC message and determine if it's a command or a simple message.
  *
  * \param message the message line
@@ -328,26 +343,98 @@
 namespace json {
 
 /**
- * Get a property or return null one if not found or if json is not an object.
+ * Require a property.
  *
  * \param json the json value
- * \param property the property key
- * \return the value or null one if not found
+ * \param key the property name
+ * \param type the requested property type
+ * \return the value
+ * \throw std::runtime_error if the property is missing
  */
-inline nlohmann::json get(const nlohmann::json &json, const std::string &property) noexcept
+inline nlohmann::json require(const nlohmann::json &json, const std::string &key, nlohmann::json::value_t type)
 {
-    if (!json.is_object())
-        return nlohmann::json();
-
-    auto it = json.find(property);
+    auto it = json.find(key);
+    auto dummy = nlohmann::json(type);
 
     if (it == json.end())
-        return nlohmann::json();
+        throw std::runtime_error(fmt::format("missing '{}' property", key));
+    if (it->type() != type)
+        throw std::runtime_error(fmt::format("invalid '{}' property ({} expected, got {})", key, it->type_name(), dummy.type_name()));
 
     return *it;
 }
 
 /**
+ * Convenient access for booleans.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the boolean
+ * \throw std::runtime_error if the property is missing or not a boolean
+ */
+inline bool requireBool(const nlohmann::json &json, const std::string &key)
+{
+    return require(json, key, nlohmann::json::value_t::boolean);
+}
+
+/**
+ * Convenient access for ints.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the int
+ * \throw std::runtime_error if the property is missing or not ant int
+ */
+inline std::int64_t requireInt(const nlohmann::json &json, const std::string &key)
+{
+    return require(json, key, nlohmann::json::value_t::number_integer);
+}
+
+/**
+ * Convenient access for unsigned ints.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the unsigned int
+ * \throw std::runtime_error if the property is missing or not ant int
+ */
+inline std::uint64_t requireUint(const nlohmann::json &json, const std::string &key)
+{
+    return require(json, key, nlohmann::json::value_t::number_unsigned);
+}
+
+/**
+ * Convenient access for strings.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the string
+ * \throw std::runtime_error if the property is missing or not a string
+ */
+inline std::string requireString(const nlohmann::json &json, const std::string &key)
+{
+    return require(json, key, nlohmann::json::value_t::string);
+}
+
+/**
+ * Convenient access for unique identifiers.
+ *
+ * \param json the json object
+ * \param key the property key
+ * \return the identifier
+ * \throw std::runtime_error if the property is invalid
+ */
+inline std::string requireIdentifier(const nlohmann::json &json, const std::string &key)
+{
+    auto id = requireString(json, key);
+
+    if (!isIdentifierValid(id))
+        throw std::runtime_error("invalid '{}' identifier property");
+
+    return id;
+}
+
+/**
  * Convert the json value to boolean.
  *
  * \param json the json value
@@ -366,9 +453,21 @@
  * \param def the default value if not an int
  * \return an int
  */
-inline int toInt(const nlohmann::json &json, int def = 0) noexcept
+inline std::int64_t toInt(const nlohmann::json &json, std::int64_t def = 0) noexcept
 {
-    return json.is_number() ? json.get<int>() : def;
+    return json.is_number_integer() ? json.get<std::int64_t>() : def;
+}
+
+/**
+ * Convert the json value to unsigned.
+ *
+ * \param json the json value
+ * \param def the default value if not a unsigned int
+ * \return an unsigned int
+ */
+inline std::uint64_t toUint(const nlohmann::json &json, std::uint64_t def = 0) noexcept
+{
+    return json.is_number_unsigned() ? json.get<std::uint64_t>() : def;
 }
 
 /**
@@ -383,6 +482,109 @@
     return json.is_string() ? json.get<std::string>() : def;
 }
 
+/**
+ * Get a property or return null one if not found or if json is not an object.
+ *
+ * \param json the json value
+ * \param property the property key
+ * \return the value or null one if not found
+ */
+inline nlohmann::json get(const nlohmann::json &json, const std::string &property) noexcept
+{
+    auto it = json.find(property);
+
+    if (it == json.end())
+        return nlohmann::json();
+
+    return *it;
+}
+
+/**
+ * Convenient access for boolean with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the boolean
+ */
+inline bool getBool(const nlohmann::json &json, const std::string &key, bool def = false) noexcept
+{
+    return toBool(get(json, key), def);
+}
+
+/**
+ * Convenient access for ints with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the int
+ */
+inline std::int64_t getInt(const nlohmann::json &json, const std::string &key, std::int64_t def = 0) noexcept
+{
+    return toInt(get(json, key), def);
+}
+
+/**
+ * Convenient access for unsigned ints with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the unsigned int
+ */
+inline std::uint64_t getUint(const nlohmann::json &json, const std::string &key, std::uint64_t def = 0) noexcept
+{
+    return toUint(get(json, key), def);
+}
+
+/**
+ * Get an integer in the given range.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the boolean
+ */
+template <typename T>
+inline T getIntRange(const nlohmann::json &json,
+                     const std::string &key,
+                     std::int64_t min = std::numeric_limits<T>::min(),
+                     std::int64_t max = std::numeric_limits<T>::max()) noexcept
+{
+    return clamp(getInt(json, key), min, max);
+}
+
+/**
+ * Get an unsigned integer in the given range.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the boolean
+ */
+template <typename T>
+inline T getUintRange(const nlohmann::json &json,
+                    const std::string &key,
+                    std::uint64_t min = std::numeric_limits<T>::min(),
+                    std::uint64_t max = std::numeric_limits<T>::max()) noexcept
+{
+    return clamp(getUint(json, key), min, max);
+}
+
+/**
+ * Convenient access for strings with default value.
+ *
+ * \param json the json value
+ * \param key the property key
+ * \param def the default value
+ * \return the string
+ */
+inline std::string getString(const nlohmann::json &json, const std::string &key, std::string def = "") noexcept
+{
+    return toString(get(json, key), def);
+}
+
 } // !json
 
 } // !util