Add support for executing RPCs

Creating a temporary YangAccess for RPC input means I need to somehow
give the right libyang schemas. For that reason I supply a callable
which is able to fetch the schema and create a YangAccess instance for
ProxyDatastore.

The ProxyDatastore class now has a simple mechanism for deciding whether
to use the normal datastore and the temporary based on a path prefix.

Change-Id: Ib455f53237598bc2620161a44fb89c48ddfeb6e3
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index 3f7eead..a594228 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -219,8 +219,49 @@
     DataFormat m_format;
 };
 
+struct rpc_ : x3::position_tagged {
+    static constexpr auto name = "rpc";
+    static constexpr auto shortHelp = "rpc - initiate RPC";
+    static constexpr auto longHelp = R"(
+    rpc <rpc-path>
+
+    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;
+
+    dataPath_ m_path;
+};
+
+struct exec_ : x3::position_tagged {
+    static constexpr auto name = "exec";
+    static constexpr auto shortHelp = "exec - execute RPC";
+    static constexpr auto longHelp = R"(
+    exec
+
+    This command executes the RPC you have previously initiated.
+
+    Usage:
+        /> exec)";
+    bool operator==(const exec_& other) const;
+};
+
+struct cancel_ : x3::position_tagged {
+    static constexpr auto name = "cancel";
+    static constexpr auto shortHelp = "cancel - cancel an ongoing RPC input";
+    static constexpr auto longHelp = R"(
+    cancel
+
+    This command cancels a previously entered RPC context.
+
+    Usage:
+        /> cancel)";
+    bool operator==(const cancel_& other) const;
+};
+
 struct help_;
-using CommandTypes = boost::mpl::vector<cd_, commit_, copy_, create_, delete_, describe_, discard_, dump_, get_, help_, ls_, move_, set_>;
+using CommandTypes = boost::mpl::vector<cancel_, cd_, commit_, copy_, create_, delete_, describe_, discard_, dump_, exec_, get_, help_, ls_, move_, rpc_, set_>;
 struct help_ : x3::position_tagged {
     static constexpr auto name = "help";
     static constexpr auto shortHelp = "help - Print help for commands.";
@@ -268,3 +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)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 08d2079..87cacd1 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -230,6 +230,10 @@
     }
 };
 
+struct rpcPath_class;
+
+struct cdPath_class;
+
 struct set_class {
     template <typename Iterator, typename Exception, typename Context>
     x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& x, Context const& context)
@@ -256,6 +260,12 @@
 
 struct dump_class;
 
+struct rpc_class;
+
+struct exec_class;
+
+struct cancel_class;
+
 struct command_class {
     template <typename Iterator, typename Exception, typename Context>
     x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& x, Context const& context)
diff --git a/src/ast_path.cpp b/src/ast_path.cpp
index 44c98c3..e8bfbcf 100644
--- a/src/ast_path.cpp
+++ b/src/ast_path.cpp
@@ -110,6 +110,11 @@
     return (this->m_name == b.m_name);
 }
 
+bool rpcNode_::operator==(const rpcNode_& 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 4e21167..d02be60 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -81,9 +81,15 @@
     std::string m_name;
 };
 
+struct rpcNode_ {
+    bool operator==(const rpcNode_& other) const;
+
+    std::string m_name;
+};
+
 struct schemaNode_ {
     boost::optional<module_> m_prefix;
-    std::variant<container_, list_, nodeup_, leaf_, leafList_> m_suffix;
+    std::variant<container_, list_, nodeup_, leaf_, leafList_, rpcNode_> m_suffix;
 
     schemaNode_();
     schemaNode_(decltype(m_suffix) node);
@@ -93,7 +99,7 @@
 
 struct dataNode_ {
     boost::optional<module_> m_prefix;
-    std::variant<container_, listElement_, nodeup_, leaf_, leafListElement_, leafList_, list_> m_suffix;
+    std::variant<container_, listElement_, nodeup_, leaf_, leafListElement_, leafList_, list_, rpcNode_> m_suffix;
 
     dataNode_();
     dataNode_(decltype(m_suffix) node);
diff --git a/src/cli.cpp b/src/cli.cpp
index 1f62f78..a539b90 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -15,6 +15,7 @@
 #include "proxy_datastore.hpp"
 #if defined(SYSREPO_CLI)
 #include "sysrepo_access.hpp"
+#include "yang_schema.hpp"
 #define PROGRAM_NAME "sysrepo-cli"
 static const auto usage = R"(CLI interface to sysrepo
 
@@ -124,7 +125,17 @@
 #error "Unknown CLI backend"
 #endif
 
-    ProxyDatastore proxyDatastore(datastore);
+#if defined(SYSREPO_CLI)
+    auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
+        return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
+    };
+#elif defined(YANG_CLI)
+    auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>&) {
+        return nullptr;
+    };
+#endif
+
+    ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
     auto dataQuery = std::make_shared<DataQuery>(*datastore);
     Parser parser(datastore->schema(), writableOps, dataQuery);
 
