changeset 649:4a13a016ea4f

Irccd: use new json_util::parser class
author David Demelier <markand@malikania.fr>
date Mon, 26 Mar 2018 20:30:15 +0200
parents 8a9cac75608f
children 27896e9bcd9e
files irccdctl/cli.cpp irccdctl/main.cpp irccdctl/plugin_info_cli.cpp irccdctl/watch_cli.cpp libcommon/irccd/json_util.hpp 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_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_topic_command.cpp libirccd/irccd/daemon/server_util.cpp libirccd/irccd/daemon/service/transport_service.cpp libirccd/irccd/daemon/transport_server.cpp libirccdctl/irccd/ctl/controller.cpp
diffstat 30 files changed, 352 insertions(+), 264 deletions(-) [+]
line wrap: on
line diff
--- a/irccdctl/cli.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/irccdctl/cli.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -36,7 +36,7 @@
         if (code)
             throw boost::system::system_error(code);
 
-        const auto c = json_util::get_string(message, "/command"_json_pointer);
+        const auto c = json_util::parser(message).get<std::string>("command");
 
         if (!c) {
             recv_response(ctl, std::move(req), std::move(handler));
--- a/irccdctl/main.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/irccdctl/main.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -514,9 +514,10 @@
             throw boost::system::system_error(code);
 
         if (verbose) {
-            const auto major = json_util::get_int(info, "/major"_json_pointer);
-            const auto minor = json_util::get_int(info, "/minor"_json_pointer);
-            const auto patch = json_util::get_int(info, "/patch"_json_pointer);
+            const json_util::parser parser(info);
+            const auto major = parser.get<int>("/major");
+            const auto minor = parser.get<int>("/minor");
+            const auto patch = parser.get<int>("/patch");
 
             if (!major || !minor || !patch)
                 std::cout << "connected to irccd (unknown version)" << std::endl;
--- a/irccdctl/plugin_info_cli.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/irccdctl/plugin_info_cli.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,15 +35,17 @@
         throw std::invalid_argument("plugin-info requires 1 argument");
 
     request(ctl, {{ "plugin", args[0] }}, [] (auto result) {
+        const json_util::parser parser(result);
+
         std::cout << std::boolalpha;
         std::cout << "Author         : " <<
-            json_util::get_string(result, "/author"_json_pointer).value_or("(unknown)") << std::endl;
+            parser.get<std::string>("author").value_or("(unknown)") << std::endl;
         std::cout << "License        : " <<
-            json_util::get_string(result, "/license"_json_pointer).value_or("(unknown)") << std::endl;
+            parser.get<std::string>("license").value_or("(unknown)") << std::endl;
         std::cout << "Summary        : " <<
-            json_util::get_string(result, "/summary"_json_pointer).value_or("(unknown)") << std::endl;
+            parser.get<std::string>("summary").value_or("(unknown)") << std::endl;
         std::cout << "Version        : " <<
-            json_util::get_string(result, "/version"_json_pointer).value_or("(unknown)") << std::endl;
+            parser.get<std::string>("version").value_or("(unknown)") << std::endl;
     });
 }
 
--- a/irccdctl/watch_cli.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/irccdctl/watch_cli.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -181,7 +181,7 @@
         if (code)
             throw boost::system::system_error(code);
 
-        const auto event = json_util::get_string(message, "/event"_json_pointer);
+        const auto event = json_util::parser(message).get<std::string>("event");
         const auto it = events.find(event ? *event : "");
 
         if (it != events.end()) {
--- a/libcommon/irccd/json_util.hpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libcommon/irccd/json_util.hpp	Mon Mar 26 20:30:15 2018 +0200
@@ -16,8 +16,8 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#ifndef IRCCD_COMMON_JSON_UTIL_HPP
-#define IRCCD_COMMON_JSON_UTIL_HPP
+#ifndef IRCCD_JSON_UTIL_HPP
+#define IRCCD_JSON_UTIL_HPP
 
 /**
  * \file json_util.hpp
@@ -25,7 +25,9 @@
  */
 
 #include <cstdint>
+#include <limits>
 #include <string>
+#include <type_traits>
 
 #include <boost/optional.hpp>
 
@@ -39,214 +41,282 @@
 namespace json_util {
 
 /**
- * Get a JSON value from the given object or array.
- *
- * \param json the JSON object/array
- * \param key the pointer to the object
- * \return the value or boost::none if not found
+ * \cond JSON_UTIL_HIDDEN_SYMBOLS
  */
-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;
+
+namespace detail {
+
+template <typename Int>
+class parser_type_traits_uint : public std::true_type {
+public:
+    static boost::optional<Int> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_unsigned())
+            return boost::none;
+
+        const auto ret = value.get<std::uint64_t>();
+
+        if (ret > std::numeric_limits<Int>::max())
+            return boost::none;
+
+        return static_cast<Int>(ret);
     }
-}
+};
+
+template <typename Int>
+class parser_type_traits_int : public std::true_type {
+public:
+    static boost::optional<Int> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_integer())
+            return boost::none;
+
+        const auto ret = value.get<std::int64_t>();
+
+        if (ret < std::numeric_limits<Int>::min() || ret > std::numeric_limits<Int>::max())
+            return boost::none;
+
+        return static_cast<Int>(ret);
+    }
+};
+
+} // !detail
 
 /**
- * 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
+ * \endcond
  */
