Allow immediately executing RPC with no input
Story: https://tree.taiga.io/project/jktjkt-netconf-cli/us/189
Change-Id: I876437b798e995c4484e97b409117a2bb207e864
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 53dda3b..94cd1ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -347,7 +347,7 @@
cli_test(path_utils)
target_link_libraries(test_path_utils path)
cli_test(keyvalue_completion)
- cli_test(prepare)
+ cli_test(parser_rpc)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/tests/init_datastore.bash.in
${CMAKE_CURRENT_BINARY_DIR}/init_datastore.bash @ONLY)
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 5b99c6a..fa2b56c 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -51,3 +51,8 @@
{
return this->m_path == other.m_path;
}
+
+bool exec_::operator==(const exec_& other) const
+{
+ return this->m_path == other.m_path;
+}
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index db78611..9ec2740 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -258,14 +258,20 @@
static constexpr auto name = "exec";
static constexpr auto shortHelp = "exec - Execute RPC/action.";
static constexpr auto longHelp = R"(
- exec
+ exec [path]
This command executes the RPC/action you have previously initiated via the
`prepare` command.
+ If the RPC/action has no input parameters, it can be directly execute via
+ `exec` without usgin `prepare`.
+
Usage:
- /> exec)";
+ /> exec
+ /> exec /mod:myRpc)";
bool operator==(const exec_& other) const;
+
+ boost::optional<dataPath_> m_path;
};
struct cancel_ : x3::position_tagged {
@@ -332,3 +338,4 @@
BOOST_FUSION_ADAPT_STRUCT(move_, m_source, m_destination)
BOOST_FUSION_ADAPT_STRUCT(dump_, m_format)
BOOST_FUSION_ADAPT_STRUCT(prepare_, m_path)
+BOOST_FUSION_ADAPT_STRUCT(exec_, m_path)
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 02da179..2ff6e7a 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -259,10 +259,10 @@
} const dump_args;
auto const prepare_def =
- prepare_::name > space_separator > rpcActionPath;
+ prepare_::name > space_separator > as<dataPath_>[RpcActionPath<AllowInput::Yes>{}];
auto const exec_def =
- exec_::name >> x3::attr(exec_{});
+ exec_::name > -(space_separator > as<dataPath_>[RpcActionPath<AllowInput::No>{}]);
auto const cancel_def =
cancel_::name >> x3::attr(cancel_{});
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index acb3f0b..ac958dc 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -220,9 +220,12 @@
m_parser.changeNode(prepare.m_path);
}
-void Interpreter::operator()(const exec_&) const
+void Interpreter::operator()(const exec_& exec) const
{
m_parser.changeNode({Scope::Absolute, {}});
+ if (exec.m_path) {
+ m_datastore.initiate(pathToString(toCanonicalPath(*exec.m_path)));
+ }
auto output = m_datastore.execute();
std::cout << "RPC/action output:\n";
printTree(output);
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 58d174e..c3df931 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -428,12 +428,28 @@
} const writableLeafPath;
-struct RpcActionPath : x3::parser<RpcActionPath> {
+enum class AllowInput {
+ Yes,
+ No
+};
+
+template <AllowInput ALLOW_INPUT>
+struct RpcActionPath : x3::parser<RpcActionPath<ALLOW_INPUT>> {
using attribute_type = dataPath_;
template <typename It, typename Ctx, typename RCtx, typename Attr>
static bool parse(It& begin, It end, Ctx const& ctx, RCtx& rctx, Attr& attr)
{
- bool res = dataPath.parse(begin, end, ctx, rctx, attr);
+ auto grammar = PathParser<PathParserMode::DataPath, CompletionMode::Data>{[] (const Schema& schema, const std::string& path) {
+ if constexpr (ALLOW_INPUT == AllowInput::No) {
+ auto nodeType = schema.nodeType(path);
+ if (nodeType == yang::NodeTypes::Rpc || nodeType == yang::NodeTypes::Action) {
+ return !schema.hasInputNodes(path);
+ }
+ }
+
+ return true;
+ }};
+ bool res = grammar.parse(begin, end, ctx, rctx, attr);
if (!res) {
return false;
}
@@ -449,8 +465,6 @@
}
};
-auto const rpcActionPath = as<dataPath_>[RpcActionPath()];
-
auto const noRpcOrAction = [](const Schema& schema, const std::string& path) {
auto nodeType = schema.nodeType(path);
return nodeType != yang::NodeTypes::Rpc && nodeType != yang::NodeTypes::Action;
diff --git a/src/schema.hpp b/src/schema.hpp
index 00586b5..6955686 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -68,6 +68,7 @@
[[nodiscard]] virtual std::string leafrefPath(const std::string& leafrefPath) const = 0;
[[nodiscard]] virtual std::optional<std::string> description(const std::string& location) const = 0;
[[nodiscard]] virtual yang::Status status(const std::string& location) const = 0;
+ [[nodiscard]] virtual bool hasInputNodes(const std::string& path) const = 0;
[[nodiscard]] virtual std::set<ModuleNodePair> availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const = 0;
};
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index a75f471..c1d4417 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -41,6 +41,8 @@
//create a new set of children for the new node
std::string key = joinPaths(location, name);
m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
+ m_nodes.emplace(joinPaths(key, "input"), std::unordered_map<std::string, NodeInfo>());
+ m_nodes.emplace(joinPaths(key, "output"), std::unordered_map<std::string, NodeInfo>());
}
void StaticSchema::addAction(const std::string& location, const std::string& name)
@@ -272,6 +274,15 @@
throw std::runtime_error{"Internal error: StaticSchema::status(std::string) not implemented. The tests should not have called this overload."};
}
+bool StaticSchema::hasInputNodes(const std::string& path) const
+{
+ if (nodeType(path) != yang::NodeTypes::Action && nodeType(path) != yang::NodeTypes::Rpc) {
+ throw std::logic_error("StaticSchema::hasInputNodes called with non-RPC/action path");
+ }
+
+ return m_nodes.at(joinPaths(path, "input")).size() != 0;
+}
+
yang::NodeTypes StaticSchema::nodeType(const std::string& path) const
{
auto locationString = stripLastNodeFromPath(path);
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 3009d7e..0b9e59e 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -80,6 +80,7 @@
std::set<ModuleNodePair> availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const override;
std::optional<std::string> description(const std::string& path) const override;
yang::Status status(const std::string& location) const override;
+ bool hasInputNodes(const std::string& path) const override;
/** A helper for making tests a little bit easier. It returns all
* identities which are based on the argument passed and which can then be
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 7f27711..d6c0bd8 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -485,6 +485,17 @@
}
}
+bool YangSchema::hasInputNodes(const std::string& path) const
+{
+ auto node = getSchemaNode(path.c_str());
+ if (auto type = node->nodetype(); type != LYS_ACTION && type != LYS_RPC) {
+ throw std::logic_error("StaticSchema::hasInputNodes called with non-RPC/action path");
+ }
+
+ // The first child gives the /input node and then I check whether it has a child.
+ return node->child()->child().get();
+}
+
bool YangSchema::isConfig(const std::string& path) const
{
auto node = getSchemaNode(path.c_str());
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index 632f538..dc45097 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -47,6 +47,7 @@
[[nodiscard]] std::set<ModuleNodePair> availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const override;
[[nodiscard]] std::optional<std::string> description(const std::string& path) const override;
[[nodiscard]] yang::Status status(const std::string& location) const override;
+ [[nodiscard]] bool hasInputNodes(const std::string& path) const override;
void registerModuleCallback(const std::function<std::string(const char*, const char*, const char*, const char*)>& clb);
diff --git a/tests/interpreter.cpp b/tests/interpreter.cpp
index c6d7c9c..4ef0a96 100644
--- a/tests/interpreter.cpp
+++ b/tests/interpreter.cpp
@@ -32,6 +32,7 @@
MAKE_CONST_MOCK1(nodeType, yang::NodeTypes(const std::string&), override);
MAKE_CONST_MOCK2(nodeType, yang::NodeTypes(const schemaPath_&, const ModuleNodePair&), override);
IMPLEMENT_CONST_MOCK1(status);
+ IMPLEMENT_CONST_MOCK1(hasInputNodes);
};
TEST_CASE("interpreter tests")
diff --git a/tests/parser_rpc.cpp b/tests/parser_rpc.cpp
new file mode 100644
index 0000000..c8458d8
--- /dev/null
+++ b/tests/parser_rpc.cpp
@@ -0,0 +1,70 @@
+
+/*
+ * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
+ * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include "trompeloeil_doctest.hpp"
+#include "parser.hpp"
+#include "pretty_printers.hpp"
+#include "static_schema.hpp"
+
+TEST_CASE("rpc")
+{
+ auto schema = std::make_shared<StaticSchema>();
+ schema->addModule("example");
+ schema->addRpc("/", "example:fire");
+ schema->addRpc("/", "example:shutdown");
+ schema->addLeaf("/example:shutdown/input", "example:interface", yang::String{});
+ schema->addList("/", "example:port", {"name"});
+ schema->addLeaf("/example:port", "example:name", yang::String{});
+ schema->addAction("/example:port", "example:shutdown");
+ Parser parser(schema);
+ std::string input;
+ std::ostringstream errorStream;
+ SECTION("with prepare")
+ {
+ prepare_ prepExpected;
+ prepExpected.m_path.m_scope = Scope::Relative;
+ SECTION("rpc")
+ {
+ input = "prepare example:fire";
+ prepExpected.m_path.m_nodes.emplace_back(module_{"example"}, rpcNode_{"fire"});
+ }
+
+ SECTION("action")
+ {
+ input = "prepare example:port[name='eth0']/shutdown";
+ prepExpected.m_path.m_nodes.emplace_back(module_{"example"}, listElement_{"port", {{"name", std::string{"eth0"}}}});
+ prepExpected.m_path.m_nodes.emplace_back(actionNode_{"shutdown"});
+ }
+
+ command_ command = parser.parseCommand(input, errorStream);
+ auto lol = boost::get<prepare_>(command);
+ REQUIRE(boost::get<prepare_>(command) == prepExpected);
+ }
+
+ SECTION("direct exec")
+ {
+ exec_ execExpected;
+ execExpected.m_path = dataPath_{};
+ execExpected.m_path->m_scope = Scope::Relative;
+ SECTION("the rpc has no arguments")
+ {
+ input = "exec example:fire";
+ execExpected.m_path->m_nodes.emplace_back(module_{"example"}, rpcNode_{"fire"});
+ command_ command = parser.parseCommand(input, errorStream);
+ auto lol = boost::get<exec_>(command);
+ REQUIRE(boost::get<exec_>(command) == execExpected);
+ }
+
+ SECTION("the rpc has arguments")
+ {
+ input = "exec example:shutdown";
+ REQUIRE_THROWS_AS(parser.parseCommand(input, errorStream), InvalidCommandException);
+ }
+ }
+}
diff --git a/tests/prepare.cpp b/tests/prepare.cpp
deleted file mode 100644
index d56a488..0000000
--- a/tests/prepare.cpp
+++ /dev/null
@@ -1,45 +0,0 @@
-
-/*
- * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
- * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
- *
- * Written by Václav Kubernát <kubervac@fit.cvut.cz>
- *
-*/
-
-#include "trompeloeil_doctest.hpp"
-#include "parser.hpp"
-#include "pretty_printers.hpp"
-#include "static_schema.hpp"
-
-TEST_CASE("prepare command")
-{
- auto schema = std::make_shared<StaticSchema>();
- schema->addModule("example");
- schema->addRpc("/", "example:fire");
- schema->addList("/", "example:port", {"name"});
- schema->addLeaf("/example:port", "example:name", yang::String{});
- schema->addAction("/example:port", "example:shutdown");
- Parser parser(schema);
- std::string input;
- std::ostringstream errorStream;
- prepare_ expected;
- expected.m_path.m_scope = Scope::Relative;
- SECTION("rpc")
- {
- input = "prepare example:fire";
- expected.m_path.m_nodes.emplace_back(module_{"example"}, rpcNode_{"fire"});
- }
-
- SECTION("action")
- {
- input = "prepare example:port[name='eth0']/shutdown";
- expected.m_path.m_nodes.emplace_back(module_{"example"}, listElement_{"port", {{"name", std::string{"eth0"}}}});
- expected.m_path.m_nodes.emplace_back(actionNode_{"shutdown"});
- }
-
- command_ command = parser.parseCommand(input, errorStream);
- REQUIRE(command.type() == typeid(prepare_));
- auto lol = boost::get<prepare_>(command);
- REQUIRE(boost::get<prepare_>(command) == expected);
-}
diff --git a/tests/yang.cpp b/tests/yang.cpp
index 1ec6507..c5ae62a 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -317,6 +317,22 @@
rpc myRpc {}
+ rpc rpcOneOutput {
+ output {
+ leaf ahoj {
+ type string;
+ }
+ }
+ }
+
+ rpc rpcOneInput {
+ input {
+ leaf ahoj {
+ type string;
+ }
+ }
+ }
+
leaf numberOrString {
type union {
type int32;
@@ -776,6 +792,8 @@
{"example-schema"s, "obsoleteLeafWithDeprecatedType"},
{"example-schema"s, "obsoleteLeafWithObsoleteType"},
{"example-schema"s, "myRpc"},
+ {"example-schema"s, "rpcOneOutput"},
+ {"example-schema"s, "rpcOneInput"},
{"example-schema"s, "systemStats"},
{"example-schema"s, "dummyLeaf"},
{"example-schema"s, "addresses"},
@@ -855,6 +873,8 @@
{"example-schema"s, "length"},
{"example-schema"s, "loopback"},
{"example-schema"s, "myRpc"},
+ {"example-schema"s, "rpcOneOutput"},
+ {"example-schema"s, "rpcOneInput"},
{"example-schema"s, "numberOrString"},
{"example-schema"s, "obsoleteLeaf"},
{"example-schema"s, "obsoleteLeafWithDeprecatedType"},
@@ -920,6 +940,14 @@
{boost::none, "/example-schema:myRpc"},
{boost::none, "/example-schema:myRpc/input"},
{boost::none, "/example-schema:myRpc/output"},
+ {boost::none, "/example-schema:rpcOneOutput"},
+ {boost::none, "/example-schema:rpcOneOutput/input"},
+ {boost::none, "/example-schema:rpcOneOutput/output"},
+ {boost::none, "/example-schema:rpcOneOutput/output/ahoj"},
+ {boost::none, "/example-schema:rpcOneInput"},
+ {boost::none, "/example-schema:rpcOneInput/input"},
+ {boost::none, "/example-schema:rpcOneInput/input/ahoj"},
+ {boost::none, "/example-schema:rpcOneInput/output"},
{boost::none, "/example-schema:numberOrString"},
{boost::none, "/example-schema:obsoleteLeaf"},
{boost::none, "/example-schema:obsoleteLeafWithDeprecatedType"},
@@ -1135,6 +1163,30 @@
REQUIRE(ys.dataPathToSchemaPath("/example-schema:portSettings[port='eth0']") == "/example-schema:portSettings");
REQUIRE(ys.dataPathToSchemaPath("/example-schema:portSettings[port='eth0']/shutdown") == "/example-schema:portSettings/shutdown");
}
+
+ SECTION("has input nodes")
+ {
+ bool expected;
+ SECTION("example-schema:myRpc")
+ {
+ path.m_nodes.emplace_back(module_{"example-schema"}, rpcNode_{"myRpc"});
+ expected = false;
+ }
+
+ SECTION("example-schema:rpcOneInput")
+ {
+ path.m_nodes.emplace_back(module_{"example-schema"}, rpcNode_{"rpcOneInput"});
+ expected = true;
+ }
+
+ SECTION("example-schema:rpcOneOutput")
+ {
+ path.m_nodes.emplace_back(module_{"example-schema"}, rpcNode_{"rpcOneOutput"});
+ expected = false;
+ }
+
+ REQUIRE(ys.hasInputNodes(pathToSchemaString(path, Prefixes::WhenNeeded)) == expected);
+ }
}
SECTION("negative")