@@ -189,6 +200,8 @@
             std::cerr << ex.what() << std::endl;
         } catch (DatastoreException& ex) {
             std::cerr << ex.what() << std::endl;
+        } catch (std::runtime_error& ex) {
+            std::cerr << ex.what() << std::endl;
         }
 
         lineEditor.history_add(line);
diff --git a/src/grammars.hpp b/src/grammars.hpp
index 742115b..ee808d5 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -29,6 +29,9 @@
 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<exec_class, exec_> const exec = "exec";
+x3::rule<cancel_class, cancel_> const cancel = "cancel";
 x3::rule<command_class, command_> const command = "command";
 
 x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
@@ -52,7 +55,7 @@
     ls_::name >> *(space_separator >> ls_options) >> -(space_separator >> (anyPath | (module >> "*")));
 
 auto const cd_def =
-    cd_::name >> space_separator > dataPath;
+    cd_::name >> space_separator > cdPath;
 
 auto const create_def =
     create_::name >> space_separator > (presenceContainerPath | listInstancePath | leafListElementPath);
@@ -255,6 +258,15 @@
     }
 } const dump_args;
 
+auto const rpc_def =
+    rpc_::name > space_separator > rpcPath;
+
+auto const exec_def =
+    exec_::name >> x3::attr(exec_{});
+
+auto const cancel_def =
+    cancel_::name >> x3::attr(cancel_{});
+
 auto const dump_def =
     dump_::name > space_separator >> dump_args;
 
@@ -262,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];
+    createCommandSuggestions >> x3::expect[cd | copy | create | delete_rule | set | commit | get | ls | discard | describe | help | move | dump | rpc | exec | cancel];
 
 #if __clang__
 #pragma GCC diagnostic pop
@@ -281,5 +293,8 @@
 BOOST_SPIRIT_DEFINE(copy)
 BOOST_SPIRIT_DEFINE(move)
 BOOST_SPIRIT_DEFINE(dump)
+BOOST_SPIRIT_DEFINE(rpc)
+BOOST_SPIRIT_DEFINE(exec)
+BOOST_SPIRIT_DEFINE(cancel)
 BOOST_SPIRIT_DEFINE(command)
 BOOST_SPIRIT_DEFINE(createCommandSuggestions)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 5ff73e7..26e2be4 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -30,6 +30,26 @@
     }
 };
 