-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.
+ * \brief Describe how to convert a JSON value.
+ *
+ * This class must be specialized for every type you want to convert from JSON
+ * to its native type.
+ *
+ * You only need to implement the get function with the following signature:
  *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \return the value or boost::none if not found or invalid
+ * ```cpp
+ * static boost::optional<T> get(const nlohmann::json& value);
+ * ```
+ *
+ * The implementation should not throw an exception but return a null optional
+ * instead.
+ *
+ * This class is already specialized for the given types:
+ *
+ * - bool
+ * - double
+ * - std::uint(8, 16, 32, 64)
+ * - std::string
  */
-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>();
-}
+template <typename T>
+class parser_type_traits : public std::false_type {
+};
 
 /**
- * Get a 64 bit signed integer or null if not found or invalid.
- *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \return the value or boost::none if not found or invalid
+ * \brief Specialization for `bool`.
  */
-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);
+template <>
+class parser_type_traits<bool> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to bool.
+     *
+     * \return the bool or none if not a boolean type
+     */
+    static boost::optional<bool> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_boolean())
+            return boost::none;
 
-    if (!v || !v->is_number_integer())
-        return boost::none;
-
-    return v->template get<std::int64_t>();
-}
+        return value.get<bool>();
+    }
+};
 
 /**
- * Get a 64 bit unsigned integer or null if not found or invalid.
- *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \return the value or boost::none if not found or invalid
+ * \brief Specialization for `double`.
  */
-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);
+template <>
+class parser_type_traits<double> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to bool.
+     *
+     * \return the double or none if not a double type
+     */
+    static boost::optional<double> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_float())
+            return boost::none;
+
+        return value.get<double>();
+    }
+};
 
-    if (!v || !v->is_number_unsigned())
-        return boost::none;
+/**
+ * \brief Specialization for `std::string`.
+ */
+template <>
+class parser_type_traits<std::string> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to bool.
+     *
+     * \return the string or none if not a string type
+     */
+    static boost::optional<std::string> get(const nlohmann::json& value)
+    {
+        if (!value.is_string())
+            return boost::none;
 
-    return v->template get<std::uint64_t>();
-}
+        return value.get<std::string>();
+    }
+};
 
 /**
- * Get a string or null if not found or invalid.
- *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \return the value or boost::none if not found or invalid
+ * \brief Specialization for `std::int8_t`.
+ */
+template <>
+class parser_type_traits<std::int8_t> : public detail::parser_type_traits_int<std::int8_t> {
+};
+
+/**
+ * \brief Specialization for `std::int16_t`.
  */
-template <typename Key>
-inline boost::optional<std::string> get_string(const nlohmann::json& json, const Key& key) noexcept
-{
-    const auto v = get(json, key);
+template <>
+class parser_type_traits<std::int16_t> : public detail::parser_type_traits_int<std::int16_t> {
+};
 
-    if (!v || !v->is_string())
-        return boost::none;
-
-    return v->template get<std::string>();
-}
+/**
+ * \brief Specialization for `std::int32_t`.
+ */
+template <>
+class parser_type_traits<std::int32_t> : public detail::parser_type_traits_int<std::int32_t> {
+};
 
 /**
- * Get an optional bool.
- *
- * If the property is not found, return default value. If the property is not
- * a bool, return boost::none, otherwise return the value.
- *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \param def the default value
- * \return the value, boost::none or def
+ * \brief Specialization for `std::int64_t`.
  */
