DatastoreAccess: add support for generic user-defined RPCs

I was lazy and in the NETCONF backend, the fillMap patch does not check
for actual string prefix match, it just optimistically trims the RPC
prefix. I think this is safe (definitely in the "won't crash" department
due to use of std::string::substr(), but also in the "won't produce
garbage" context because libyang is expected to validate everything).

In the sysrepo backend, I had to introduce some duplication into that
visitor which converts from our data types to sysrepo. It tirns out that
sysrepo::Session::set_item requires a separate S_Val, whereas in context
of handling an RPC's output, we have a Vals_Holder which, after
reallocation, becomes a Vals instance, and that one does not support
replacing the individual Val instances by "something" -- one has to call
a Val::set, and these methods are different from Val::Val constructors.

I'm explicitly testing for lists and containers because the
documentation looked a bit scary -- I understood it in a way which make
me spend extra effort to make sure that all that has to be created gets
created.

Change-Id: I717af71d69b209c444e1c5fe6d8ec2c2fcbdde8b
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 499597f..cf19407 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -8,6 +8,7 @@
 
 #include <sysrepo-cpp/Session.hpp>
 #include "sysrepo_access.hpp"
+#include "utils.hpp"
 #include "yang_schema.hpp"
 
 leaf_data_ leafValueFromVal(const sysrepo::S_Val& value)
@@ -83,6 +84,47 @@
     }
 };
 
+struct updateSrValFromValue : boost::static_visitor<void> {
+    std::string xpath;
+    sysrepo::S_Val v;
+    updateSrValFromValue(const std::string& xpath, sysrepo::S_Val v)
+        : xpath(xpath)
+        , v(v)
+    {
+    }
+
+    void operator()(const enum_& value) const
+    {
+        v->set(xpath.c_str(), value.m_value.c_str(), SR_ENUM_T);
+    }
+
+    void operator()(const binary_& value) const
+    {
+        v->set(xpath.c_str(), value.m_value.c_str(), SR_BINARY_T);
+    }
+
+    void operator()(const identityRef_& value) const
+    {
+        v->set(xpath.c_str(), (value.m_prefix.value().m_name + ":" + value.m_value).c_str(), SR_IDENTITYREF_T);
+    }
+
+    void operator()(const special_& value) const
+    {
+        throw std::runtime_error("Tried constructing S_Val from a " + specialValueToString(value));
+    }
+
+    void operator()(const std::string& value) const
+    {
+        v->set(xpath.c_str(), value.c_str(), SR_STRING_T);
+    }
+
+    template <typename T>
+    void operator()(const T value) const
+    {
+        v->set(xpath.c_str(), value);
+    }
+};
+
 SysrepoAccess::~SysrepoAccess() = default;
 
 SysrepoAccess::SysrepoAccess(const std::string& appname)
@@ -203,6 +245,25 @@
     }
 }
 
+DatastoreAccess::Tree SysrepoAccess::executeRpc(const std::string &path, const Tree &input)
+{
+    auto srInput = std::make_shared<sysrepo::Vals>(input.size());
+    {
+        size_t i = 0;
+        for (const auto& [k, v] : input) {
+            boost::apply_visitor(updateSrValFromValue(joinPaths(path, k), srInput->val(i)), v);
+            ++i;
+        }
+    }
+    auto output = m_session->rpc_send(path.c_str(), srInput);
+    Tree res;
+    for (size_t i = 0; i < output->val_cnt(); ++i) {
+        const auto& v = output->val(i);
+        res.emplace(std::string(v->xpath()).substr(joinPaths(path, "/").size()), leafValueFromVal(v));
+    }
+    return res;
+}
+
 std::string SysrepoAccess::fetchSchema(const char* module, const char* revision, const char* submodule)
 {
     std::string schema;