+namespace {
+void printTree(const DatastoreAccess::Tree tree)
+{
+    for (auto it = tree.begin(); it != tree.end(); it++) {
+        auto [path, value] = *it;
+        if (value.type() == typeid(special_) && boost::get<special_>(value).m_value == SpecialValue::LeafList) {
+            auto leafListPrefix = path;
+            std::cout << path << " = " << leafDataToString(value) << std::endl;
+
+            while (it + 1 != tree.end() && boost::starts_with((it + 1)->first, leafListPrefix)) {
+                ++it;
+                std::cout << stripLeafListValueFromPath(it->first) << " = " << leafDataToString(it->second) << std::endl;
+            }
+        } else {
+            std::cout << path << " = " << leafDataToString(value) << std::endl;
+        }
+    }
+}
+}
+
 template <typename PathType>
 std::string pathToString(const PathType& path)
 {
@@ -64,20 +84,7 @@
 void Interpreter::operator()(const get_& get) const
 {
     auto items = m_datastore.getItems(pathToString(toCanonicalPath(get.m_path)));
-    for (auto it = items.begin(); it != items.end(); it++) {
-        auto [path, value] = *it;
-        if (value.type() == typeid(special_) && boost::get<special_>(value).m_value == SpecialValue::LeafList) {
-            auto leafListPrefix = path;
-            std::cout << path << " = " << leafDataToString(value) << std::endl;
-
-            while (it + 1 != items.end() && boost::starts_with((it + 1) ->first, leafListPrefix)) {
-                ++it;
-                std::cout << stripLeafListValueFromPath(it->first) << " = " << leafDataToString(it->second) << std::endl;
-            }
-        } else {
-            std::cout << path << " = " << leafDataToString(value) << std::endl;
-        }
-    }
+    printTree(items);
 }
 
 void Interpreter::operator()(const cd_& cd) const
@@ -162,12 +169,14 @@
     case yang::NodeTypes::List:
         ss << "list";
         break;
+    case yang::NodeTypes::Rpc:
+        ss << "RPC";
+        break;
     case yang::NodeTypes::Action:
     case yang::NodeTypes::AnyXml:
     case yang::NodeTypes::LeafList:
     case yang::NodeTypes::Notification:
-    case yang::NodeTypes::Rpc:
-        throw std::logic_error("describe got an rpc or an action: this should never happen, because their paths cannot be parsed");
+        throw std::logic_error("describe can't handle the type of " + path);
     }
 
     if (!m_datastore.schema()->isConfig(path)) {
@@ -201,6 +210,26 @@
     std::cout << m_datastore.dump(dump.m_format) << "\n";
 }
 
+void Interpreter::operator()(const rpc_& rpc) const
+{
+    m_datastore.initiateRpc(pathToString(toCanonicalPath(rpc.m_path)));
+    m_parser.changeNode(rpc.m_path);
+}
+
+void Interpreter::operator()(const exec_&) const
+{
+    m_parser.changeNode({Scope::Absolute, {}});
+    auto output = m_datastore.executeRpc();
+    std::cout << "RPC output:\n";
+    printTree(output);
+}
+
+void Interpreter::operator()(const cancel_&) const
+{
+    m_parser.changeNode({Scope::Absolute, {}});
+    m_datastore.cancelRpc();
+}
+
 struct commandLongHelpVisitor : boost::static_visitor<const char*> {
     template <typename T>
     auto constexpr operator()(boost::type<T>) const
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index 000cc21..0807c02 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -29,6 +29,9 @@
     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 exec_& exec) const;
+    void operator()(const cancel_& cancel) const;
 
 private:
     [[nodiscard]] std::string buildTypeInfo(const std::string& path) const;
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 9fa3db1..00629bb 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -14,6 +14,8 @@
 
 namespace x3 = boost::spirit::x3;
 
+x3::rule<cdPath_class, dataPath_> const cdPath = "cdPath";
+x3::rule<rpcPath_class, dataPath_> const rpcPath = "rpcPath";
 x3::rule<presenceContainerPath_class, dataPath_> const presenceContainerPath = "presenceContainerPath";
 x3::rule<listInstancePath_class, dataPath_> const listInstancePath = "listInstancePath";
 x3::rule<leafListElementPath_class, dataPath_> const leafListElementPath = "leafListElementPath";
@@ -137,10 +139,13 @@
                         parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
                     }
                     break;
+                case yang::NodeTypes::Rpc:
+                    out.m_suffix = rpcNode_{child.second};
+                    parserContext.m_suggestions.emplace(Completion{parseString + "/"});
+                    break;
                 case yang::NodeTypes::Action:
                 case yang::NodeTypes::AnyXml:
                 case yang::NodeTypes::Notification:
-                case yang::NodeTypes::Rpc:
                     continue;
             }
             table.add(parseString, out);
@@ -421,6 +426,20 @@
 auto const writableLeafPath_def =
     PathParser<PathParserMode::DataPath, CompletionMode::Data>{filterConfigFalse};
 
+auto const onlyRpc = [] (const Schema& schema, const std::string& path) {
+    return schema.nodeType(path) == yang::NodeTypes::Rpc;
+};
+
+auto const rpcPath_def =
+    PathParser<PathParserMode::DataPath, CompletionMode::Data>{onlyRpc};
+
+auto const noRpc = [] (const Schema& schema, const std::string& path) {
+    return schema.nodeType(path) != yang::NodeTypes::Rpc;
+};
+
+auto const cdPath_def =
+    PathParser<PathParserMode::DataPath, CompletionMode::Data>{noRpc};
+
 auto const presenceContainerPath_def =
     dataPath;
 
