Mercurial > irccd
changeset 204:751ac58d7747
Irccd: initial properties required in Command, #516
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 10 Jun 2016 11:54:13 +0200 |
parents | c26754e419c4 |
children | eb5b0f480d63 |
files | lib/irccd/cmd-plugin-config.cpp lib/irccd/cmd-plugin-config.hpp lib/irccd/cmd-plugin-info.cpp lib/irccd/cmd-plugin-info.hpp lib/irccd/cmd-plugin-load.cpp lib/irccd/cmd-plugin-load.hpp lib/irccd/cmd-plugin-reload.cpp lib/irccd/cmd-plugin-reload.hpp lib/irccd/command.cpp lib/irccd/command.hpp tests/CMakeLists.txt tests/command/CMakeLists.txt tests/command/main.cpp |
diffstat | 13 files changed, 338 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- a/lib/irccd/cmd-plugin-config.cpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-config.cpp Fri Jun 10 11:54:13 2016 +0200 @@ -78,6 +78,11 @@ }; } +std::vector<Command::Property> PluginConfig::properties() const +{ + return {{ "plugin", { json::Type::String }}}; +} + json::Value PluginConfig::request(Irccdctl &, const CommandRequest &args) const { auto object = json::object({ @@ -96,6 +101,8 @@ json::Value PluginConfig::exec(Irccd &irccd, const json::Value &request) const { + Command::exec(irccd, request); + return request.contains("value") ? execSet(irccd, request) : execGet(irccd, request); }
--- a/lib/irccd/cmd-plugin-config.hpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-config.hpp Fri Jun 10 11:54:13 2016 +0200 @@ -51,6 +51,11 @@ IRCCD_EXPORT std::vector<Arg> args() const override; /** + * \copydoc Command::properties + */ + IRCCD_EXPORT std::vector<Property> properties() const override; + + /** * \copydoc Command::request */ IRCCD_EXPORT json::Value request(Irccdctl &irccdctl, const CommandRequest &args) const override;
--- a/lib/irccd/cmd-plugin-info.cpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-info.cpp Fri Jun 10 11:54:13 2016 +0200 @@ -43,6 +43,11 @@ return {{ "plugin", true }}; } +std::vector<Command::Property> PluginInfo::properties() const +{ + return {{ "plugin", { json::Type::String }}}; +} + json::Value PluginInfo::request(Irccdctl &, const CommandRequest &args) const { return json::object({{ "plugin", args.arg(0) }}); @@ -50,6 +55,8 @@ json::Value PluginInfo::exec(Irccd &irccd, const json::Value &request) const { + Command::exec(irccd, request); + auto plugin = irccd.pluginService().require(request.at("plugin").toString()); return json::object({
--- a/lib/irccd/cmd-plugin-info.hpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-info.hpp Fri Jun 10 11:54:13 2016 +0200 @@ -52,6 +52,11 @@ IRCCD_EXPORT std::vector<Arg> args() const override; /** + * \copydoc Command::properties + */ + IRCCD_EXPORT std::vector<Property> properties() const override; + + /** * \copydoc Command::request */ IRCCD_EXPORT json::Value request(Irccdctl &irccdctl, const CommandRequest &args) const override;
--- a/lib/irccd/cmd-plugin-load.cpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-load.cpp Fri Jun 10 11:54:13 2016 +0200 @@ -40,13 +40,18 @@ return {{ "plugin", true }}; } +std::vector<Command::Property> PluginLoad::properties() const +{ + return {{ "plugin", { json::Type::String }}}; +} + json::Value PluginLoad::exec(Irccd &irccd, const json::Value &request) const { - auto name = request.at("plugin").toString(); + Command::exec(irccd, request); - irccd.pluginService().load(name); + irccd.pluginService().load(request.at("plugin").toString()); - return Command::exec(irccd, request); + return json::object(); } } // !command
--- a/lib/irccd/cmd-plugin-load.hpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-load.hpp Fri Jun 10 11:54:13 2016 +0200 @@ -52,6 +52,11 @@ IRCCD_EXPORT std::vector<Arg> args() const override; /** + * \copydoc Command::properties + */ + IRCCD_EXPORT std::vector<Property> properties() const override; + + /** * \copydoc Command::exec */ IRCCD_EXPORT json::Value exec(Irccd &irccd, const json::Value &request) const override;
--- a/lib/irccd/cmd-plugin-reload.cpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-reload.cpp Fri Jun 10 11:54:13 2016 +0200 @@ -41,11 +41,18 @@ return {{ "plugin", true }}; } +std::vector<Command::Property> PluginReload::properties() const +{ + return {{ "plugin", { json::Type::String }}}; +} + json::Value PluginReload::exec(Irccd &irccd, const json::Value &request) const { + Command::exec(irccd, request); + irccd.pluginService().require(request.at("plugin").toString())->onReload(irccd); - return Command::exec(irccd, request); + return json::object(); } } // !command
--- a/lib/irccd/cmd-plugin-reload.hpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/cmd-plugin-reload.hpp Fri Jun 10 11:54:13 2016 +0200 @@ -52,6 +52,11 @@ IRCCD_EXPORT std::vector<Arg> args() const override; /** + * \copydoc Command::properties + */ + IRCCD_EXPORT std::vector<Property> properties() const override; + + /** * \copydoc Command::exec */ IRCCD_EXPORT json::Value exec(Irccd &irccd, const json::Value &request) const override;
--- a/lib/irccd/command.cpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/command.cpp Fri Jun 10 11:54:13 2016 +0200 @@ -20,14 +20,65 @@ #include <numeric> #include <sstream> +#include <format.h> + #include "command.hpp" #include "logger.hpp" #include "system.hpp" using namespace std::string_literals; +using namespace fmt::literals; + namespace irccd { +namespace { + +/* + * typeName + * ------------------------------------------------------------------ + * + * Convert a JSON value type to string for convenience. + */ +std::string typeName(json::Type type) +{ + static const std::vector<std::string> typenames{ + "array", "boolean", "int", "null", "object", "real", "string" + }; + + assert(type >= json::Type::Array && type <= json::Type::String); + + return typenames[static_cast<int>(type)]; +} + +/* + * typeNameList + * ------------------------------------------------------------------ + * + * Construct a list of names to send a convenient error message if properties are invalid, example: string, int or bool expected. + */ + +std::string typeNameList(const std::vector<json::Type> &types) +{ + std::ostringstream oss; + + if (types.size() == 1) + return typeName(types[0]); + + for (std::size_t i = 0; i < types.size(); ++i) { + oss << typeName(types[i]); + + if (i == types.size() - 2) + oss << " or "; + else if (i < types.size() - 1) + oss << ", "; + } + + return oss.str(); +} + +} // !namespace + std::string Command::usage() const { std::ostringstream oss; @@ -90,8 +141,23 @@ return json::object({}); } -json::Value Command::exec(Irccd &, const json::Value &) const +json::Value Command::exec(Irccd &, const json::Value &request) const { + // Verify that requested properties are present in the request. + for (const auto &prop : properties()) { + auto it = request.find(prop.name()); + + if (it == request.end()) + throw std::invalid_argument("missing '{}' property"_format(prop.name())); + + if (std::find(prop.types().begin(), prop.types().end(), it->typeOf()) == prop.types().end()) { + auto expected = typeNameList(prop.types()); + auto got = typeName(it->typeOf()); + + throw std::invalid_argument("invalid '{}' property ({} expected, got {})"_format(prop.name(), expected, got)); + } + } + return json::object({}); }
--- a/lib/irccd/command.hpp Thu Jun 09 13:50:55 2016 +0200 +++ b/lib/irccd/command.hpp Fri Jun 10 11:54:13 2016 +0200 @@ -208,6 +208,11 @@ */ class Arg; + /** + * \brief Defines properties that must be available in the JSON request. + */ + class Property; + private: std::string m_name; std::string m_category; @@ -304,6 +309,19 @@ } /** + * Get the properties required in the JSON request. + * + * Default implementation returns empty list. + * + * \return the required properties + * \note Put only **required** properties + */ + virtual std::vector<Property> properties() const + { + return {}; + } + + /** * Get the minimum number of arguments required. * * \return the minimum @@ -495,6 +513,52 @@ } }; +/** + * \brief Property description for JSON request. + */ +class Command::Property { +private: + std::string m_name; + std::vector<json::Type> m_types; + +public: + /** + * Construct the property description. + * + * \pre !name.empty() + * \pre types.size() >= 1 + * \param name the name + * \param types the json types allowed + */ + inline Property(std::string name, std::vector<json::Type> types = { json::Type::String }) noexcept + : m_name(std::move(name)) + , m_types(std::move(types)) + { + assert(!m_name.empty()); + assert(m_types.size() >= 1); + } + + /** + * Get the property name. + * + * \return the name + */ + inline const std::string &name() const noexcept + { + return m_name; + } + + /** + * Get the property types. + * + * \return the types + */ + inline const std::vector<json::Type> &types() const noexcept + { + return m_types; + } +}; + } // !irccd #endif // !IRCCD_COMMAND_HPP
--- a/tests/CMakeLists.txt Thu Jun 09 13:50:55 2016 +0200 +++ b/tests/CMakeLists.txt Fri Jun 10 11:54:13 2016 +0200 @@ -21,6 +21,7 @@ if (WITH_TESTS) # Misc + add_subdirectory(command) add_subdirectory(elapsedtimer) add_subdirectory(logger) add_subdirectory(path)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/command/CMakeLists.txt Fri Jun 10 11:54:13 2016 +0200 @@ -0,0 +1,23 @@ +# +# CMakeLists.txt -- CMake build system for irccd +# +# Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# + +irccd_define_test( + NAME command + SOURCES main.cpp + LIBRARIES libirccd +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/command/main.cpp Fri Jun 10 11:54:13 2016 +0200 @@ -0,0 +1,133 @@ +/* + * main.cpp -- test Command class + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <gtest/gtest.h> + +#include <irccd/command.hpp> + +using namespace irccd; + +class MyCommand : public Command { +public: + MyCommand() + : Command("test", "Test") + { + } + + std::string help() const override + { + return "This is a super command"; + } + + std::vector<Property> properties() const + { + return { + { "b", { json::Type::Boolean } }, + { "i", { json::Type::Int } }, + { "m", { json::Type::Boolean, json::Type::Int, json::Type::String } } + }; + } +}; + +TEST(Properties, valid) +{ + Irccd *irccd = nullptr; + MyCommand cmd; + + ASSERT_NO_THROW(cmd.exec(*irccd, json::object({ + { "b", true }, + { "i", 123 }, + { "m", "abc" } + }))); + + ASSERT_NO_THROW(cmd.exec(*irccd, json::object({ + { "b", true }, + { "i", 123 }, + { "m", 456 } + }))); + + ASSERT_NO_THROW(cmd.exec(*irccd, json::object({ + { "b", true }, + { "i", 123 }, + { "m", "456" } + }))); +} + +TEST(Properties, missingB) +{ + Irccd *irccd = nullptr; + MyCommand cmd; + + ASSERT_THROW(cmd.exec(*irccd, json::object({ + { "i", 123 }, + { "m", "abc" } + })), std::invalid_argument); +} + +TEST(Properties, missingI) +{ + Irccd *irccd = nullptr; + MyCommand cmd; + + ASSERT_THROW(cmd.exec(*irccd, json::object({ + { "b", true }, + { "m", "abc" } + })), std::invalid_argument); +} + +TEST(Properties, missingM) +{ + Irccd *irccd = nullptr; + MyCommand cmd; + + ASSERT_THROW(cmd.exec(*irccd, json::object({ + { "b", true }, + { "i", 123 }, + })), std::invalid_argument); +} + +TEST(Properties, invalidB) +{ + Irccd *irccd = nullptr; + MyCommand cmd; + + ASSERT_THROW(cmd.exec(*irccd, json::object({ + { "b", "fail" }, + { "i", 123 }, + { "m", "abc" } + })), std::invalid_argument); +} + +TEST(Properties, invalidM) +{ + Irccd *irccd = nullptr; + MyCommand cmd; + + ASSERT_THROW(cmd.exec(*irccd, json::object({ + { "b", "fail" }, + { "i", 123 }, + { "m", nullptr } + })), std::invalid_argument); +} + +int main(int argc, char **argv) +{ + testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} \ No newline at end of file