Add ProxyDatastore

This class will be used to intercept certain commands from the cli, like
entering RPC input. Right now, it works just as a pass-through.

Change-Id: I2d252609c1354005a0ccf4a1f26399dc895a73e8
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4fd1b0d..4081ce1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -133,10 +133,15 @@
     )
 target_link_libraries(parser schemas utils ast_values)
 
+add_library(proxydatastore STATIC
+    src/proxy_datastore.cpp
+    )
+target_link_libraries(proxydatastore PUBLIC datastoreaccess)
+
 # Links libraries, that aren't specific to a datastore type
 function(cli_link_required cli_target)
     target_include_directories(${cli_target} PRIVATE ${REPLXX_PATH})
-    target_link_libraries(${cli_target} yangschema docopt parser ${REPLXX_LIBRARY})
+    target_link_libraries(${cli_target} proxydatastore yangschema docopt parser ${REPLXX_LIBRARY})
     add_dependencies(${cli_target} target-NETCONF_CLI_VERSION)
     target_include_directories(${cli_target} PRIVATE ${PROJECT_BINARY_DIR})
 endfunction()
@@ -302,6 +307,7 @@
     target_link_libraries(test_set_value_completion leaf_data_type)
     cli_test(list_manipulation)
     cli_test(interpreter)
+    target_link_libraries(test_interpreter proxydatastore)
     cli_test(path_utils)
     target_link_libraries(test_path_utils path)
     cli_test(keyvalue_completion)
diff --git a/src/cli.cpp b/src/cli.cpp
index 8a3536f..1f62f78 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -12,6 +12,7 @@
 #include <sstream>
 #include "NETCONF_CLI_VERSION.h"
 #include "interpreter.hpp"
+#include "proxy_datastore.hpp"
 #if defined(SYSREPO_CLI)
 #include "sysrepo_access.hpp"
 #define PROGRAM_NAME "sysrepo-cli"
@@ -72,10 +73,10 @@
             return 1;
         }
     }
-    SysrepoAccess datastore(PROGRAM_NAME, datastoreType);
+    auto datastore = std::make_shared<SysrepoAccess>(PROGRAM_NAME, datastoreType);
     std::cout << "Connected to sysrepo [datastore: " << (datastoreType == Datastore::Startup ? "startup" : "running") << "]" << std::endl;
 #elif defined(YANG_CLI)
-    YangAccess datastore;
+    auto datastore = std::make_shared<YangAccess>();
     if (args["--configonly"].asBool()) {
         writableOps = WritableOps::No;
     } else {
@@ -83,13 +84,13 @@
         std::cout << "ops is writable" << std::endl;
     }
     if (const auto& search_dir = args["-s"]) {
-        datastore.addSchemaDir(search_dir.asString());
+        datastore->addSchemaDir(search_dir.asString());
     }
     for (const auto& schemaFile : args["<schema_file_or_module_name>"].asStringList()) {
         if (std::filesystem::exists(schemaFile)) {
-            datastore.addSchemaFile(schemaFile);
+            datastore->addSchemaFile(schemaFile);
         } else if (schemaFile.find('/') == std::string::npos) { // Module names cannot have a slash
-            datastore.loadModule(schemaFile);
+            datastore->loadModule(schemaFile);
         } else {
             std::cerr << "Cannot load YANG module " << schemaFile << "\n";
         }
@@ -106,7 +107,7 @@
                 return 1;
             }
             try {
-                datastore.enableFeature(parsed.first, parsed.second);
+                datastore->enableFeature(parsed.first, parsed.second);
             } catch (std::runtime_error& ex) {
                 std::cerr << ex.what() << "\n";
                 return 1;
@@ -116,15 +117,16 @@
     }
     if (const auto& dataFiles = args["-i"]) {
         for (const auto& dataFile : dataFiles.asStringList()) {
-            datastore.addDataFile(dataFile);
+            datastore->addDataFile(dataFile);
         }
     }
 #else
 #error "Unknown CLI backend"
 #endif
 
-    auto dataQuery = std::make_shared<DataQuery>(datastore);
-    Parser parser(datastore.schema(), writableOps, dataQuery);
+    ProxyDatastore proxyDatastore(datastore);
+    auto dataQuery = std::make_shared<DataQuery>(*datastore);
+    Parser parser(datastore->schema(), writableOps, dataQuery);
 
     using replxx::Replxx;
 
