Commit ca03350e authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3793] {create,parse}{Command,Answer} methods updated to new format

 createCommand, parseCommand, createAnswer, parseAnswer updated
 to an new format described here: http://kea.isc.org/wiki/StatsDesign
parent 4ace5ca4
......@@ -17,7 +17,6 @@ lib_LTLIBRARIES = libkea-cc.la
libkea_cc_la_SOURCES = data.cc data.h
libkea_cc_la_SOURCES += logger.cc logger.h
nodist_libkea_cc_la_SOURCES = cc_messages.cc cc_messages.h
libkea_cc_la_SOURCES += proto_defs.cc proto_defs.h
libkea_cc_la_LIBADD = $(top_builddir)/src/lib/log/libkea-log.la
CLEANFILES = *.gcno *.gcda session_config.h cc_messages.cc cc_messages.h \
......@@ -32,11 +31,6 @@ s-messages: cc_messages.mes
$(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/cc/cc_messages.mes
touch $@
BUILT_SOURCES = session_config.h cc_messages.cc cc_messages.h proto_defs.h
# This rule is here, but we added proto_defs.h to the git repo,
# so the script is no longer needed.
#proto_defs.h: $(top_srcdir)/src/lib/util/python/const2hdr.py proto_defs.cc
# $(PYTHON) $(top_srcdir)/src/lib/util/python/const2hdr.py $(srcdir)/proto_defs.cc $@
BUILT_SOURCES = session_config.h cc_messages.cc cc_messages.h
EXTRA_DIST = cc_messages.mes
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC 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 <cc/proto_defs.h>
namespace isc {
namespace cc {
// Aside from defining the values for the C++ library, this file is also
// used as direct input of the generator of the python counterpart. Please,
// keep the syntax here simple and check the generated file
// (lib/python/isc/cc/proto_defs.py) is correct and sane.
// The constants used in the CC protocol
// First the header names
const char* const CC_HEADER_TYPE = "type";
const char* const CC_HEADER_FROM = "from";
const char* const CC_HEADER_TO = "to";
const char* const CC_HEADER_GROUP = "group";
const char* const CC_HEADER_INSTANCE = "instance";
const char* const CC_HEADER_SEQ = "seq";
const char* const CC_HEADER_WANT_ANSWER = "want_answer";
const char* const CC_HEADER_REPLY = "reply";
// The commands in the "type" header
const char* const CC_COMMAND_SEND = "send";
const char* const CC_COMMAND_SUBSCRIBE = "subscribe";
const char* const CC_COMMAND_UNSUBSCRIBE = "unsubscribe";
const char* const CC_COMMAND_GET_LNAME = "getlname";
const char* const CC_COMMAND_PING = "ping";
const char* const CC_COMMAND_PONG = "pong";
const char* const CC_COMMAND_STOP = "stop";
// The wildcards of some headers
const char* const CC_TO_WILDCARD = "*";
const char* const CC_INSTANCE_WILDCARD = "*";
// Prefixes for groups
const char* const CC_GROUP_NOTIFICATION_PREFIX = "notifications/";
// Reply codes
const int CC_REPLY_NO_RECPT = -1;
const int CC_REPLY_SUCCESS = 0;
// Payload in the message
const char *const CC_PAYLOAD_LNAME = "lname";
const char *const CC_PAYLOAD_RESULT = "result";
const char *const CC_PAYLOAD_COMMAND = "command";
const char *const CC_PAYLOAD_NOTIFICATION = "notification";
}
}
// This file is generated from ./proto_defs.cc
// by the const2hdr.py script.
// Do not edit, all changes will be lost.
// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
//
// 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 ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef KEA_COMMON_DEFS_H
#define KEA_COMMON_DEFS_H
// \file proto_defs.h
// \brief Common shared constants
// This file contains common definitions of constasts used across the sources.
// It includes, but is not limited to the definitions of messages sent from
// one process to another. Since the names should be self-explanatory and
// the variables here are used mostly to synchronize the same values across
// multiple programs, separate documentation for each variable is not provided.
namespace isc {
namespace cc {
// Aside from defining the values for the C++ library, this file is also
// used as direct input of the generator of the python counterpart. Please,
// keep the syntax here simple and check the generated file
// (lib/python/isc/cc/proto_defs.py) is correct and sane.
// The constants used in the CC protocol
// First the header names
extern const char* const CC_HEADER_TYPE;
extern const char* const CC_HEADER_FROM;
extern const char* const CC_HEADER_TO;
extern const char* const CC_HEADER_GROUP;
extern const char* const CC_HEADER_INSTANCE;
extern const char* const CC_HEADER_SEQ;
extern const char* const CC_HEADER_WANT_ANSWER;
extern const char* const CC_HEADER_REPLY;
// The commands in the "type" header
extern const char* const CC_COMMAND_SEND;
extern const char* const CC_COMMAND_SUBSCRIBE;
extern const char* const CC_COMMAND_UNSUBSCRIBE;
extern const char* const CC_COMMAND_GET_LNAME;
extern const char* const CC_COMMAND_PING;
extern const char* const CC_COMMAND_PONG;
extern const char* const CC_COMMAND_STOP;
// The wildcards of some headers
extern const char* const CC_TO_WILDCARD;
extern const char* const CC_INSTANCE_WILDCARD;
// Prefixes for groups
extern const char* const CC_GROUP_NOTIFICATION_PREFIX;
// Reply codes
extern const int CC_REPLY_NO_RECPT;
extern const int CC_REPLY_SUCCESS;
// Payload in the message
extern const char *const CC_PAYLOAD_LNAME;
extern const char *const CC_PAYLOAD_RESULT;
extern const char *const CC_PAYLOAD_COMMAND;
extern const char *const CC_PAYLOAD_NOTIFICATION;
}
}
#endif
......@@ -53,70 +53,78 @@ using isc::data::JSONError;
namespace isc {
namespace config {
/// Creates a standard config/command protocol answer message
ConstElementPtr
createAnswer() {
ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
answer_content->add(Element::create(isc::cc::CC_REPLY_SUCCESS));
answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
const char *CONTROL_COMMAND="command";
return (answer);
}
const char *CONTROL_RESULT="result";
const char *CONTROL_TEXT="text";
const char *CONTROL_ARGUMENTS="arguments";
// Full version, with status, text and arguments
ConstElementPtr
createAnswer(const int rcode, ConstElementPtr arg) {
if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
createAnswer(const int status_code, const std::string& text,
const ConstElementPtr& arg) {
if (status_code != 0 && text.empty()) {
isc_throw(CtrlChannelError, "Text has to be provided for status_code != 0");
}
ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
answer_content->add(Element::create(rcode));
answer_content->add(arg);
answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
ElementPtr result = Element::create(status_code);
answer->set(CONTROL_RESULT, result);
if (!text.empty()) {
answer->set(CONTROL_TEXT, Element::create(text));
}
if (arg) {
answer->set(CONTROL_ARGUMENTS, arg);
}
return (answer);
}
ConstElementPtr
createAnswer(const int rcode, const std::string& arg) {
ElementPtr answer = Element::createMap();
ElementPtr answer_content = Element::createList();
answer_content->add(Element::create(rcode));
answer_content->add(Element::create(arg));
answer->set(isc::cc::CC_PAYLOAD_RESULT, answer_content);
createAnswer() {
return (createAnswer(0, string(""), ConstElementPtr()));
}
return (answer);
ConstElementPtr
createAnswer(const int status_code, const std::string& text) {
return (createAnswer(status_code, text, ElementPtr()));
}
ConstElementPtr
parseAnswer(int &rcode, ConstElementPtr msg) {
if (msg &&
msg->getType() == Element::map &&
msg->contains(isc::cc::CC_PAYLOAD_RESULT)) {
ConstElementPtr result = msg->get(isc::cc::CC_PAYLOAD_RESULT);
if (result->getType() != Element::list) {
isc_throw(CCSessionError, "Result element in answer message is not a list");
} else if (result->get(0)->getType() != Element::integer) {
isc_throw(CCSessionError, "First element of result is not an rcode in answer message");
}
rcode = result->get(0)->intValue();
if (result->size() > 1) {
if (rcode == 0 || result->get(1)->getType() == Element::string) {
return (result->get(1));
} else {
isc_throw(CCSessionError, "Error description in result with rcode != 0 is not a string");
}
} else {
if (rcode == 0) {
return (ElementPtr());
} else {
isc_throw(CCSessionError, "Result with rcode != 0 does not have an error description");
}
}
} else {
isc_throw(CCSessionError, "No result part in answer message");
createAnswer(const int status_code, const ConstElementPtr& arg) {
return (createAnswer(status_code, "", arg));
}
ConstElementPtr
parseAnswer(int &rcode, const ConstElementPtr& msg) {
if (!msg) {
isc_throw(CtrlChannelError, "No answer specified");
}
if (msg->getType() != Element::map) {
isc_throw(CtrlChannelError,
"Invalid answer Element specified, expected map");
}
if (!msg->contains(CONTROL_RESULT)) {
isc_throw(CtrlChannelError,
"Invalid answer specified, does not contain mandatory 'result'");
}
ConstElementPtr result = msg->get(CONTROL_RESULT);
if (result->getType() != Element::integer) {
isc_throw(CtrlChannelError,
"Result element in answer message is not a string");
}
rcode = result->intValue();
// If there are arguments, return them.
ConstElementPtr args = msg->get(CONTROL_ARGUMENTS);
if (args) {
return (args);
}
// There are no arguments, let's try to return just the text status
return (msg->get(CONTROL_TEXT));
}
ConstElementPtr
......@@ -126,37 +134,37 @@ createCommand(const std::string& command) {
ConstElementPtr
createCommand(const std::string& command, ConstElementPtr arg) {
ElementPtr cmd = Element::createMap();
ElementPtr cmd_parts = Element::createList();
cmd_parts->add(Element::create(command));
ElementPtr query = Element::createMap();
ElementPtr cmd = Element::create(command);
query->set(CONTROL_COMMAND, cmd);
if (arg) {
cmd_parts->add(arg);
query->set(CONTROL_ARGUMENTS, arg);
}
cmd->set(isc::cc::CC_PAYLOAD_COMMAND, cmd_parts);
return (cmd);
return (query);
}
std::string
parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
if (command &&
command->getType() == Element::map &&
command->contains(isc::cc::CC_PAYLOAD_COMMAND)) {
ConstElementPtr cmd = command->get(isc::cc::CC_PAYLOAD_COMMAND);
if (cmd->getType() == Element::list &&
!cmd->empty() &&
cmd->get(0)->getType() == Element::string) {
if (cmd->size() > 1) {
arg = cmd->get(1);
} else {
arg = Element::createMap();
}
return (cmd->get(0)->stringValue());
} else {
isc_throw(CCSessionError, "Command part in command message missing, empty, or not a list");
}
} else {
isc_throw(CCSessionError, "Command Element empty or not a map with \"command\"");
if (!command) {
isc_throw(CtrlChannelError, "No command specified");
}
if (command->getType() != Element::map) {
isc_throw(CtrlChannelError, "Invalid command Element specified, expected map");
}
if (!command->contains(CONTROL_COMMAND)) {
isc_throw(CtrlChannelError,
"Invalid answer specified, does not contain mandatory 'command'");
}
ConstElementPtr cmd = command->get(CONTROL_COMMAND);
if (cmd->getType() != Element::string) {
isc_throw(CtrlChannelError,
"'command' element in command message is not a string");
}
arg = command->get(CONTROL_ARGUMENTS);
return (cmd->stringValue());
}
namespace {
......
......@@ -19,7 +19,6 @@
#include <config/module_spec.h>
#include <cc/data.h>
#include <cc/proto_defs.h>
#include <string>
#include <list>
......@@ -28,112 +27,97 @@
namespace isc {
namespace config {
///
/// \brief Creates a standard config/command level success answer message
/// (i.e. of the form { "result": [ 0 ] }
/// \return Standard command/config success answer message
extern const char *CONTROL_COMMAND;
extern const char *CONTROL_RESULT;
extern const char *CONTROL_TEXT;
extern const char *CONTROL_ARGUMENTS;
const int CONTROL_RESULT_SUCCESS = 0;
const int CONTROL_RESULT_ERROR = 1;
/// @brief Creates a standard config/command level success answer message
/// (i.e. of the form { "result": 0 }
/// @return Standard command/config success answer message
isc::data::ConstElementPtr createAnswer();
/// @brief Creates a standard config/command level answer message
/// (i.e. of the form { "result": 1, "text": "Invalid command received" }
///
/// \brief Creates a standard config/command level answer message
/// (i.e. of the form { "result": [ rcode, arg ] }
/// If rcode != 0, arg must be a StringElement
///
/// \param rcode The return code (0 for success)
/// \param arg For rcode == 0, this is an optional argument of any
/// Element type. For rcode == 1, this argument is mandatory,
/// and must be a StringElement containing an error description
/// \return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int rcode,
isc::data::ConstElementPtr arg);
/// @param status_code The return code (0 for success)
/// @param status_text A string to put into the "text" argument
/// @return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int status_code,
const std::string& status_text);
/// @brief Creates a standard config/command level answer message
/// (i.e. of the form { "result": status_code, "arguments": arg }
///
/// \brief Creates a standard config/command level answer message
/// (i.e. of the form { "result": [ rcode, arg ] }
/// @param status_code The return code (0 for success)
/// @param status_text A string to put into the "text" argument
/// @return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int status_code,
const isc::data::ConstElementPtr& arg);
/// @brief Creates a standard config/command level answer message
/// (i.e. of the form { "result": X, "[ rcode, arg ] }
/// If rcode != 0, arg must be a StringElement
///
/// \param rcode The return code (0 for success)
/// \param arg A string to put into the StringElement argument
/// \return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int rcode,
const std::string& arg);
/// @param status_code The return code (0 for success)
/// @param arg For status_code == 0, this is an optional argument of any
/// Element type. For status_code == 1, this argument is mandatory,
/// and may be any type of ElementPtr.
/// @return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int status_code,
const std::string& status,
const isc::data::ConstElementPtr& arg);
/// @brief Parses a standard config/command level answer message.
///
/// Parses a standard config/command level answer message
///
/// \param rcode This value will be set to the return code contained in
/// @param status_code This value will be set to the return code contained in
/// the message
/// \param msg The message to parse
/// \return The optional argument in the message, or an empty ElementPtr
/// @param msg The message to parse
/// @return The optional argument in the message, or an empty ElementPtr
/// if there was no argument. If rcode != 0, this contains a
/// StringElement with the error description.
isc::data::ConstElementPtr parseAnswer(int &rcode,
isc::data::ConstElementPtr msg);
isc::data::ConstElementPtr parseAnswer(int &status_code,
const isc::data::ConstElementPtr& msg);
/// @brief Creates a standard config/command command message with no
/// argument (of the form { "command": "my_command" }
///
/// \brief Creates a standard config/command command message with no
/// argument (of the form { "command": [ "my_command" ] }
///
/// \param command The command string
/// \return The created message
/// @param command The command string
/// @return The created message
isc::data::ConstElementPtr createCommand(const std::string& command);
/// @brief Creates a standard config/command command message with the
/// given argument (of the form { "command": "my_command", "arguments": arg }
///
/// \brief Creates a standard config/command command message with the
/// given argument (of the form { "command": [ "my_command", arg ] }
///
/// \param command The command string
/// \param arg The optional argument for the command. This can be of
/// @param command The command string
/// @param arg The optional argument for the command. This can be of
/// any Element type, but it should conform to the .spec file.
/// \return The created message
/// @return The created message
isc::data::ConstElementPtr createCommand(const std::string& command,
isc::data::ConstElementPtr arg);
///
/// \brief Parses the given command into a string containing the actual
/// @brief Parses the given command into a string containing the actual
/// command and an ElementPtr containing the optional argument.
///
/// Raises a CCSessionError if this is not a well-formed command
/// @throw Raises a CtrlChannelError if this is not a well-formed command
///
/// Example code: (command_message is a ConstElementPtr that is
/// passed here)
/// \code
/// ElementPtr command_message = Element::fromJSON("{ \"command\": [\"foo\", { \"bar\": 123 } ] }");
/// try {
/// ConstElementPtr args;
/// std::string command_str = parseCommand(args, command_message);
/// if (command_str == "foo") {
/// std::cout << "The command 'foo' was given" << std::endl;
/// if (args->contains("bar")) {
/// std::cout << "It had argument name 'bar' set, which has"
/// << "value "
/// << args->get("bar")->intValue();
/// }
/// } else {
/// std::cout << "Unknown command '" << command_str << std::endl;
/// }
/// } catch (CCSessionError cse) {
/// std::cerr << "Bad command in CC Session: "
/// << cse.what() << std::endl;
/// }
/// \endcode
///
/// \param arg This value will be set to the ElementPtr pointing to
/// @param arg This value will be set to the ElementPtr pointing to
/// the argument, or to an empty Map (ElementPtr) if there was none.
/// \param command The command message containing the command (as made
/// @param command The command message containing the command (as made
/// by createCommand()
/// \return The command name
std::string parseCommand(isc::data::ConstElementPtr& arg,
isc::data::ConstElementPtr command);
///
/// \brief A standard cc session exception that is thrown if a function
/// @brief A standard control channel exception that is thrown if a function
/// is there is a problem with one of the messages
///
// todo: include types and called function in the exception
class CCSessionError : public isc::Exception {
class CtrlChannelError : public isc::Exception {
public:
CCSessionError(const char* file, size_t line, const char* what) :
CtrlChannelError(const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) {}
};
......
......@@ -24,7 +24,6 @@
using namespace isc::data;
using namespace isc::config;
using namespace isc::cc;
using namespace std;
namespace {
......@@ -50,16 +49,17 @@ protected:
TEST_F(CCSessionTest, createAnswer) {
ConstElementPtr answer;
answer = createAnswer();
EXPECT_EQ("{ \"result\": [ 0 ] }", answer->str());
EXPECT_EQ("{ \"result\": 0 }", answer->str());
answer = createAnswer(1, "error");
EXPECT_EQ("{ \"result\": [ 1, \"error\" ] }", answer->str());
EXPECT_EQ("{ \"result\": 1, \"text\": \"error\" }", answer->str());
EXPECT_THROW(createAnswer(1, ElementPtr()), CCSessionError);
EXPECT_THROW(createAnswer(1, Element::create(1)), CCSessionError);
EXPECT_THROW(createAnswer(1, ElementPtr()), CtrlChannelError);
EXPECT_THROW(createAnswer(1, Element::create(1)), CtrlChannelError);
ConstElementPtr arg = el("[ \"just\", \"some\", \"data\" ]");
answer = createAnswer(0, arg);
EXPECT_EQ("{ \"result\": [ 0, [ \"just\", \"some\", \"data\" ] ] }", answer->str());
EXPECT_EQ("{ \"arguments\": [ \"just\", \"some\", \"data\" ], \"result\": 0 }",
answer->str());
}
TEST_F(CCSessionTest, parseAnswer) {
......@@ -67,27 +67,26 @@ TEST_F(CCSessionTest, parseAnswer) {
ConstElementPtr arg;
int rcode;
EXPECT_THROW(parseAnswer(rcode, ElementPtr()), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("1")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("[]")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("{ }")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"something\": 1 }")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": 0 }")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": 1 }")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1 ] }")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1, 1 ] }")), CCSessionError);
EXPECT_THROW(parseAnswer(rcode, ElementPtr()), CtrlChannelError);
EXPECT_THROW(parseAnswer(rcode, el("1")), CtrlChannelError);
EXPECT_THROW(parseAnswer(rcode, el("[]")), CtrlChannelError);
EXPECT_THROW(parseAnswer(rcode, el("{ }")), CtrlChannelError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"something\": 1 }")), CtrlChannelError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 0 ] }")), CtrlChannelError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1 ] }")), CtrlChannelError);
EXPECT_THROW(parseAnswer(rcode, el("{ \"result\": [ 1, 1 ] }")), CtrlChannelError);
answer = el("{ \"result\": [ 0 ] }");
answer = el("{ \"result\": 0 }");
arg = parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
EXPECT_TRUE(isNull(arg));
answer = el("{ \"result\": [ 1, \"error\"] }");
answer = el("{ \"result\": 1, \"text\": \"error\" }");
arg = parseAnswer(rcode, answer);
EXPECT_EQ(1, rcode);
EXPECT_EQ("error", arg->stringValue());
answer = el("{ \"result\": [ 0, [ \"just\", \"some\", \"data\" ] ] }");
answer = el("{ \"result\": 0, \"arguments\": [ \"just\", \"some\", \"data\" ] }");
arg = parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
EXPECT_EQ("[ \"just\", \"some\", \"data\" ]", arg->str());
......@@ -98,19 +97,22 @@ TEST_F(CCSessionTest, createCommand) {
ConstElementPtr arg;
command = createCommand("my_command");
ASSERT_EQ("{ \"command\": [ \"my_command\" ] }", command->str());
ASSERT_EQ("{ \"command\": \"my_command\" }", command->str());
arg = el("1");
command = createCommand("my_command", arg);
ASSERT_EQ("{ \"command\": [ \"my_command\", 1 ] }", command->str());
ASSERT_EQ("{ \"arguments\": 1, \"command\": \"my_command\" }",
command->str());
arg = el("[ \"a\", \"b\" ]");