Add parser support for YANG actions
Change-Id: I9e96ae15cee3f990dfa5ec3fac433faf71348e11
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index a594228..b863640 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -219,17 +219,17 @@
DataFormat m_format;
};
-struct rpc_ : x3::position_tagged {
- static constexpr auto name = "rpc";
- static constexpr auto shortHelp = "rpc - initiate RPC";
+struct prepare_ : x3::position_tagged {
+ static constexpr auto name = "prepare";
+ static constexpr auto shortHelp = "prepare - initiate RPC or action";
static constexpr auto longHelp = R"(
- rpc <rpc-path>
+ prepare <path-to-rpc-or-action>
This command puts you into a mode where you can set your input parameters.
Usage:
- /> rpc <path-to-rpc>)";
- bool operator==(const rpc_& other) const;
+ /> prepare <path-to-rpc-or-action>)";
+ bool operator==(const prepare_& other) const;
dataPath_ m_path;
};
@@ -261,7 +261,7 @@
};
struct help_;
-using CommandTypes = boost::mpl::vector<cancel_, cd_, commit_, copy_, create_, delete_, describe_, discard_, dump_, exec_, get_, help_, ls_, move_, rpc_, set_>;
+using CommandTypes = boost::mpl::vector<cancel_, cd_, commit_, copy_, create_, delete_, describe_, discard_, dump_, exec_, get_, help_, ls_, move_, prepare_, set_>;
struct help_ : x3::position_tagged {
static constexpr auto name = "help";
static constexpr auto shortHelp = "help - Print help for commands.";
@@ -309,4 +309,4 @@
BOOST_FUSION_ADAPT_STRUCT(copy_, m_source, m_destination)
BOOST_FUSION_ADAPT_STRUCT(move_, m_source, m_destination)
BOOST_FUSION_ADAPT_STRUCT(dump_, m_format)
-BOOST_FUSION_ADAPT_STRUCT(rpc_, m_path)
+BOOST_FUSION_ADAPT_STRUCT(prepare_, m_path)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 87cacd1..e583207 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -232,6 +232,8 @@
struct rpcPath_class;
+struct actionPath_class;
+
struct cdPath_class;
struct set_class {
@@ -260,7 +262,9 @@
struct dump_class;
-struct rpc_class;
+struct prepare_class;
+
+struct action_class;
struct exec_class;
diff --git a/src/ast_path.cpp b/src/ast_path.cpp
index e8bfbcf..b9f7ae4 100644
--- a/src/ast_path.cpp
+++ b/src/ast_path.cpp
@@ -115,6 +115,11 @@
return this->m_name == other.m_name;
}
+bool actionNode_::operator==(const actionNode_& other) const
+{
+ return this->m_name == other.m_name;
+}
+
list_::list_(const std::string& listName)
: m_name(listName)
{
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index d02be60..8a86181 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -87,9 +87,15 @@
std::string m_name;
};
+struct actionNode_ {
+ bool operator==(const actionNode_& other) const;
+
+ std::string m_name;
+};
+
struct schemaNode_ {
boost::optional<module_> m_prefix;
- std::variant<container_, list_, nodeup_, leaf_, leafList_, rpcNode_> m_suffix;
+ std::variant<container_, list_, nodeup_, leaf_, leafList_, rpcNode_, actionNode_> m_suffix;
schemaNode_();
schemaNode_(decltype(m_suffix) node);
@@ -99,7 +105,7 @@
struct dataNode_ {
boost::optional<module_> m_prefix;
- std::variant<container_, listElement_, nodeup_, leaf_, leafListElement_, leafList_, list_, rpcNode_> m_suffix;
+ std::variant<container_, listElement_, nodeup_, leaf_, leafListElement_, leafList_, list_, rpcNode_, actionNode_> m_suffix;
dataNode_();
dataNode_(decltype(m_suffix) node);
diff --git a/src/grammars.hpp b/src/grammars.hpp
index ee808d5..86dda33 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -29,7 +29,7 @@
x3::rule<copy_class, copy_> const copy = "copy";
x3::rule<move_class, move_> const move = "move";
x3::rule<dump_class, dump_> const dump = "dump";
-x3::rule<rpc_class, rpc_> const rpc = "rpc";
+x3::rule<prepare_class, prepare_> const prepare = "prepare";
x3::rule<exec_class, exec_> const exec = "exec";
x3::rule<cancel_class, cancel_> const cancel = "cancel";
x3::rule<command_class, command_> const command = "command";
@@ -258,8 +258,8 @@
}
} const dump_args;
-auto const rpc_def =
- rpc_::name > space_separator > rpcPath;
+auto const prepare_def =
+ prepare_::name > space_separator > rpcActionPath;
auto const exec_def =
exec_::name >> x3::attr(exec_{});
@@ -274,7 +274,7 @@
x3::eps;
auto const command_def =
- createCommandSuggestions >> x3::expect[cd | copy | create | delete_rule | set | commit | get | ls | discard | describe | help | move | dump | rpc | exec | cancel];
+ createCommandSuggestions >> x3::expect[cd | copy | create | delete_rule | set | commit | get | ls | discard | describe | help | move | dump | prepare | exec | cancel];
#if __clang__
#pragma GCC diagnostic pop
@@ -293,7 +293,7 @@
BOOST_SPIRIT_DEFINE(copy)
BOOST_SPIRIT_DEFINE(move)
BOOST_SPIRIT_DEFINE(dump)
-BOOST_SPIRIT_DEFINE(rpc)
+BOOST_SPIRIT_DEFINE(prepare)
BOOST_SPIRIT_DEFINE(exec)
BOOST_SPIRIT_DEFINE(cancel)
BOOST_SPIRIT_DEFINE(command)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 26e2be4..a39167e 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -210,24 +210,28 @@
std::cout << m_datastore.dump(dump.m_format) << "\n";
}
-void Interpreter::operator()(const rpc_& rpc) const
+void Interpreter::operator()(const prepare_& prepare) const
{
- m_datastore.initiateRpc(pathToString(toCanonicalPath(rpc.m_path)));
- m_parser.changeNode(rpc.m_path);
+ if (std::holds_alternative<rpcNode_>(prepare.m_path.m_nodes.back().m_suffix)) {
+ m_datastore.initiateRpc(pathToString(toCanonicalPath(prepare.m_path)));
+ } else {
+ m_datastore.initiateAction(pathToString(toCanonicalPath(prepare.m_path)));
+ }
+ m_parser.changeNode(prepare.m_path);
}
void Interpreter::operator()(const exec_&) const
{
m_parser.changeNode({Scope::Absolute, {}});
- auto output = m_datastore.executeRpc();
- std::cout << "RPC output:\n";
+ auto output = m_datastore.execute();
+ std::cout << "RPC/action output:\n";
printTree(output);
}
void Interpreter::operator()(const cancel_&) const
{
m_parser.changeNode({Scope::Absolute, {}});
- m_datastore.cancelRpc();
+ m_datastore.cancel();
}
struct commandLongHelpVisitor : boost::static_visitor<const char*> {
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index 0807c02..54a8d05 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -29,7 +29,7 @@
void operator()(const copy_& copy) const;
void operator()(const move_& move) const;
void operator()(const dump_& dump) const;
- void operator()(const rpc_& rpc) const;
+ void operator()(const prepare_& prepare) const;
void operator()(const exec_& exec) const;
void operator()(const cancel_& cancel) const;
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 00629bb..65e5140 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -144,6 +144,9 @@
parserContext.m_suggestions.emplace(Completion{parseString + "/"});
break;
case yang::NodeTypes::Action:
+ out.m_suffix = actionNode_{child.second};
+ parserContext.m_suggestions.emplace(Completion{parseString + "/"});
+ break;
case yang::NodeTypes::AnyXml:
case yang::NodeTypes::Notification:
continue;
@@ -423,22 +426,36 @@
} writableLeafPath;
-auto const writableLeafPath_def =
- PathParser<PathParserMode::DataPath, CompletionMode::Data>{filterConfigFalse};
+struct RpcActionPath : x3::parser<RpcActionPath> {
+ 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);
+ if (!res) {
+ return false;
+ }
-auto const onlyRpc = [] (const Schema& schema, const std::string& path) {
- return schema.nodeType(path) == yang::NodeTypes::Rpc;
+ if (attr.m_nodes.empty()
+ || (!std::holds_alternative<actionNode_>(attr.m_nodes.back().m_suffix) && !std::holds_alternative<actionNode_>(attr.m_nodes.back().m_suffix))) {
+ auto& parserContext = x3::get<parser_context_tag>(ctx);
+ parserContext.m_errorMsg = "This is not a path to an RPC/action.";
+ return false;
+ }
+
+ return true;
+ }
};
-auto const rpcPath_def =
- PathParser<PathParserMode::DataPath, CompletionMode::Data>{onlyRpc};
+auto const rpcActionPath = as<dataPath_>[RpcActionPath()];
-auto const noRpc = [] (const Schema& schema, const std::string& path) {
- return schema.nodeType(path) != yang::NodeTypes::Rpc;
+auto const noRpcOrAction = [] (const Schema& schema, const std::string& path) {
+ auto nodeType = schema.nodeType(path);
+ return nodeType != yang::NodeTypes::Rpc && nodeType != yang::NodeTypes::Action;
};
auto const cdPath_def =
- PathParser<PathParserMode::DataPath, CompletionMode::Data>{noRpc};
+ PathParser<PathParserMode::DataPath, CompletionMode::Data>{noRpcOrAction};
auto const presenceContainerPath_def =
dataPath;
@@ -462,7 +479,6 @@
BOOST_SPIRIT_DEFINE(keyValue)
BOOST_SPIRIT_DEFINE(key_identifier)
BOOST_SPIRIT_DEFINE(listSuffix)
-BOOST_SPIRIT_DEFINE(rpcPath)
BOOST_SPIRIT_DEFINE(cdPath)
BOOST_SPIRIT_DEFINE(presenceContainerPath)
BOOST_SPIRIT_DEFINE(listInstancePath)
diff --git a/src/proxy_datastore.cpp b/src/proxy_datastore.cpp
index e6878cb..393edef 100644
--- a/src/proxy_datastore.cpp
+++ b/src/proxy_datastore.cpp
@@ -59,27 +59,52 @@
return m_datastore->dump(format);
}
+namespace {
+struct getInputPath
+{
+ template<typename InputType>
+ auto operator()(const InputType& input)
+ {
+ return input.m_path;
+ }
+};
+}
+
void ProxyDatastore::initiateRpc(const std::string& rpcPath)
{
if (m_inputDatastore) {
- throw std::runtime_error("RPC input already in progress (" + m_rpcPath + ")");
+ throw std::runtime_error("RPC/action input already in progress (" + std::visit(getInputPath{}, m_inputPath) + ")");
}
m_inputDatastore = m_createTemporaryDatastore(m_datastore);
- m_rpcPath = rpcPath;
+ m_inputPath = RpcInput{rpcPath};
m_inputDatastore->createItem(rpcPath);
}
-DatastoreAccess::Tree ProxyDatastore::executeRpc()
+void ProxyDatastore::initiateAction(const std::string& actionPath)
+{
+ if (m_inputDatastore) {
+ throw std::runtime_error("RPC/action input already in progress (" + std::visit(getInputPath{}, m_inputPath) + ")");
+ }
+ m_inputDatastore = m_createTemporaryDatastore(m_datastore);
+ m_inputPath = ActionInput{actionPath};
+ m_inputDatastore->createItem(actionPath);
+}
+
+DatastoreAccess::Tree ProxyDatastore::execute()
{
if (!m_inputDatastore) {
- throw std::runtime_error("No RPC input in progress");
+ throw std::runtime_error("No RPC/action input in progress");
}
auto inputData = m_inputDatastore->getItems("/");
m_inputDatastore = nullptr;
- return m_datastore->executeRpc(m_rpcPath, inputData);
+ if (std::holds_alternative<RpcInput>(m_inputPath)) {
+ return m_datastore->executeRpc(std::visit(getInputPath{}, m_inputPath), inputData);
+ } else {
+ return m_datastore->executeAction(std::visit(getInputPath{}, m_inputPath), inputData);
+ }
}
-void ProxyDatastore::cancelRpc()
+void ProxyDatastore::cancel()
{
m_inputDatastore = nullptr;
}
@@ -91,7 +116,7 @@
std::shared_ptr<DatastoreAccess> ProxyDatastore::pickDatastore(const std::string& path) const
{
- if (!m_inputDatastore || !boost::starts_with(path, m_rpcPath)) {
+ if (!m_inputDatastore || !boost::starts_with(path, std::visit(getInputPath{}, m_inputPath))) {
return m_datastore;
} else {
return m_inputDatastore;
diff --git a/src/proxy_datastore.hpp b/src/proxy_datastore.hpp
index c15a869..ab69193 100644
--- a/src/proxy_datastore.hpp
+++ b/src/proxy_datastore.hpp
@@ -30,8 +30,9 @@
[[nodiscard]] std::string dump(const DataFormat format) const;
void initiateRpc(const std::string& rpcPath);
- [[nodiscard]] DatastoreAccess::Tree executeRpc();
- void cancelRpc();
+ void initiateAction(const std::string& actionPath);
+ [[nodiscard]] DatastoreAccess::Tree execute();
+ void cancel();
[[nodiscard]] std::shared_ptr<Schema> schema() const;
private:
@@ -45,5 +46,15 @@
std::shared_ptr<DatastoreAccess> m_datastore;
std::function<std::shared_ptr<DatastoreAccess>(const std::shared_ptr<DatastoreAccess>&)> m_createTemporaryDatastore;
std::shared_ptr<DatastoreAccess> m_inputDatastore;
- std::string m_rpcPath;
+
+ struct RpcInput {
+ std::string m_path;
+ };
+
+ struct ActionInput {
+ std::string m_path;
+ };
+ // This variant is needed, so that I know whether to call executeRpc or executeAction
+ // TODO: get rid of this variant with sysrepo2 because the method for RPC/action is the same there
+ std::variant<ActionInput, RpcInput> m_inputPath;
};
diff --git a/tests/command_completion.cpp b/tests/command_completion.cpp
index 49017b0..2fedb78 100644
--- a/tests/command_completion.cpp
+++ b/tests/command_completion.cpp
@@ -21,7 +21,7 @@
int expectedContextLength;
SECTION("no prefix")
{
- expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe", "move", "dump", "rpc", "exec", "cancel"};
+ expectedCompletions = {"cd", "copy", "create", "delete", "set", "commit", "get", "ls", "discard", "help", "describe", "move", "dump", "prepare", "exec", "cancel"};
expectedContextLength = 0;
SECTION("no space") {
input = "";
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 2fac222..da3bc48 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -992,7 +992,7 @@
}
catching<OnExec>([&] {REQUIRE(datastore->executeRpc(rpc, input) == output);});
- catching<OnExec>([&] {REQUIRE(proxyDatastore.executeRpc() == output);});
+ catching<OnExec>([&] {REQUIRE(proxyDatastore.execute() == output);});
}
SECTION("non-existing RPC")
diff --git a/tests/interpreter.cpp b/tests/interpreter.cpp
index a88aa48..966855d 100644
--- a/tests/interpreter.cpp
+++ b/tests/interpreter.cpp
@@ -442,12 +442,12 @@
{
dataPath_ rpcPath;
rpcPath.pushFragment({{"example"}, rpcNode_{"launch-nukes"}});
- rpc_ rpcCmd;
- rpcCmd.m_path = rpcPath;
+ prepare_ prepareCmd;
+ prepareCmd.m_path = rpcPath;
{
REQUIRE_CALL(*input_datastore, createItem("/example:launch-nukes"));
- boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{rpcCmd});
+ boost::apply_visitor(Interpreter(parser, proxyDatastore), command_{prepareCmd});
}
REQUIRE(parser.currentPath() == rpcPath);
diff --git a/tests/path_completion.cpp b/tests/path_completion.cpp
index 1c0c8f1..0b0d3f8 100644
--- a/tests/path_completion.cpp
+++ b/tests/path_completion.cpp
@@ -336,9 +336,9 @@
expectedContextLength = 0;
}
- SECTION("rpc input nodes NOT completed for rpc command")
+ SECTION("rpc input nodes NOT completed for prepare command")
{
- input = "rpc example:fire/";
+ input = "prepare example:fire/";
expectedCompletions = {};
expectedContextLength = 13;
}