@@ -443,6 +462,8 @@
 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)
 BOOST_SPIRIT_DEFINE(leafListElementPath)
diff --git a/src/proxy_datastore.cpp b/src/proxy_datastore.cpp
index ab9193c..e6878cb 100644
--- a/src/proxy_datastore.cpp
+++ b/src/proxy_datastore.cpp
@@ -4,36 +4,39 @@
  * Written by Václav Kubernát <kubernat@cesnet.cz>
  *
 */
+#include <boost/algorithm/string/predicate.hpp>
 #include "proxy_datastore.hpp"
+#include "yang_schema.hpp"
 
-ProxyDatastore::ProxyDatastore(const std::shared_ptr<DatastoreAccess>& datastore)
+ProxyDatastore::ProxyDatastore(const std::shared_ptr<DatastoreAccess>& datastore, std::function<std::shared_ptr<DatastoreAccess>(const std::shared_ptr<DatastoreAccess>&)> createTemporaryDatastore)
     : m_datastore(datastore)
+    , m_createTemporaryDatastore(createTemporaryDatastore)
 {
 }
 
 DatastoreAccess::Tree ProxyDatastore::getItems(const std::string& path) const
 {
-    return m_datastore->getItems(path);
+    return pickDatastore(path)->getItems(path);
 }
 
 void ProxyDatastore::setLeaf(const std::string& path, leaf_data_ value)
 {
-    m_datastore->setLeaf(path, value);
+    pickDatastore(path)->setLeaf(path, value);
 }
 
 void ProxyDatastore::createItem(const std::string& path)
 {
-    m_datastore->createItem(path);
+    pickDatastore(path)->createItem(path);
 }
 
 void ProxyDatastore::deleteItem(const std::string& path)
 {
-    m_datastore->deleteItem(path);
+    pickDatastore(path)->deleteItem(path);
 }
 
 void ProxyDatastore::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
 {
-    m_datastore->moveItem(source, move);
+    pickDatastore(source)->moveItem(source, move);
 }
 
 void ProxyDatastore::commitChanges()
@@ -56,7 +59,41 @@
     return m_datastore->dump(format);
 }
 
+void ProxyDatastore::initiateRpc(const std::string& rpcPath)
+{
+    if (m_inputDatastore) {
+        throw std::runtime_error("RPC input already in progress (" + m_rpcPath + ")");
+    }
+    m_inputDatastore = m_createTemporaryDatastore(m_datastore);
+    m_rpcPath = rpcPath;
+    m_inputDatastore->createItem(rpcPath);
+}
+
+DatastoreAccess::Tree ProxyDatastore::executeRpc()
+{
+    if (!m_inputDatastore) {
+        throw std::runtime_error("No RPC input in progress");
+    }
+    auto inputData = m_inputDatastore->getItems("/");
+    m_inputDatastore = nullptr;
+    return m_datastore->executeRpc(m_rpcPath, inputData);
+}
+
+void ProxyDatastore::cancelRpc()
+{
+    m_inputDatastore = nullptr;
+}
+
 std::shared_ptr<Schema> ProxyDatastore::schema() const
 {
     return m_datastore->schema();
 }
+
+std::shared_ptr<DatastoreAccess> ProxyDatastore::pickDatastore(const std::string& path) const
+{
+    if (!m_inputDatastore || !boost::starts_with(path, m_rpcPath)) {
+        return m_datastore;
+    } else {
+        return m_inputDatastore;
+    }
+}
diff --git a/src/proxy_datastore.hpp b/src/proxy_datastore.hpp
index 0ed5e2d..c15a869 100644
--- a/src/proxy_datastore.hpp
+++ b/src/proxy_datastore.hpp
@@ -7,13 +7,18 @@
 
 #pragma once
 #include "datastore_access.hpp"