@@ -182,7 +184,7 @@
 
         try {
             command_ cmd = parser.parseCommand(line, std::cout);
-            boost::apply_visitor(Interpreter(parser, datastore), cmd);
+            boost::apply_visitor(Interpreter(parser, proxyDatastore), cmd);
         } catch (InvalidCommandException& ex) {
             std::cerr << ex.what() << std::endl;
         } catch (DatastoreException& ex) {
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 3b52960..5ff73e7 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -293,7 +293,7 @@
     }
 }
 
-Interpreter::Interpreter(Parser& parser, DatastoreAccess& datastore)
+Interpreter::Interpreter(Parser& parser, ProxyDatastore& datastore)
     : m_parser(parser)
     , m_datastore(datastore)
 {
diff --git a/src/interpreter.hpp b/src/interpreter.hpp
index 543fb2e..000cc21 100644
--- a/src/interpreter.hpp
+++ b/src/interpreter.hpp
@@ -9,12 +9,12 @@
 #pragma once
 
 #include <boost/variant/static_visitor.hpp>
-#include "datastore_access.hpp"
+#include "proxy_datastore.hpp"
 #include "parser.hpp"
 
 
 struct Interpreter : boost::static_visitor<void> {
-    Interpreter(Parser& parser, DatastoreAccess& datastore);
+    Interpreter(Parser& parser, ProxyDatastore& datastore);
 
     void operator()(const commit_&) const;
     void operator()(const set_&) const;
@@ -40,5 +40,5 @@
     [[nodiscard]] boost::variant<dataPath_, schemaPath_, module_> toCanonicalPath(const PathType& path) const;
 
     Parser& m_parser;
-    DatastoreAccess& m_datastore;
+    ProxyDatastore& m_datastore;
 };
diff --git a/src/proxy_datastore.cpp b/src/proxy_datastore.cpp
new file mode 100644
index 0000000..ab9193c
--- /dev/null
+++ b/src/proxy_datastore.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+#include "proxy_datastore.hpp"
+
+ProxyDatastore::ProxyDatastore(const std::shared_ptr<DatastoreAccess>& datastore)
+    : m_datastore(datastore)
+{
+}
+
+DatastoreAccess::Tree ProxyDatastore::getItems(const std::string& path) const
+{
+    return m_datastore->getItems(path);
+}
+
+void ProxyDatastore::setLeaf(const std::string& path, leaf_data_ value)
+{
+    m_datastore->setLeaf(path, value);
+}
+
+void ProxyDatastore::createItem(const std::string& path)
+{
+    m_datastore->createItem(path);
+}
+
+void ProxyDatastore::deleteItem(const std::string& path)
+{
+    m_datastore->deleteItem(path);
+}
+
+void ProxyDatastore::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
+{
+    m_datastore->moveItem(source, move);
+}
+
+void ProxyDatastore::commitChanges()
+{
+    m_datastore->commitChanges();
+}
+
+void ProxyDatastore::discardChanges()
+{
+    m_datastore->discardChanges();
+}
+
+void ProxyDatastore::copyConfig(const Datastore source, const Datastore destination)
+{
+    m_datastore->copyConfig(source, destination);
+}
+
+std::string ProxyDatastore::dump(const DataFormat format) const
+{
+    return m_datastore->dump(format);
+}
+
+std::shared_ptr<Schema> ProxyDatastore::schema() const
+{
+    return m_datastore->schema();
+}
diff --git a/src/proxy_datastore.hpp b/src/proxy_datastore.hpp
new file mode 100644
index 0000000..0ed5e2d
--- /dev/null
+++ b/src/proxy_datastore.hpp
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+
+#pragma once
+#include "datastore_access.hpp"
+
+/*! \class ProxyDatastore
+ *     \brief DatastoreAccess wrapper that handles RPC input
+ */
+class ProxyDatastore {
+public:
+    ProxyDatastore(const std::shared_ptr<DatastoreAccess>& datastore);
+    [[nodiscard]] DatastoreAccess::Tree getItems(const std::string& path) const;
+    void setLeaf(const std::string& path, leaf_data_ value);
+    void createItem(const std::string& path);
+    void deleteItem(const std::string& path);
+    void moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move);
+    void commitChanges();
+    void discardChanges();
+    void copyConfig(const Datastore source, const Datastore destination);
+    [[nodiscard]] std::string dump(const DataFormat format) const;
+
+    [[nodiscard]] std::shared_ptr<Schema> schema() const;
+private:
+    std::shared_ptr<DatastoreAccess> m_datastore;
+};
diff --git a/tests/interpreter.cpp b/tests/interpreter.cpp
index 3b9736b..89fbd29 100644
--- a/tests/interpreter.cpp
+++ b/tests/interpreter.cpp
@@ -38,7 +38,8 @@
 {
     auto schema = std::make_shared<MockSchema>();
     Parser parser(schema);
-    MockDatastoreAccess datastore;
+    auto datastore = std::make_shared<MockDatastoreAccess>();
+    ProxyDatastore proxyDatastore(datastore);
     std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
 
     std::vector<command_> toInterpret;
@@ -173,7 +174,7 @@
         }
         ls_ ls;
         ls.m_path = lsArg;