-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);
+template <>
+class parser_type_traits<std::int64_t> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to std::int64_t.
+     *
+     * \return the int or none if not a int type
+     */
+    static boost::optional<std::int64_t> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_integer())
+            return boost::none;
+
+        return value.get<std::int64_t>();
+    }
+};
 
-    if (!v)
-        return def;
-    if (!v->is_boolean())
-        return boost::none;
+/**
+ * \brief Specialization for `std::int8_t`.
+ */
+template <>
+class parser_type_traits<std::uint8_t> : public detail::parser_type_traits_uint<std::uint8_t> {
+};
 
-    return v->template get<bool>();
-}
+/**
+ * \brief Specialization for `std::int16_t`.
+ */
+template <>
+class parser_type_traits<std::uint16_t> : public detail::parser_type_traits_uint<std::uint16_t> {
+};
+
+/**
+ * \brief Specialization for `std::int32_t`.
+ */
+template <>
+class parser_type_traits<std::uint32_t> : public detail::parser_type_traits_uint<std::uint32_t> {
+};
 
 /**
- * Get an optional integer.
- *
- * If the property is not found, return default value. If the property is not
- * an integer, return boost::none, otherwise return the value.
- *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \param def the default value
- * \return the value, boost::none or def
+ * \brief Specialization for `std::int64_t`.
  */
-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);
+template <>
+class parser_type_traits<std::uint64_t> : public std::true_type {
+public:
+    /**
+     * Convert the JSON value to std::uint64_t.
+     *
+     * \return the int or none if not a int type
+     */
+    static boost::optional<std::uint64_t> get(const nlohmann::json& value) noexcept
+    {
+        if (!value.is_number_unsigned())
+            return boost::none;
 
-    if (!v)
-        return def;
-    if (!v->is_number_integer())
-        return boost::none;
-
-    return v->template get<std::int64_t>();
-}
+        return value.get<std::uint64_t>();
+    }
+};
 
 /**
- * Get an optional unsigned integer.
- *
- * If the property is not found, return default value. If the property is not
- * an unsigned integer, return boost::none, otherwise return the value.
+ * \brief Convenient JSON object parser
  *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \param def the default value
- * \return the value, boost::none or def
+ * This class helps destructuring insecure JSON input by returning optional
+ * values if they are not present or invalid.
  */
-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);
+class parser {
+private:
+    nlohmann::json json_;
 
-    if (!v)
-        return def;
-    if (!v->is_number_unsigned())
-        return boost::none;
+public:
+    /**
+     * Construct the parser.
+     *
+     * \param document the document
+     */
+    inline parser(nlohmann::json document) noexcept
+        : json_(std::move(document))
+    {
+    }
 
-    return v->template get<std::uint64_t>();
-}
+    /**
+     * Get a value from the document object.
+     *
+     * \param key the property key
+     * \return the value or boost::none if not found
+     */
+    template <typename Type>
+    inline boost::optional<Type> get(const std::string& key) const noexcept
+    {
+        static_assert(parser_type_traits<Type>::value, "type not supported");
 
-/**
- * Get an optional string.
- *
- * If the property is not found, return default value. If the property is not
- * a string, return boost::none, otherwise return the value.
- *
- * \param json the JSON object/array
- * \param key the pointer or property key
- * \param def the default value
- * \return the value, boost::none or def
- */
-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);
+        const auto it = json_.find(key);
+
+        if (it == json_.end())
+            return boost::none;
+
+        return parser_type_traits<Type>::get(*it);
+    }
 