+#include "yang_access.hpp"
 
 /*! \class ProxyDatastore
  *     \brief DatastoreAccess wrapper that handles RPC input
  */
 class ProxyDatastore {
 public:
-    ProxyDatastore(const std::shared_ptr<DatastoreAccess>& datastore);
+    /**
+     * The createTemporaryDatastore function should create a temporary datastore that's going to be used for RPC input.
+     * This temporary datastore and the main datastore are supposed share the same schemas.
+     * */
+    ProxyDatastore(const std::shared_ptr<DatastoreAccess>& datastore, std::function<std::shared_ptr<DatastoreAccess>(const std::shared_ptr<DatastoreAccess>&)> createTemporaryDatastore);
     [[nodiscard]] DatastoreAccess::Tree getItems(const std::string& path) const;
     void setLeaf(const std::string& path, leaf_data_ value);
     void createItem(const std::string& path);
@@ -24,7 +29,21 @@
     void copyConfig(const Datastore source, const Datastore destination);
     [[nodiscard]] std::string dump(const DataFormat format) const;
 
+    void initiateRpc(const std::string& rpcPath);
+    [[nodiscard]] DatastoreAccess::Tree executeRpc();
+    void cancelRpc();
+
     [[nodiscard]] std::shared_ptr<Schema> schema() const;
 private:
+    /** @brief Picks a datastore based on the requested path.
+     *
+     * If the path starts with a currently processed RPC, m_inputDatastore is picked.
+     * Otherwise m_datastore is picked.
+     */
+    [[nodiscard]] std::shared_ptr<DatastoreAccess> pickDatastore(const std::string& path) const;
+
     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;
 };
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index 75ccb4f..e54ca68 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -34,6 +34,15 @@
     m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
 }
 
+void StaticSchema::addRpc(const std::string& location, const std::string& name)
+{
+    m_nodes.at(location).emplace(name, NodeInfo{yang::rpc{}, yang::AccessType::Writable});
+
+    //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>());
+}
+
 bool StaticSchema::listHasKey(const schemaPath_& listPath, const std::string& key) const
 {
     return listKeys(listPath).count(key);
@@ -201,6 +210,11 @@
     {
         return yang::NodeTypes::LeafList;
     }
+
+    yang::NodeTypes operator()(const yang::rpc)
+    {
+        return yang::NodeTypes::Rpc;
+    }
 };
 
 yang::NodeTypes StaticSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 9d897b1..e343087 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -34,6 +34,9 @@
     yang::TypeInfo m_type;
 };
 
+struct rpc {
+};
+
 struct module {
 };
 
@@ -43,7 +46,7 @@
 };
 }
 
-using NodeType = std::variant<yang::container, yang::list, yang::leaf, yang::leaflist>;
+using NodeType = std::variant<yang::container, yang::list, yang::leaf, yang::leaflist, yang::rpc>;
 
 struct NodeInfo {
     NodeType m_nodeType;
@@ -83,6 +86,7 @@
     void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type, const yang::AccessType accessType = yang::AccessType::Writable);
     void addLeafList(const std::string& location, const std::string& name, const yang::LeafDataType& type);
     void addList(const std::string& location, const std::string& name, const std::set<std::string>& keys);
+    void addRpc(const std::string& location, const std::string& name);
     void addModule(const std::string& name);
     void addIdentity(const std::optional<identityRef_>& base, const identityRef_& name);
 
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 0fac1d8..510c530 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -132,7 +132,16 @@
 
     void operator()(const special_& value) const
     {
-        throw std::runtime_error("Tried constructing S_Val from a " + specialValueToString(value));
+        switch (value.m_value) {
+        case SpecialValue::PresenceContainer:
+            v->set(xpath.c_str(), nullptr, SR_CONTAINER_PRESENCE_T);
+            break;
+        case SpecialValue::List:
+            v->set(xpath.c_str(), nullptr, SR_LIST_T);
+            break;
+        default:
+            throw std::runtime_error("Tried constructing S_Val from a " + specialValueToString(value));
+        }
     }
 
     void operator()(const std::string& value) const
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
index 8277ef1..ebe39f1 100644
--- a/src/yang_access.cpp
+++ b/src/yang_access.cpp
@@ -31,6 +31,15 @@
     : m_ctx(lyWrap(ly_ctx_new(nullptr, LY_CTX_DISABLE_SEARCHDIR_CWD)))
     , m_datastore(lyWrap<lyd_node>(nullptr))
     , m_schema(std::make_shared<YangSchema>(libyang::create_new_Context(m_ctx.get())))
