Refactor RPC input generation

Change-Id: I4fac9ffc939a7df41a6e3f3332199da0070edb9e
diff --git a/src/libyang_utils.cpp b/src/libyang_utils.cpp
index 1f0c372..a6b22cd 100644
--- a/src/libyang_utils.cpp
+++ b/src/libyang_utils.cpp
@@ -90,9 +90,6 @@
 }
 }
 
-// This is very similar to the fillMap lambda in SysrepoAccess, however,
-// Sysrepo returns a weird array-like structure, while libnetconf
-// returns libyang::Data_Node
 void lyNodesToTree(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items, std::optional<std::string> ignoredXPathPrefix)
 {
     for (auto it = items.begin(); it < items.end(); it++) {
@@ -108,3 +105,27 @@
         }
     }
 }
+
+DatastoreAccess::Tree rpcOutputToTree(const std::string& rpcPath, libyang::S_Data_Node output)
+{
+    DatastoreAccess::Tree res;
+    if (output) {
+        // The output is "some top-level node". If we actually want the output of our RPC/action we need to use
+        // find_path.  Also, our `path` is fully prefixed, but the output paths aren't. So we use outputNode->path() to
+        // get the unprefixed path.
+
+        auto outputNode = output->find_path(rpcPath.c_str())->data().front();
+        lyNodesToTree(res, {outputNode}, joinPaths(outputNode->path(), "/"));
+    }
+    return res;
+}
+
+libyang::S_Data_Node treeToRpcInput(libyang::S_Context ctx, const std::string& path, DatastoreAccess::Tree in)
+{
+    auto root = std::make_shared<libyang::Data_Node>(ctx, path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
+    for (const auto& [k, v] : in) {
+        root->new_path(ctx, k.c_str(), leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
+    }
+
+    return root;
+}