-        expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, schema()).RETURN(schema));
+        expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, schema()).RETURN(schema));
         expectations.emplace_back(NAMED_REQUIRE_CALL(*schema, availableNodes(expectedPath, Recursion::NonRecursive)).RETURN(std::set<ModuleNodePair>{}));
         toInterpret.emplace_back(ls);
     }
@@ -323,7 +324,7 @@
 
         get_ getCmd;
         getCmd.m_path = inputPath;
-        expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, getItems(expectedPathArg)).RETURN(treeReturned));
+        expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, getItems(expectedPathArg)).RETURN(treeReturned));
         toInterpret.emplace_back(getCmd);
     }
 
@@ -335,22 +336,22 @@
         SECTION("list instance")
         {
             inputPath.m_nodes = {dataNode_{{"mod"}, listElement_{"department", {{"name", "engineering"s}}}}};
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, createItem("/mod:department[name='engineering']")));
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, deleteItem("/mod:department[name='engineering']")));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:department[name='engineering']")));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:department[name='engineering']")));
         }
 
         SECTION("leaflist instance")
         {
             inputPath.m_nodes = {dataNode_{{"mod"}, leafListElement_{"addresses", "127.0.0.1"s}}};
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, createItem("/mod:addresses[.='127.0.0.1']")));
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, deleteItem("/mod:addresses[.='127.0.0.1']")));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:addresses[.='127.0.0.1']")));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:addresses[.='127.0.0.1']")));
         }
 
         SECTION("presence container")
         {
             inputPath.m_nodes = {dataNode_{{"mod"}, container_{"pContainer"}}};
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, createItem("/mod:pContainer")));
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, deleteItem("/mod:pContainer")));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, createItem("/mod:pContainer")));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:pContainer")));
         }
 
         create_ createCmd;
@@ -363,7 +364,7 @@
 
     SECTION("delete a leaf")
     {
-        expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, deleteItem("/mod:someLeaf")));
+        expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, deleteItem("/mod:someLeaf")));
         delete_ deleteCmd;
         deleteCmd.m_path = {Scope::Absolute, {dataNode_{{"mod"}, leaf_{"someLeaf"}}, }};
         toInterpret.emplace_back(deleteCmd);
@@ -371,13 +372,13 @@
 
     SECTION("commit")
     {
-        expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, commitChanges()));
+        expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, commitChanges()));
         toInterpret.emplace_back(commit_{});
     }
 
     SECTION("discard")
     {
-        expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, discardChanges()));
+        expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, discardChanges()));
         toInterpret.emplace_back(discard_{});
     }
 
@@ -391,7 +392,7 @@
         {
             inputPath.m_nodes = {dataNode_{{"mod"}, leaf_{"animal"}}};
             inputData = identityRef_{"Doge"};
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, setLeaf("/mod:animal", identityRef_{"mod", "Doge"})));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, setLeaf("/mod:animal", identityRef_{"mod", "Doge"})));
         }
 
 
@@ -406,18 +407,18 @@
     {
         SECTION("running -> startup")
         {
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Running, Datastore::Startup)));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, copyConfig(Datastore::Running, Datastore::Startup)));
             toInterpret.emplace_back(copy_{{}, Datastore::Running, Datastore::Startup});
         }
 
         SECTION("startup -> running")
         {
-            expectations.emplace_back(NAMED_REQUIRE_CALL(datastore, copyConfig(Datastore::Startup, Datastore::Running)));
+            expectations.emplace_back(NAMED_REQUIRE_CALL(*datastore, copyConfig(Datastore::Startup, Datastore::Running)));
             toInterpret.emplace_back(copy_{{}, Datastore::Startup, Datastore::Running});
         }
     }
 
     for (const auto& command : toInterpret) {
-        boost::apply_visitor(Interpreter(parser, datastore), command);
+        boost::apply_visitor(Interpreter(parser, proxyDatastore), command);
     }
 }