+    , m_validation_mode(LYD_OPT_DATA)
+{
+}
+
+YangAccess::YangAccess(std::shared_ptr<YangSchema> schema)
+    : m_ctx(schema->m_context->swig_ctx(), [](auto) {})
+    , m_datastore(lyWrap<lyd_node>(nullptr))
+    , m_schema(schema)
+    , m_validation_mode(LYD_OPT_RPC)
 {
 }
 
@@ -103,7 +112,12 @@
 void YangAccess::validate()
 {
     auto datastore = m_datastore.release();
-    lyd_validate(&datastore, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB, m_ctx.get());
+
+    if (m_validation_mode == LYD_OPT_RPC) {
+        lyd_validate(&datastore, m_validation_mode, nullptr);
+    } else {
+        lyd_validate(&datastore, m_validation_mode | LYD_OPT_DATA_NO_YANGLIB, m_ctx.get());
+    }
     m_datastore = lyWrap(datastore);
 }
 
@@ -116,8 +130,12 @@
 
     auto set = lyWrap(lyd_find_path(m_datastore.get(), path == "/" ? "/*" : path.c_str()));
     auto setWrapper = libyang::Set(set.get(), nullptr);
-
-    lyNodesToTree(res, setWrapper.data());
+    std::optional<std::string> ignoredXPathPrefix;
+    if (m_datastore->schema->nodetype == LYS_RPC) {
+        auto path = std::unique_ptr<char, decltype(&free)>(lys_path(m_datastore->schema, 0), &free);
+        ignoredXPathPrefix = joinPaths(path.get(), "/");
+    }
+    lyNodesToTree(res, setWrapper.data(), ignoredXPathPrefix);
     return res;
 }
 
@@ -218,6 +236,9 @@
         getErrorsAndThrow();
     }
     for (const auto& [k, v] : input) {
+        if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) {
+            continue;
+        }
         auto node = lyd_new_path(root.get(), m_ctx.get(), joinPaths(path, k).c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, 0);
         if (!node) {
             getErrorsAndThrow();
diff --git a/src/yang_access.hpp b/src/yang_access.hpp
index 075ba26..503a077 100644
--- a/src/yang_access.hpp
+++ b/src/yang_access.hpp
@@ -21,6 +21,7 @@
 class YangAccess : public DatastoreAccess {
 public:
     YangAccess();
+    YangAccess(std::shared_ptr<YangSchema> schema);
     ~YangAccess() override;
     [[nodiscard]] Tree getItems(const std::string& path) const override;
     void setLeaf(const std::string& path, leaf_data_ value) override;
@@ -53,4 +54,5 @@
     std::unique_ptr<ly_ctx, void(*)(ly_ctx*)> m_ctx;
     std::unique_ptr<lyd_node, void(*)(lyd_node*)> m_datastore;
     std::shared_ptr<YangSchema> m_schema;
+    const int m_validation_mode;
 };
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 711cdbd..05195b9 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -101,7 +101,11 @@
             [&oldOptions]() {
                 libyang::set_log_options(oldOptions);
             });
-        return m_context->get_node(nullptr, node.c_str());
+        auto res = m_context->get_node(nullptr, node.c_str());
+        if (!res) { // If no node is found, try output rpc nodes too.
+            res = m_context->get_node(nullptr, node.c_str(), 1);
+        }
+        return res;
     }
 }
 
@@ -476,7 +480,20 @@
 
 bool YangSchema::isConfig(const std::string& path) const
 {
-    return getSchemaNode(path.c_str())->flags() & LYS_CONFIG_W;
+    auto node = getSchemaNode(path.c_str());
+    if (node->flags() & LYS_CONFIG_W) {
+        return true;
+    }
+
+    // Node can still be an input node.
+    while (node->parent()) {
+        node = node->parent();
+        if (node->nodetype() == LYS_INPUT) {
+            return true;
+        }
+    }
+
+    return false;
 }
 
 std::optional<std::string> YangSchema::defaultValue(const std::string& leafPath) const
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index a527ef3..5f3aa20 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -70,6 +70,7 @@
     std::shared_ptr<libyang::Module> getYangModule(const std::string& name);
 
 private:
+    friend class YangAccess;
     template <typename NodeType>
     [[nodiscard]] yang::TypeInfo impl_leafType(const std::shared_ptr<libyang::Schema_Node>& node) const;
     [[nodiscard]] std::set<std::string> modules() const;