-    if (!v)
-        return def;
-    if (!v->is_string())
-        return boost::none;
+    /**
+     * Get an optional value from the document object.
+     *
+     * If the value is undefined, the default value is returned. Otherwise, if
+     * the value is not in the given type, boost::none is returned.
+     *
+     * \param key the property key
+     * \param def the default value if property is undefined
+     * \return the value, boost::none or def
+     */
+    template <typename Type, typename DefaultValue>
+    inline boost::optional<Type> optional(const std::string& key, DefaultValue&& def) const noexcept
+    {
+        static_assert(parser_type_traits<Type>::value, "type not supported");
 
-    return v->template get<std::string>();
-}
+        const auto it = json_.find(key);
+
+        if (it == json_.end())
+            return boost::optional<Type>(std::forward<DefaultValue>(def));
+
+        return parser_type_traits<Type>::get(*it);
+    }
+};
 
 /**
  * Print the value as human readable.
@@ -292,8 +362,6 @@
     return false;
 }
 
-
-
 } // !json_util
 
 } // !irccd
--- a/libirccd/irccd/daemon/command/plugin_config_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_config_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -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");
+    const auto id = json_util::parser(args).get<std::string>("plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_info_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_info_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -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");
+    const auto id = json_util::parser(args).get<std::string>("plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_load_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_load_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -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");
+    const auto id = json_util::parser(args).get<std::string>("plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_reload_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_reload_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -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");
+    const auto id = json_util::parser(args).get<std::string>("plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/plugin_unload_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/plugin_unload_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -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");
+    const auto id = json_util::parser(args).get<std::string>("plugin");
 
     if (!id || !string_util::is_identifier(*id))
         throw plugin_error(plugin_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/rule_add_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_add_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,11 +35,9 @@
 
 void rule_add_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    auto index = json_util::get_uint(args, "index");
+    const auto index = json_util::parser(args).optional<unsigned>("index", irccd.rules().length());
 
-    if (!index)
-        index = irccd.rules().length();
-    if (index > irccd.rules().length())
+    if (!index || *index > irccd.rules().length())
         throw rule_error(rule_error::error::invalid_index);
 
     irccd.rules().insert(rule_util::from_json(args), *index);
--- a/libirccd/irccd/daemon/command/rule_edit_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_edit_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -49,7 +49,7 @@
     };
 
     // Create a copy to avoid incomplete edition in case of errors.
-    const auto index = json_util::get_uint(args, "index");
+    const auto index = json_util::parser(args).get<unsigned>("index");
 
     if (!index)
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/rule_info_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_info_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -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");
+    const auto index = json_util::parser(args).get<unsigned>("index");
 
     if (!index)
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/rule_move_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_move_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,8 +35,9 @@
 
 void rule_move_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto from = json_util::get_uint(args, "from");
-    const auto to = json_util::get_uint(args, "to");
+    const json_util::parser parser(args);
+    const auto from = parser.get<unsigned>("from");
+    const auto to = parser.get<unsigned>("to");
 
     if (!from || !to)
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/rule_remove_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/rule_remove_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -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");
+    const auto index = json_util::parser(args).get<unsigned>("index");
 
     if (!index || *index >= irccd.rules().length())
         throw rule_error(rule_error::invalid_index);
--- a/libirccd/irccd/daemon/command/server_info_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_info_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -36,7 +36,7 @@
 
 void server_info_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = json_util::get_string(args, "server");
+    const auto id = json_util::parser(args).get<std::string>("server");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_invite_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_invite_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,9 +35,10 @@
 
 void server_invite_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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 json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto target = parser.get<std::string>("target");
+    const auto channel = parser.get<std::string>("channel");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_join_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_join_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,16 +35,19 @@
 
 void server_join_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto channel = parser.get<std::string>("channel");
+    const auto password = parser.optional<std::string>("password", "");
 
     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 (!password)
+        throw server_error(server_error::invalid_password);
 
-    irccd.servers().require(*id)->join(*channel, password ? *password : "");
+    irccd.servers().require(*id)->join(*channel, *password);
     client.success("server-join");
 }
 
--- a/libirccd/irccd/daemon/command/server_kick_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_kick_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,10 +35,11 @@
 
 void server_kick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto target = parser.get<std::string>("target");
+    const auto channel = parser.get<std::string>("channel");
+    const auto reason = parser.optional<std::string>("reason", "");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
@@ -46,8 +47,10 @@
         throw server_error(server_error::invalid_nickname);
     if (!channel || channel->empty())
         throw server_error(server_error::invalid_channel);
+    if (!reason)
+        throw server_error(server_error::invalid_message);
 
-    irccd.servers().require(*id)->kick(*target, *channel, reason ? *reason : "");
+    irccd.servers().require(*id)->kick(*target, *channel, *reason);
     client.success("server-kick");
 }
 
--- a/libirccd/irccd/daemon/command/server_me_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_me_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,9 +35,10 @@
 
 void server_me_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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", "");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto channel = parser.get<std::string>("target");
+    const auto message = parser.optional<std::string>("message", "");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_message_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_message_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,9 +35,10 @@
 
 void server_message_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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", "");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto channel = parser.get<std::string>("target");
+    const auto message = parser.optional<std::string>("message", "");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_mode_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_mode_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,12 +35,13 @@
 
 void server_mode_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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", "");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto channel = parser.get<std::string>("channel");
+    const auto mode = parser.get<std::string>("mode");
+    const auto limit = parser.optional<std::string>("limit", "");
+    const auto user = parser.optional<std::string>("user", "");
+    const auto mask = parser.optional<std::string>("mask", "");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_nick_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_nick_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,8 +35,9 @@
 
 void server_nick_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    const auto id = json_util::get_string(args, "server");
-    const auto nick = json_util::get_string(args, "nickname");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto nick = parser.get<std::string>("nickname");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_notice_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_notice_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,9 +35,10 @@
 
 void server_notice_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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", "");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto channel = parser.get<std::string>("target");
+    const auto message = parser.optional<std::string>("message", "");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_part_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_part_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,9 +35,10 @@
 
 void server_part_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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", "");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto channel = parser.get<std::string>("channel");
+    const auto reason = parser.optional<std::string>("reason", "");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/command/server_topic_command.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/command/server_topic_command.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -35,9 +35,10 @@
 
 void server_topic_command::exec(irccd& irccd, transport_client& client, const nlohmann::json& args)
 {
-    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", "");
+    const json_util::parser parser(args);
+    const auto id = parser.get<std::string>("server");
+    const auto channel = parser.get<std::string>("channel");
+    const auto topic = parser.optional<std::string>("topic", "");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
--- a/libirccd/irccd/daemon/server_util.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/server_util.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -119,15 +119,15 @@
     sv.set_command_char(command_char);
 }
 
-void from_json_load_options(server& sv, const nlohmann::json& object)
+void from_json_load_options(server& sv, const json_util::parser& parser)
 {
-    const auto port = json_util::optional_uint(object, "port", sv.get_port());
-    const auto nickname = json_util::optional_string(object, "nickname", sv.get_nickname());
-    const auto realname = json_util::optional_string(object, "realname", sv.get_realname());
-    const auto username = json_util::optional_string(object, "username", sv.get_username());
-    const auto ctcp_version = json_util::optional_string(object, "ctcpVersion", sv.get_ctcp_version());
-    const auto command = json_util::optional_string(object, "commandChar", sv.get_command_char());
-    const auto password = json_util::optional_string(object, "password", sv.get_password());
+    const auto port = parser.optional<std::uint16_t>("port", sv.get_port());
+    const auto nickname = parser.optional<std::string>("nickname", sv.get_nickname());
+    const auto realname = parser.optional<std::string>("realname", sv.get_realname());
+    const auto username = parser.optional<std::string>("username", sv.get_username());
+    const auto ctcp_version = parser.optional<std::string>("ctcpVersion", sv.get_ctcp_version());
+    const auto command = parser.optional<std::string>("commandChar", sv.get_command_char());
+    const auto password = parser.optional<std::string>("password", sv.get_password());
 
     if (!port || *port > std::numeric_limits<std::uint16_t>::max())
         throw server_error(server_error::invalid_port);
@@ -153,29 +153,29 @@
     sv.set_password(*password);
 }
 
-void from_json_load_flags(server& sv, nlohmann::json object)
+void from_json_load_flags(server& sv, const json_util::parser& parser)
 {
-    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"];
+    const auto ipv6 = parser.get<bool>("ipv6");
+    const auto auto_rejoin = parser.get<bool>("autoRejoin");
+    const auto join_invite = parser.get<bool>("joinInvite");
+    const auto ssl = parser.get<bool>("ssl");
+    const auto ssl_verify = parser.get<bool>("sslVerify");
 
-    if (ipv6.is_boolean() && ipv6.get<bool>())
+    if (ipv6.value_or(false))
         sv.set_flags(sv.get_flags() | server::ipv6);
-    if (ssl.is_boolean() && ssl.get<bool>())
-        sv.set_flags(sv.get_flags() | server::ssl);
-    if (ssl_verify.is_boolean() && ssl_verify.get<bool>())
-        sv.set_flags(sv.get_flags() | server::ssl_verify);
-    if (auto_rejoin.is_boolean() && auto_rejoin.get<bool>())
+    if (auto_rejoin.value_or(false))
         sv.set_flags(sv.get_flags() | server::auto_rejoin);
-    if (join_invite.is_boolean() && join_invite.get<bool>())
+    if (join_invite.value_or(false))
         sv.set_flags(sv.get_flags() | server::join_invite);
 
+    if (ssl.value_or(false))
 #if !defined(HAVE_SSL)
-    if (sv.get_flags() & server::ssl)
         throw server_error(server_error::ssl_disabled);
+#else
+        sv.set_flags(sv.get_flags() | server::ssl);
 #endif
+    if (ssl_verify.value_or(false))
+        sv.set_flags(sv.get_flags() | server::ssl_verify);
 }
 
 } // !namespace
@@ -183,8 +183,9 @@
 std::shared_ptr<server> from_json(boost::asio::io_service& service, const nlohmann::json& object)
 {
     // Mandatory parameters.
-    const auto id = json_util::get_string(object, "name");
-    const auto host = json_util::get_string(object, "host");
+    const json_util::parser parser(object);
+    const auto id = parser.get<std::string>("name");
+    const auto host = parser.get<std::string>("host");
 
     if (!id || !string_util::is_identifier(*id))
         throw server_error(server_error::invalid_identifier);
@@ -193,8 +194,8 @@
 
     const auto sv = std::make_shared<server>(service, *id, *host);
 
-    from_json_load_options(*sv, object);
-    from_json_load_flags(*sv, object);
+    from_json_load_options(*sv, parser);
+    from_json_load_flags(*sv, parser);
 
     return sv;
 }
--- a/libirccd/irccd/daemon/service/transport_service.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/service/transport_service.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -41,7 +41,7 @@
 {
     assert(object.is_object());
 
-    const auto name = json_util::get_string(object, "/command"_json_pointer);
+    const auto name = json_util::parser(object).get<std::string>("command");
 
     if (!name) {
         tc->error(irccd_error::invalid_message);
--- a/libirccd/irccd/daemon/transport_server.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccd/irccd/daemon/transport_server.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -39,8 +39,9 @@
             return;
         }
 
-        const auto command = json_util::get_string(message, "/command"_json_pointer);
-        const auto password = json_util::get_string(message, "/password"_json_pointer);
+        const json_util::parser parser(message);
+        const auto command = parser.get<std::string>("command");
+        const auto password = parser.get<std::string>("password");
 
         if (!command || *command != "auth") {
             client->error(irccd_error::auth_required);
--- a/libirccdctl/irccd/ctl/controller.cpp	Mon Mar 26 19:22:01 2018 +0200
+++ b/libirccdctl/irccd/ctl/controller.cpp	Mon Mar 26 20:30:15 2018 +0200
@@ -60,8 +60,9 @@
             return;
         }
 
-        const auto program = json_util::get_string(message, "/program"_json_pointer);
-        const auto major = json_util::get_int(message, "/major"_json_pointer);
+        const json_util::parser parser(message);
+        const auto program = parser.get<std::string>("program");
+        const auto major = parser.get<int>("major");
 
         if (!program && *program != "irccd")
             handler(irccd_error::not_irccd, std::move(message));
@@ -99,8 +100,9 @@
             return;
         }
 
-        const auto e = json_util::get_int(msg, "/error"_json_pointer);
-        const auto c = json_util::get_string(msg, "/errorCategory"_json_pointer);
+        const json_util::parser parser(msg);
+        const auto e = parser.get<int>("error");
+        const auto c = parser.get<std::string>("errorCategory");
 
         if (e && c) {
             if (*c == "irccd")