Migrate to libyang2
libnetconf2: getSchema and getConfig were no longer used in netconf-cli,
so I deleted them. They can get readded once the bindings get split into
a separate project.
sysrepo_access: Some sr_val stuff was removed.
YangSchema: type descriptions are not available
availableNodes returns only input nodes for RPC nodes
impl_getSchemaNode: no longer disables error printing
libyang: No longer supports leafrefs without the leaf it points to.
Depends-on: https://cesnet-gerrit-czechlight/c/CzechLight/dependencies/+/5171
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/5171
Change-Id: Ie49381a003a61a7bb028be7b2fa1d9d926ac4e58
diff --git a/.zuul.public.yaml b/.zuul.public.yaml
index 7582f50..117b3ac 100644
--- a/.zuul.public.yaml
+++ b/.zuul.public.yaml
@@ -8,16 +8,22 @@
check:
jobs:
- f34-gcc-cover:
+ pre-run: ci/pre.yaml
requires: CzechLight-deps-f34-gcc
- f34-clang-asan-ubsan:
+ pre-run: ci/pre.yaml
requires: CzechLight-deps-f34-clang-asan-ubsan
- f34-clang-tsan:
+ pre-run: ci/pre.yaml
requires: CzechLight-deps-f34-clang-tsan
- f34-gcc-netconf-cli-no-sysrepo:
+ pre-run: ci/pre.yaml
requires: CzechLight-deps-f34-gcc
- f34-cpp-coverage-diff:
+ pre-run: ci/pre.yaml
voting: false
- clang-format:
voting: false
- f34-gcc-cover-previous:
+ pre-run: ci/pre.yaml
requires: CzechLight-deps-f34-gcc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f95c615..573e582 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -77,14 +77,14 @@
message(FATAL_ERROR "Cannot combine ENABLE_FULL_TESTS=ON with BUILD_TESTING=OFF")
endif()
-pkg_check_modules(LIBYANG REQUIRED libyang-cpp>=1.0.248 IMPORTED_TARGET libyang)
-pkg_check_modules(LIBNETCONF2 REQUIRED libnetconf2>=1.1.48 IMPORTED_TARGET libnetconf2)
+pkg_check_modules(LIBYANG-CPP REQUIRED libyang-cpp>=1.0.0 IMPORTED_TARGET)
+pkg_check_modules(LIBNETCONF2 REQUIRED libnetconf2>=2.0.5 IMPORTED_TARGET)
if(ENABLE_FULL_TESTS STREQUAL OFF AND ENABLE_SYSREPO_CLI STREQUAL OFF)
message(STATUS "Skipping sysrepo per configure options")
set(SYSREPO_FOUND 0)
else()
- pkg_check_modules(SYSREPO sysrepo-cpp>=1.4.148 IMPORTED_TARGET sysrepo)
+ pkg_check_modules(SYSREPO sysrepo-cpp>=1.0.0 IMPORTED_TARGET sysrepo)
endif()
if(SYSREPO_FOUND)
@@ -165,7 +165,7 @@
src/sysrepo_access.cpp
)
- target_link_libraries(sysrepoaccess PUBLIC datastoreaccess ast_values PRIVATE PkgConfig::SYSREPO PkgConfig::LIBYANG)
+ target_link_libraries(sysrepoaccess PUBLIC datastoreaccess ast_values PkgConfig::SYSREPO PRIVATE PkgConfig::LIBYANG-CPP)
endif()
add_library(netconfaccess STATIC
@@ -173,23 +173,23 @@
src/netconf_access.cpp
)
-target_link_libraries(netconfaccess PUBLIC datastoreaccess yangschema ast_values utils PkgConfig::LIBNETCONF2 PRIVATE PkgConfig::LIBYANG)
+target_link_libraries(netconfaccess PUBLIC datastoreaccess yangschema ast_values utils PkgConfig::LIBNETCONF2 PRIVATE PkgConfig::LIBYANG-CPP)
add_library(yangaccess STATIC
src/yang_access.cpp
)
-target_link_libraries(yangaccess PUBLIC datastoreaccess yangschema PRIVATE PkgConfig::LIBYANG)
+target_link_libraries(yangaccess PUBLIC datastoreaccess yangschema PRIVATE PkgConfig::LIBYANG-CPP)
add_library(yangutils STATIC
src/libyang_utils.cpp
)
-target_link_libraries(yangutils PUBLIC PkgConfig::LIBYANG)
+target_link_libraries(yangutils PUBLIC PkgConfig::LIBYANG-CPP)
add_library(yangschema STATIC
src/yang_schema.cpp
)
-target_link_libraries(yangschema PUBLIC schemas utils yangutils PRIVATE PkgConfig::LIBYANG)
+target_link_libraries(yangschema PUBLIC schemas utils yangutils PRIVATE PkgConfig::LIBYANG-CPP)
add_library(parser STATIC
src/parser.cpp
@@ -199,7 +199,7 @@
src/ast_handlers.cpp
src/completion.cpp
)
-target_link_libraries(parser schemas utils ast_values)
+target_link_libraries(parser schemas utils ast_values yangutils)
add_library(proxydatastore STATIC
src/proxy_datastore.cpp
@@ -257,7 +257,7 @@
tests/wait-a-bit-longer.cpp
)
target_include_directories(DoctestIntegration PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/tests/ ${CMAKE_CURRENT_SOURCE_DIR}/src/)
- target_link_libraries(DoctestIntegration doctest::doctest trompeloeil)
+ target_link_libraries(DoctestIntegration doctest::doctest trompeloeil::trompeloeil)
target_compile_definitions(DoctestIntegration PUBLIC DOCTEST_CONFIG_SUPER_FAST_ASSERTS)
if(DO_ENABLE_NETCONF_TESTS)
diff --git a/ci/pre.yaml b/ci/pre.yaml
new file mode 100644
index 0000000..4c71e88
--- /dev/null
+++ b/ci/pre.yaml
@@ -0,0 +1,7 @@
+- hosts: all
+ tasks:
+ - name: install pcre2
+ package:
+ name: pcre2-devel
+ state: present
+ become: true
diff --git a/src/cli.cpp b/src/cli.cpp
index fec77d1..24ed37e 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -138,6 +138,7 @@
}
if (const auto& enableFeatures = args["-e"]) {
namespace x3 = boost::spirit::x3;
+ std::map<std::string, std::vector<std::string>> toEnable;
auto grammar = +(x3::char_-":") >> ":" >> +(x3::char_-":");
for (const auto& enableFeature : enableFeatures.asStringList()) {
std::pair<std::string, std::string> parsed;
@@ -147,12 +148,15 @@
std::cerr << "Error parsing feature enable flags: " << enableFeature << "\n";
return 1;
}
- try {
- datastore->enableFeature(parsed.first, parsed.second);
- } catch (std::runtime_error& ex) {
- std::cerr << ex.what() << "\n";
- return 1;
+ toEnable[parsed.first].emplace_back(parsed.second);
+ }
+ try {
+ for (const auto& [moduleName, features] : toEnable) {
+ datastore->setEnabledFeatures(moduleName, features);
}
+ } catch (std::runtime_error& ex) {
+ std::cerr << ex.what() << "\n";
+ return 1;
}
}
diff --git a/src/libyang_utils.cpp b/src/libyang_utils.cpp
index a6b22cd..f3881fe 100644
--- a/src/libyang_utils.cpp
+++ b/src/libyang_utils.cpp
@@ -1,130 +1,131 @@
#include <boost/algorithm/string/predicate.hpp>
#include <cmath>
+#include <libyang-cpp/Context.hpp>
#include "datastore_access.hpp"
#include "libyang_utils.hpp"
#include "utils.hpp"
-leaf_data_ leafValueFromNode(libyang::S_Data_Node_Leaf_List node)
+struct impl_leafValueFromNode {
+ leaf_data_ operator()(const libyang::Empty) const
+ {
+ return empty_{};
+ }
+
+ leaf_data_ operator()(const libyang::Binary& bin) const
+ {
+ return binary_{std::string{bin.base64}};
+ }
+
+ leaf_data_ operator()(const std::vector<libyang::Bit>& bits) const
+ {
+ bits_ res;
+ std::transform(bits.begin(), bits.end(), std::back_inserter(res.m_bits), [] (const libyang::Bit& bit) {
+ return bit.name;
+ });
+ return res;
+ }
+
+ leaf_data_ operator()(const libyang::Enum& enumVal) const
+ {
+ return enum_{enumVal.name};
+ }
+
+ leaf_data_ operator()(const libyang::IdentityRef& identRef) const
+ {
+ return identityRef_{identRef.module, identRef.name};
+ }
+
+ leaf_data_ operator()(const libyang::Decimal64& dec) const
+ {
+ return dec.number * std::pow(10, -dec.digits);
+ }
+
+ leaf_data_ operator()(const std::optional<libyang::DataNode>&) const
+ {
+ throw std::runtime_error("instance-identifier is not supported");
+ }
+
+ template <typename Type>
+ leaf_data_ operator()(const Type& val) const
+ {
+ return val;
+ }
+};
+
+leaf_data_ leafValueFromNode(libyang::DataNodeTerm node)
{
- std::function<leaf_data_(libyang::S_Data_Node_Leaf_List)> impl = [&impl](libyang::S_Data_Node_Leaf_List node) -> leaf_data_ {
- // value_type() is what's ACTUALLY stored inside `node`
- // Leafrefs sometimes don't hold a reference to another, but they have the actual pointed-to value.
- switch (node->value_type()) {
- case LY_TYPE_ENUM:
- return enum_{node->value()->enm()->name()};
- case LY_TYPE_UINT8:
- return node->value()->uint8();
- case LY_TYPE_UINT16:
- return node->value()->uint16();
- case LY_TYPE_UINT32:
- return node->value()->uint32();
- case LY_TYPE_UINT64:
- return node->value()->uint64();
- case LY_TYPE_INT8:
- return node->value()->int8();
- case LY_TYPE_INT16:
- return node->value()->int16();
- case LY_TYPE_INT32:
- return node->value()->int32();
- case LY_TYPE_INT64:
- return node->value()->int64();
- case LY_TYPE_DEC64: {
- auto v = node->value()->dec64();
- return v.value * std::pow(10, -v.digits);
- }
- case LY_TYPE_BOOL:
- return node->value()->bln();
- case LY_TYPE_STRING:
- return std::string{node->value()->string()};
- case LY_TYPE_BINARY:
- return binary_{node->value()->binary()};
- case LY_TYPE_IDENT:
- return identityRef_{node->value()->ident()->module()->name(), node->value()->ident()->name()};
- case LY_TYPE_EMPTY:
- return empty_{};
- case LY_TYPE_LEAFREF: {
- auto refsTo = node->value()->leafref();
- assert(refsTo);
- return impl(std::make_shared<libyang::Data_Node_Leaf_List>(node->value()->leafref()));
- }
- case LY_TYPE_BITS: {
- auto bits = node->value()->bit();
- std::vector<libyang::S_Type_Bit> filterNull;
- std::copy_if(bits.begin(), bits.end(), std::back_inserter(filterNull), [](auto bit) { return bit; });
- bits_ res;
- std::transform(filterNull.begin(), filterNull.end(), std::inserter(res.m_bits, res.m_bits.end()), [](const auto& bit) { return bit->name(); });
- return bits_{res};
- }
- default:
- return std::string{"(can't print)"};
- }
- };
- return impl(node);
+ return std::visit(impl_leafValueFromNode{},node.value());
}
namespace {
-void impl_lyNodesToTree(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items, std::optional<std::string> ignoredXPathPrefix)
+template <typename CollectionType>
+void impl_lyNodesToTree(DatastoreAccess::Tree& res, CollectionType items, std::optional<std::string> ignoredXPathPrefix)
{
auto stripXPathPrefix = [&ignoredXPathPrefix](auto path) {
return ignoredXPathPrefix && path.find(*ignoredXPathPrefix) != std::string::npos ? path.substr(ignoredXPathPrefix->size()) : path;
};
for (const auto& it : items) {
- if (it->schema()->nodetype() == LYS_CONTAINER) {
- if (libyang::Schema_Node_Container{it->schema()}.presence()) {
+ if (it.schema().nodeType() == libyang::NodeType::Container) {
+ if (it.schema().asContainer().isPresence()) {
// The fact that the container is included in the data tree
// means that it is present and I don't need to check any
// value.
- res.emplace_back(stripXPathPrefix(it->path()), special_{SpecialValue::PresenceContainer});
+ res.emplace_back(stripXPathPrefix(std::string{it.path()}), special_{SpecialValue::PresenceContainer});
}
}
- if (it->schema()->nodetype() == LYS_LIST) {
- res.emplace_back(stripXPathPrefix(it->path()), special_{SpecialValue::List});
+ if (it.schema().nodeType() == libyang::NodeType::List) {
+ res.emplace_back(stripXPathPrefix(std::string{it.path()}), special_{SpecialValue::List});
}
- if (it->schema()->nodetype() == LYS_LEAF || it->schema()->nodetype() == LYS_LEAFLIST) {
- auto leaf = std::make_shared<libyang::Data_Node_Leaf_List>(it);
- auto value = leafValueFromNode(leaf);
- res.emplace_back(stripXPathPrefix(it->path()), value);
+ if (it.schema().nodeType() == libyang::NodeType::Leaf || it.schema().nodeType() == libyang::NodeType::Leaflist) {
+ auto term = it.asTerm();
+ auto value = leafValueFromNode(term);
+ res.emplace_back(stripXPathPrefix(std::string{it.path()}), value);
}
}
}
}
-void lyNodesToTree(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items, std::optional<std::string> ignoredXPathPrefix)
+template <typename CollectionType>
+void lyNodesToTree(DatastoreAccess::Tree& res, CollectionType items, std::optional<std::string> ignoredXPathPrefix)
{
- for (auto it = items.begin(); it < items.end(); it++) {
- if ((*it)->schema()->nodetype() == LYS_LEAFLIST) {
- auto leafListPath = stripLeafListValueFromPath((*it)->path());
+ for (auto it = items.begin(); it != items.end(); /* nothing */) {
+ if ((*it).schema().nodeType() == libyang::NodeType::Leaflist) {
+ auto leafListPath = stripLeafListValueFromPath(std::string{(*it).path()});
res.emplace_back(leafListPath, special_{SpecialValue::LeafList});
- while (it != items.end() && boost::starts_with((*it)->path(), leafListPath)) {
- impl_lyNodesToTree(res, (*it)->tree_dfs(), ignoredXPathPrefix);
+ while (it != items.end() && boost::starts_with(std::string{(*it).path()}, leafListPath)) {
+ impl_lyNodesToTree(res, it->childrenDfs(), ignoredXPathPrefix);
it++;
}
} else {
- impl_lyNodesToTree(res, (*it)->tree_dfs(), ignoredXPathPrefix);
+ impl_lyNodesToTree(res, it->childrenDfs(), ignoredXPathPrefix);
+ it++;
}
}
}
-DatastoreAccess::Tree rpcOutputToTree(const std::string& rpcPath, libyang::S_Data_Node output)
+using SiblingColl = libyang::Collection<libyang::DataNode, libyang::IterationType::Sibling>;
+using DfsColl = libyang::Collection<libyang::DataNode, libyang::IterationType::Dfs>;
+
+template
+void lyNodesToTree<SiblingColl>(DatastoreAccess::Tree& res, SiblingColl items, std::optional<std::string> ignoredXPathPrefix);
+template
+void lyNodesToTree<DfsColl>(DatastoreAccess::Tree& res, DfsColl items, std::optional<std::string> ignoredXPathPrefix);
+template
+void lyNodesToTree<libyang::Set<libyang::DataNode>>(DatastoreAccess::Tree& res, libyang::Set<libyang::DataNode> items, std::optional<std::string> ignoredXPathPrefix);
+
+DatastoreAccess::Tree rpcOutputToTree(libyang::DataNode 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(), "/"));
- }
+ lyNodesToTree(res, output.siblings(), joinPaths(std::string{output.path()}, "/"));
return res;
}
-libyang::S_Data_Node treeToRpcInput(libyang::S_Context ctx, const std::string& path, DatastoreAccess::Tree in)
+libyang::DataNode treeToRpcInput(libyang::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);
+ auto root = ctx.newPath(path.c_str(), nullptr, libyang::CreationOptions::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);
+ root.newPath(k.c_str(), leafDataToString(v).c_str(), libyang::CreationOptions::Update);
}
return root;
diff --git a/src/libyang_utils.hpp b/src/libyang_utils.hpp
index b5f5e96..8a470c7 100644
--- a/src/libyang_utils.hpp
+++ b/src/libyang_utils.hpp
@@ -5,11 +5,12 @@
*
*/
-#include <libyang/Tree_Data.hpp>
+#include <libyang-cpp/DataNode.hpp>
#include "ast_values.hpp"
#include "datastore_access.hpp"
-leaf_data_ leafValueFromNode(libyang::S_Data_Node_Leaf_List node);
-void lyNodesToTree(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items, std::optional<std::string> ignoredXPathPrefix = std::nullopt);
-libyang::S_Data_Node treeToRpcInput(libyang::S_Context ctx, const std::string& path, DatastoreAccess::Tree in);
-DatastoreAccess::Tree rpcOutputToTree(const std::string& rpcPath, libyang::S_Data_Node output);
+leaf_data_ leafValueFromNode(libyang::DataNodeTerm node);
+template <typename CollectionType>
+void lyNodesToTree(DatastoreAccess::Tree& res, CollectionType items, std::optional<std::string> ignoredXPathPrefix = std::nullopt);
+libyang::DataNode treeToRpcInput(libyang::Context ctx, const std::string& path, DatastoreAccess::Tree in);
+DatastoreAccess::Tree rpcOutputToTree(libyang::DataNode output);
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
index 4638bcb..0685db6 100644
--- a/src/netconf-client.cpp
+++ b/src/netconf-client.cpp
@@ -7,7 +7,8 @@
*/
#include <cstring>
-#include <libyang/Tree_Data.hpp>
+#include <libyang-cpp/Context.hpp>
+#include <libyang-cpp/DataNode.hpp>
#include <mutex>
extern "C" {
#include <nc_client.h>
@@ -58,15 +59,6 @@
static std::mutex clientOptions;
-inline void custom_free_nc_reply_data(nc_reply_data* reply)
-{
- nc_reply_free(reinterpret_cast<nc_reply*>(reply));
-}
-inline void custom_free_nc_reply_error(nc_reply_error* reply)
-{
- nc_reply_free(reinterpret_cast<nc_reply*>(reply));
-}
-
char* ssh_auth_interactive_cb(const char* auth_name, const char* instruction, const char* prompt, int echo, void* priv)
{
const auto cb = static_cast<const client::KbdInteractiveCb*>(priv);
@@ -74,24 +66,19 @@
return ::strdup(res.c_str());
}
-template <typename Type> using deleter_type_for = void (*)(Type*);
-template <typename Type> deleter_type_for<Type> const deleter_for;
-
-template <> const auto deleter_for<nc_rpc> = nc_rpc_free;
-template <> const auto deleter_for<nc_reply> = nc_reply_free;
-template <> const auto deleter_for<nc_reply_data> = custom_free_nc_reply_data;
-template <> const auto deleter_for<nc_reply_error> = custom_free_nc_reply_error;
-
-template <typename T>
-using unique_ptr_for = std::unique_ptr<T, decltype(deleter_for<T>)>;
-
-template <typename T>
-auto guarded(T* ptr)
+auto guarded(nc_rpc* ptr)
{
- return unique_ptr_for<T>(ptr, deleter_for<T>);
+ return std::unique_ptr<nc_rpc, decltype(&nc_rpc_free)>(ptr, nc_rpc_free);
}
-unique_ptr_for<struct nc_reply> do_rpc(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
+namespace {
+const auto getData_path = "/ietf-netconf-nmda:get-data/data";
+const auto get_path = "/ietf-netconf:get/data";
+}
+
+using managed_rpc = std::invoke_result_t<decltype(guarded), nc_rpc*>;
+
+std::optional<libyang::DataNode> do_rpc(client::Session* session, managed_rpc&& rpc, const char* dataIdentifier)
{
uint64_t msgid;
NC_MSG_TYPE msgtype;
@@ -104,11 +91,11 @@
throw std::runtime_error{"Timeout sending an RPC"};
}
- struct nc_reply* raw_reply;
+ lyd_node* raw_reply;
+ lyd_node* envp;
while (true) {
- msgtype = nc_recv_reply(session->session_internal(), rpc.get(), msgid, 20000, LYD_OPT_DESTRUCT | LYD_OPT_NOSIBLINGS, &raw_reply);
- auto reply = guarded(raw_reply);
- raw_reply = nullptr;
+ msgtype = nc_recv_reply(session->session_internal(), rpc.get(), msgid, 20000, &envp, &raw_reply);
+ auto replyInfo = libyang::wrapRawNode(envp);
switch (msgtype) {
case NC_MSG_ERROR:
@@ -120,76 +107,75 @@
case NC_MSG_NOTIF:
continue;
default:
- return reply;
+ if (!raw_reply) { // <ok> reply, or empty data node, or error
+ std::string msg;
+ for (const auto& child : replyInfo.child()->siblings()) {
+ if (child.asOpaque().name().name == "rpc-error") {
+ for (const auto& error : child.childrenDfs()) {
+ if (error.asOpaque().name().name == "error-message") {
+ msg += "Error: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-path") {
+ msg += "Path: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-type") {
+ msg += "Type: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-tag") {
+ msg += "Tag: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-app-tag") {
+ msg += "App-tag: ";
+ msg += error.asOpaque().value();
+ }
+ }
+
+ msg += "\n";
+ }
+ }
+
+ if (!msg.empty()) {
+ throw client::ReportedError{msg};
+ }
+
+ return std::nullopt;
+ }
+ auto wrapped = libyang::wrapRawNode(raw_reply);
+
+ // If we have a dataIdentifier, then we'll need to look for it.
+ // Some operations don't have that, and then the result data are just the wrapped node.
+ if (!dataIdentifier) {
+ return wrapped;
+ }
+
+ auto anydataValue = wrapped.findPath(dataIdentifier, libyang::OutputNodes::Yes)->asAny().releaseValue();
+
+ // If there's no anydata value, then that means we get empty (but valid) data.
+ if (!anydataValue) {
+ return std::nullopt;
+ }
+
+ return std::get<libyang::DataNode>(*anydataValue);
}
}
__builtin_unreachable();
}
-client::ReportedError make_error(unique_ptr_for<struct nc_reply>&& reply)
+void do_rpc_ok(client::Session* session, managed_rpc&& rpc)
{
- if (reply->type != NC_RPL_ERROR) {
- throw std::logic_error{"Cannot extract an error from a non-error server reply"};
- }
-
- auto errorReply = guarded(reinterpret_cast<struct nc_reply_error*>(reply.release()));
-
- // TODO: capture the error details, not just that human-readable string
- std::ostringstream ss;
- ss << "Server error:" << std::endl;
- for (uint32_t i = 0; i < errorReply->count; ++i) {
- const auto e = errorReply->err[i];
- ss << " #" << i << ": " << e.message;
- if (e.path) {
- ss << " (XPath " << e.path << ")";
- }
- ss << std::endl;
- }
- return client::ReportedError{ss.str()};
-}
-
-std::optional<unique_ptr_for<struct nc_reply_data>> do_rpc_data_or_ok(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
- auto x = do_rpc(session, std::move(rpc));
-
- switch (x->type) {
- case NC_RPL_DATA:
- return guarded(reinterpret_cast<struct nc_reply_data*>(x.release()));
- case NC_RPL_OK:
- return std::nullopt;
- case NC_RPL_ERROR:
- throw make_error(std::move(x));
- default:
- throw std::runtime_error{"Unhandled reply type"};
- }
-}
-
-unique_ptr_for<struct nc_reply_data> do_rpc_data(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
- auto x = do_rpc_data_or_ok(session, std::move(rpc));
- if (!x) {
- throw std::runtime_error{"Received OK instead of a data reply"};
- }
- return std::move(*x);
-}
-
-void do_rpc_ok(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
- auto x = do_rpc_data_or_ok(session, std::move(rpc));
+ auto x = do_rpc(session, std::move(rpc), nullptr);
if (x) {
throw std::runtime_error{"Unexpected DATA reply"};
}
}
-
-std::shared_ptr<libyang::Data_Node> do_get(client::Session* session, unique_ptr_for<nc_rpc> rpc)
-{
- auto reply = impl::do_rpc_data(session, std::move(rpc));
- auto dataNode = libyang::create_new_Data_Node(reply->data);
- // TODO: can we do without copying?
- // If we just default-construct a new node (or use the create_new_Data_Node) and then set reply->data to nullptr,
- // there are mem leaks and even libnetconf2 complains loudly.
- return dataNode ? dataNode->dup_withsiblings(1) : nullptr;
-}
}
namespace client {
@@ -210,9 +196,9 @@
return m_session;
}
-libyang::S_Context Session::libyangContext()
+libyang::Context Session::libyangContext()
{
- return std::make_shared<libyang::Context>(nc_session_get_ctx(m_session), nullptr);
+ return libyang::createUnmanagedContext(const_cast<ly_ctx*>(nc_session_get_ctx(m_session)), nullptr);
}
Session::Session(struct nc_session* session)
@@ -298,13 +284,13 @@
return res;
}
-std::shared_ptr<libyang::Data_Node> Session::get(const std::optional<std::string>& filter)
+std::optional<libyang::DataNode> Session::get(const std::optional<std::string>& filter)
{
auto rpc = impl::guarded(nc_rpc_get(filter ? filter->c_str() : nullptr, NC_WD_ALL, NC_PARAMTYPE_CONST));
if (!rpc) {
throw std::runtime_error("Cannot create get RPC");
}
- return impl::do_get(this, std::move(rpc));
+ return impl::do_rpc(this, std::move(rpc), impl::get_path);
}
const char* datastoreToString(NmdaDatastore datastore)
@@ -322,13 +308,13 @@
__builtin_unreachable();
}
-std::shared_ptr<libyang::Data_Node> Session::getData(const NmdaDatastore datastore, const std::optional<std::string>& filter)
+std::optional<libyang::DataNode> Session::getData(const NmdaDatastore datastore, const std::optional<std::string>& filter)
{
auto rpc = impl::guarded(nc_rpc_getdata(datastoreToString(datastore), filter ? filter->c_str() : nullptr, nullptr, nullptr, 0, 0, 0, 0, NC_WD_ALL, NC_PARAMTYPE_CONST));
if (!rpc) {
throw std::runtime_error("Cannot create get RPC");
}
- return impl::do_get(this, std::move(rpc));
+ return impl::do_rpc(this, std::move(rpc), impl::getData_path);
}
void Session::editData(const NmdaDatastore datastore, const std::string& data)
@@ -340,34 +326,6 @@
return impl::do_rpc_ok(this, std::move(rpc));
}
-std::string Session::getSchema(const std::string_view identifier, const std::optional<std::string_view> version)
-{
- auto rpc = impl::guarded(nc_rpc_getschema(identifier.data(), version ? version.value().data() : nullptr, nullptr, NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create get-schema RPC");
- }
- auto reply = impl::do_rpc_data(this, std::move(rpc));
-
- auto node = libyang::create_new_Data_Node(reply->data)->dup_withsiblings(1);
- auto set = node->find_path("data");
- for (auto node : set->data()) {
- if (node->schema()->nodetype() == LYS_ANYXML) {
- libyang::Data_Node_Anydata anydata(node);
- return anydata.value().str;
- }
- }
- throw std::runtime_error("Got a reply to get-schema, but it didn't contain the required schema");
-}
-
-std::shared_ptr<libyang::Data_Node> Session::getConfig(const NC_DATASTORE datastore, const std::optional<const std::string> filter)
-{
- auto rpc = impl::guarded(nc_rpc_getconfig(datastore, filter ? filter->c_str() : nullptr, NC_WD_ALL, NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create get-config RPC");
- }
- return impl::do_get(this, std::move(rpc));
-}
-
void Session::editConfig(const NC_DATASTORE datastore,
const NC_RPC_EDIT_DFLTOP defaultOperation,
const NC_RPC_EDIT_TESTOPT testOption,
@@ -408,19 +366,14 @@
impl::do_rpc_ok(this, std::move(rpc));
}
-std::shared_ptr<libyang::Data_Node> Session::rpc_or_action(const std::string& xmlData)
+std::optional<libyang::DataNode> Session::rpc_or_action(const std::string& xmlData)
{
auto rpc = impl::guarded(nc_rpc_act_generic_xml(xmlData.c_str(), NC_PARAMTYPE_CONST));
if (!rpc) {
throw std::runtime_error("Cannot create generic RPC");
}
- auto reply = impl::do_rpc_data_or_ok(this, std::move(rpc));
- if (reply) {
- auto dataNode = libyang::create_new_Data_Node((*reply)->data);
- return dataNode->dup_withsiblings(1);
- } else {
- return nullptr;
- }
+
+ return impl::do_rpc(this, std::move(rpc), nullptr);
}
void Session::copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination)
diff --git a/src/netconf-client.hpp b/src/netconf-client.hpp
index 839c528..5a16cf2 100644
--- a/src/netconf-client.hpp
+++ b/src/netconf-client.hpp
@@ -12,8 +12,8 @@
struct nc_session;
namespace libyang {
-class Data_Node;
class Context;
+class DataNode;
}
namespace libnetconf {
@@ -46,11 +46,8 @@
static std::unique_ptr<Session> connectSocket(const std::string& path);
static std::unique_ptr<Session> connectFd(const int source, const int sink);
[[nodiscard]] std::vector<std::string_view> capabilities() const;
- std::shared_ptr<libyang::Data_Node> getConfig(const NC_DATASTORE datastore,
- const std::optional<const std::string> filter = std::nullopt); // TODO: arguments...
- std::shared_ptr<libyang::Data_Node> get(const std::optional<std::string>& filter = std::nullopt);
- std::shared_ptr<libyang::Data_Node> getData(const NmdaDatastore datastore, const std::optional<std::string>& filter = std::nullopt);
- std::string getSchema(const std::string_view identifier, const std::optional<std::string_view> version);
+ std::optional<libyang::DataNode> get(const std::optional<std::string>& filter = std::nullopt);
+ std::optional<libyang::DataNode> getData(const NmdaDatastore datastore, const std::optional<std::string>& filter = std::nullopt);
void editConfig(const NC_DATASTORE datastore,
const NC_RPC_EDIT_DFLTOP defaultOperation,
const NC_RPC_EDIT_TESTOPT testOption,
@@ -58,12 +55,12 @@
const std::string& data);
void editData(const NmdaDatastore datastore, const std::string& data);
void copyConfigFromString(const NC_DATASTORE target, const std::string& data);
- std::shared_ptr<libyang::Data_Node> rpc_or_action(const std::string& xmlData);
+ std::optional<libyang::DataNode> rpc_or_action(const std::string& xmlData);
void copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination);
void commit();
void discard();
- std::shared_ptr<libyang::Context> libyangContext();
+ libyang::Context libyangContext();
struct nc_session* session_internal(); // FIXME: remove me
protected:
struct nc_session* m_session;
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index a6dbb7c..2b76e50 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -5,8 +5,6 @@
*
*/
-#include <libyang/Libyang.hpp>
-#include <libyang/Tree_Data.hpp>
#include "libyang_utils.hpp"
#include "netconf-client.hpp"
#include "netconf_access.hpp"
@@ -55,7 +53,7 @@
}();
if (config) {
- lyNodesToTree(res, config->tree_for());
+ lyNodesToTree(res, config->siblings());
}
return res;
}
@@ -107,22 +105,28 @@
void NetconfAccess::setLeaf(const std::string& path, leaf_data_ value)
{
auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
- auto node = m_schema->dataNodeFromPath(path, lyValue);
- doEditFromDataNode(node);
+ auto nodes = m_schema->dataNodeFromPath(path, lyValue);
+ doEditFromDataNode(*nodes.createdParent);
}
void NetconfAccess::createItem(const std::string& path)
{
- auto node = m_schema->dataNodeFromPath(path);
- doEditFromDataNode(node);
+ auto nodes = m_schema->dataNodeFromPath(path);
+ doEditFromDataNode(*nodes.createdParent);
}
void NetconfAccess::deleteItem(const std::string& path)
{
- auto node = m_schema->dataNodeFromPath(path);
- auto container = *(node->find_path(path.c_str())->data().begin());
- container->insert_attr(m_schema->getYangModule("ietf-netconf"), "operation", "delete");
- doEditFromDataNode(node);
+ auto nodes = m_schema->dataNodeFromPath(path);
+
+ // When deleting leafs, `nodes.newNode` is opaque, because the leaf does not have a value. We need to use
+ // newAttrOpaqueJSON for opaque leafs.
+ if (nodes.createdNode->isOpaque()) {
+ nodes.createdNode->newAttrOpaqueJSON("ietf-netconf", "ietf-netconf:operation", "delete");
+ } else {
+ nodes.createdNode->newMeta(*m_schema->getYangModule("ietf-netconf"), "operation", "delete");
+ }
+ doEditFromDataNode(*nodes.createdParent);
}
struct impl_toYangInsert {
@@ -143,29 +147,29 @@
void NetconfAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
{
- auto node = m_schema->dataNodeFromPath(source);
- auto sourceNode = *(node->find_path(source.c_str())->data().begin());
- auto yangModule = m_schema->getYangModule("yang");
- sourceNode->insert_attr(yangModule, "insert", toYangInsert(move).c_str());
+ auto nodes = m_schema->dataNodeFromPath(source);
+ auto sourceNode = *(nodes.createdNode->findPath(source.c_str()));
+ auto yangModule = *m_schema->getYangModule("yang");
+ sourceNode.newMeta(yangModule, "insert", toYangInsert(move).c_str());
if (std::holds_alternative<yang::move::Relative>(move)) {
auto relative = std::get<yang::move::Relative>(move);
if (m_schema->nodeType(source) == yang::NodeTypes::LeafList) {
- sourceNode->insert_attr(yangModule, "value", leafDataToString(relative.m_path.at(".")).c_str());
+ sourceNode.newMeta(yangModule, "value", leafDataToString(relative.m_path.at(".")).c_str());
} else {
- sourceNode->insert_attr(yangModule, "key", instanceToString(relative.m_path, node->node_module()->name()).c_str());
+ sourceNode.newMeta(yangModule, "key", instanceToString(relative.m_path, std::string{nodes.createdNode->schema().module().name()}).c_str());
}
}
doEditFromDataNode(sourceNode);
}
-void NetconfAccess::doEditFromDataNode(std::shared_ptr<libyang::Data_Node> dataNode)
+void NetconfAccess::doEditFromDataNode(libyang::DataNode dataNode)
{
- auto data = dataNode->print_mem(LYD_XML, 0);
+ auto data = dataNode.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings);
if (m_serverHasNMDA) {
- m_session->editData(targetToDs_set(m_target), data);
+ m_session->editData(targetToDs_set(m_target), std::string{*data});
} else {
- m_session->editConfig(NC_DATASTORE_CANDIDATE, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_TESTSET, NC_RPC_EDIT_ERROPT_STOP, data);
+ m_session->editConfig(NC_DATASTORE_CANDIDATE, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_TESTSET, NC_RPC_EDIT_ERROPT_STOP, std::string{*data});
}
}
@@ -182,10 +186,13 @@
DatastoreAccess::Tree NetconfAccess::execute(const std::string& path, const Tree& input)
{
auto inputNode = treeToRpcInput(m_session->libyangContext(), path, input);
- auto data = inputNode->print_mem(LYD_XML, 0);
+ auto data = inputNode.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings);
- auto output = m_session->rpc_or_action(data);
- return rpcOutputToTree(path, output);
+ auto output = m_session->rpc_or_action(std::string{*data});
+ if (!output) {
+ return {};
+ }
+ return rpcOutputToTree(*output);
}
NC_DATASTORE toNcDatastore(Datastore datastore)
@@ -212,41 +219,41 @@
std::vector<ListInstance> NetconfAccess::listInstances(const std::string& path)
{
std::vector<ListInstance> res;
- auto list = m_schema->dataNodeFromPath(path);
+ auto keys = m_session->libyangContext().findXPath(path.c_str()).front().asList().keys();
+ auto nodes = m_session->libyangContext().newPath2(path.c_str(), nullptr, libyang::CreationOptions::Opaque);
- // This inserts selection nodes - I only want keys not other data
- // To get the keys, I have to call find_path here - otherwise I would get keys of a top-level node (which might not even be a list)
- auto keys = libyang::Schema_Node_List{(*(list->find_path(path.c_str())->data().begin()))->schema()}.keys();
+ // Here we create a tree with "selection leafs" for all they keys of our wanted list. These leafs tell NETCONF, that
+ // we only want the list's keys and not any other data.
for (const auto& keyLeaf : keys) {
- // Have to call find_path here - otherwise I'll have the list, not the leaf inside it
- auto selectionLeaf = *(m_schema->dataNodeFromPath(keyLeaf->path())->find_path(keyLeaf->path().c_str())->data().begin());
- auto actualList = *(list->find_path(path.c_str())->data().begin());
- actualList->insert(selectionLeaf);
+ // Selection leafs need to be inserted directly to the list using relative paths, that's why `newNode` is used
+ // here.
+ nodes.createdNode->newPath(keyLeaf.name().data(), nullptr, libyang::CreationOptions::Opaque);
}
- auto instances = m_session->get(list->print_mem(LYD_XML, 0));
+ // Have to use `newParent` in case our wanted list is a nested list. With `newNode` I would only send the inner
+ // nested list and not the whole tree.
+ auto instances = m_session->get(std::string{*nodes.createdParent->printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings)});
if (!instances) {
return res;
}
- for (const auto& instance : instances->find_path(path.c_str())->data()) {
+ for (const auto& instance : instances->findXPath(path.c_str())) {
ListInstance instanceRes;
- // I take the first child here, because the first element (the parent of the child()) will be the list
- for (const auto& keyLeaf : instance->child()->tree_for()) {
+ for (const auto& keyLeaf : instance.child()->siblings()) {
// FIXME: even though we specified we only want the key leafs, Netopeer disregards that and sends more data,
// even lists and other stuff. We only want keys, so filter out non-leafs and non-keys
// https://github.com/CESNET/netopeer2/issues/825
- if (keyLeaf->schema()->nodetype() != LYS_LEAF) {
+ if (keyLeaf.schema().nodeType() != libyang::NodeType::Leaf) {
continue;
}
- if (!std::make_shared<libyang::Schema_Node_Leaf>(keyLeaf->schema())->is_key()) {
+ if (!keyLeaf.schema().asLeaf().isKey()) {
continue;
}
- auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(keyLeaf);
- instanceRes.insert({leafData->schema()->name(), leafValueFromNode(leafData)});
+ auto leafData = keyLeaf.asTerm();
+ instanceRes.insert({std::string{leafData.schema().name()}, leafValueFromNode(leafData)});
}
res.emplace_back(instanceRes);
}
@@ -260,5 +267,10 @@
if (!config) {
return "";
}
- return config->print_mem(format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
+ auto str = config->printStr(format == DataFormat::Xml ? libyang::DataFormat::XML : libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
+ if (!str) {
+ return "";
+ }
+
+ return std::string{*str};
}
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 43ca1e8..eb678d5 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -21,10 +21,6 @@
}
}
-namespace libyang {
-class Data_Node;
-}
-
class Schema;
class YangSchema;
@@ -57,7 +53,7 @@
private:
std::vector<ListInstance> listInstances(const std::string& path) override;
- void doEditFromDataNode(std::shared_ptr<libyang::Data_Node> dataNode);
+ void doEditFromDataNode(libyang::DataNode dataNode);
void checkNMDA();
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index bbdf6d1..34d255a 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -7,86 +7,34 @@
*/
#include <experimental/iterator>
-#include <libyang/Tree_Data.hpp>
-#include <libyang/Tree_Schema.hpp>
#include <sstream>
#include <sysrepo-cpp/Session.hpp>
+#include <sysrepo-cpp/utils/exception.hpp>
#include "libyang_utils.hpp"
#include "sysrepo_access.hpp"
#include "utils.hpp"
#include "yang_schema.hpp"
-const auto OPERATION_TIMEOUT_MS = 1000;
-
-struct valFromValue : boost::static_visitor<sysrepo::S_Val> {
- sysrepo::S_Val operator()(const enum_& value) const
- {
- return std::make_shared<sysrepo::Val>(value.m_value.c_str(), SR_ENUM_T);
- }
-
- sysrepo::S_Val operator()(const binary_& value) const
- {
- return std::make_shared<sysrepo::Val>(value.m_value.c_str(), SR_BINARY_T);
- }
-
- sysrepo::S_Val operator()(const empty_) const
- {
- return std::make_shared<sysrepo::Val>(nullptr, SR_LEAF_EMPTY_T);
- }
-
- sysrepo::S_Val operator()(const identityRef_& value) const
- {
- auto res = value.m_prefix ? (value.m_prefix.value().m_name + ":" + value.m_value) : value.m_value;
- return std::make_shared<sysrepo::Val>(res.c_str(), SR_IDENTITYREF_T);
- }
-
- sysrepo::S_Val operator()(const special_& value) const
- {
- throw std::runtime_error("Tried constructing S_Val from a " + specialValueToString(value));
- }
-
- sysrepo::S_Val operator()(const std::string& value) const
- {
- return std::make_shared<sysrepo::Val>(value.c_str());
- }
-
- sysrepo::S_Val operator()(const bits_& value) const
- {
- std::stringstream ss;
- std::copy(value.m_bits.begin(), value.m_bits.end(), std::experimental::make_ostream_joiner(ss, " "));
- return std::make_shared<sysrepo::Val>(ss.str().c_str(), SR_BITS_T);
- }
-
- template <typename T>
- sysrepo::S_Val operator()(const T& value) const
- {
- return std::make_shared<sysrepo::Val>(value);
- }
-};
+const auto OPERATION_TIMEOUT_MS = std::chrono::milliseconds{1000};
SysrepoAccess::~SysrepoAccess() = default;
-sr_datastore_t toSrDatastore(Datastore datastore)
+sysrepo::Datastore toSrDatastore(Datastore datastore)
{
switch (datastore) {
case Datastore::Running:
- return SR_DS_RUNNING;
+ return sysrepo::Datastore::Running;
case Datastore::Startup:
- return SR_DS_STARTUP;
+ return sysrepo::Datastore::Startup;
}
__builtin_unreachable();
}
SysrepoAccess::SysrepoAccess()
- : m_connection(std::make_shared<sysrepo::Connection>())
- , m_session(std::make_shared<sysrepo::Session>(m_connection))
- , m_schema(std::make_shared<YangSchema>(m_session->get_context()))
+ : m_connection()
+ , m_session(m_connection.sessionStart())
+ , m_schema(std::make_shared<YangSchema>(m_session.getContext()))
{
- try {
- m_session = std::make_shared<sysrepo::Session>(m_connection);
- } catch (sysrepo::sysrepo_exception& ex) {
- reportErrors();
- }
}
namespace {
@@ -94,11 +42,11 @@
{
switch (target) {
case DatastoreTarget::Operational:
- return SR_DS_OPERATIONAL;
+ return sysrepo::Datastore::Operational;
case DatastoreTarget::Running:
- return SR_DS_RUNNING;
+ return sysrepo::Datastore::Running;
case DatastoreTarget::Startup:
- return SR_DS_STARTUP;
+ return sysrepo::Datastore::Startup;
}
__builtin_unreachable();
@@ -110,9 +58,9 @@
case DatastoreTarget::Operational:
case DatastoreTarget::Running:
// TODO: Doing candidate here doesn't work, why?
- return SR_DS_RUNNING;
+ return sysrepo::Datastore::Running;
case DatastoreTarget::Startup:
- return SR_DS_STARTUP;
+ return sysrepo::Datastore::Startup;
}
__builtin_unreachable();
@@ -125,12 +73,12 @@
Tree res;
try {
- m_session->session_switch_ds(targetToDs_get(m_target));
- auto config = m_session->get_data(((path == "/") ? "/*" : path).c_str());
+ m_session.switchDatastore(targetToDs_get(m_target));
+ auto config = m_session.getData(((path == "/") ? "/*" : path).c_str());
if (config) {
- lyNodesToTree(res, config->tree_for());
+ lyNodesToTree(res, config->siblings());
}
- } catch (sysrepo::sysrepo_exception& ex) {
+ } catch (sysrepo::Error& ex) {
reportErrors();
}
return res;
@@ -139,9 +87,10 @@
void SysrepoAccess::setLeaf(const std::string& path, leaf_data_ value)
{
try {
- m_session->session_switch_ds(targetToDs_set(m_target));
- m_session->set_item(path.c_str(), boost::apply_visitor(valFromValue(), value), SR_EDIT_ISOLATE);
- } catch (sysrepo::sysrepo_exception& ex) {
+ m_session.switchDatastore(targetToDs_set(m_target));
+ auto lyValue = value.type() == typeid(empty_) ? "" : leafDataToString(value);
+ m_session.setItem(path.c_str(), lyValue.c_str(), sysrepo::EditOptions::Isolate);
+ } catch (sysrepo::Error& ex) {
reportErrors();
}
}
@@ -149,9 +98,9 @@
void SysrepoAccess::createItem(const std::string& path)
{
try {
- m_session->session_switch_ds(targetToDs_set(m_target));
- m_session->set_item(path.c_str());
- } catch (sysrepo::sysrepo_exception& ex) {
+ m_session.switchDatastore(targetToDs_set(m_target));
+ m_session.setItem(path.c_str(), nullptr);
+ } catch (sysrepo::Error& ex) {
reportErrors();
}
}
@@ -159,27 +108,28 @@
void SysrepoAccess::deleteItem(const std::string& path)
{
try {
- // Have to use SR_EDIT_ISOLATE, because deleting something that's been set without committing is not supported
+ // Have to use sysrepo::EditOptions::Isolate, because deleting something that's been set without committing is
+ // not supported.
// https://github.com/sysrepo/sysrepo/issues/1967#issuecomment-625085090
- m_session->session_switch_ds(targetToDs_set(m_target));
- m_session->delete_item(path.c_str(), SR_EDIT_ISOLATE);
- } catch (sysrepo::sysrepo_exception& ex) {
+ m_session.switchDatastore(targetToDs_set(m_target));
+ m_session.deleteItem(path.c_str(), sysrepo::EditOptions::Isolate);
+ } catch (sysrepo::Error& ex) {
reportErrors();
}
}
struct impl_toSrMoveOp {
- sr_move_position_t operator()(yang::move::Absolute& absolute)
+ sysrepo::MovePosition operator()(yang::move::Absolute& absolute)
{
- return absolute == yang::move::Absolute::Begin ? SR_MOVE_FIRST : SR_MOVE_LAST;
+ return absolute == yang::move::Absolute::Begin ? sysrepo::MovePosition::First : sysrepo::MovePosition::Last;
}
- sr_move_position_t operator()(yang::move::Relative& relative)
+ sysrepo::MovePosition operator()(yang::move::Relative& relative)
{
- return relative.m_position == yang::move::Relative::Position::After ? SR_MOVE_AFTER : SR_MOVE_BEFORE;
+ return relative.m_position == yang::move::Relative::Position::After ? sysrepo::MovePosition::After : sysrepo::MovePosition::Before;
}
};
-sr_move_position_t toSrMoveOp(std::variant<yang::move::Absolute, yang::move::Relative> move)
+sysrepo::MovePosition toSrMoveOp(std::variant<yang::move::Absolute, yang::move::Relative> move)
{
return std::visit(impl_toSrMoveOp{}, move);
}
@@ -195,16 +145,16 @@
destination = instanceToString(relative.m_path);
}
}
- m_session->session_switch_ds(targetToDs_set(m_target));
- m_session->move_item(source.c_str(), toSrMoveOp(move), destination.c_str(), destination.c_str());
+ m_session.switchDatastore(targetToDs_set(m_target));
+ m_session.moveItem(source.c_str(), toSrMoveOp(move), destination.c_str());
}
void SysrepoAccess::commitChanges()
{
try {
- m_session->session_switch_ds(targetToDs_set(m_target));
- m_session->apply_changes(OPERATION_TIMEOUT_MS, 1);
- } catch (sysrepo::sysrepo_exception& ex) {
+ m_session.switchDatastore(targetToDs_set(m_target));
+ m_session.applyChanges(OPERATION_TIMEOUT_MS);
+ } catch (sysrepo::Error& ex) {
reportErrors();
}
}
@@ -212,25 +162,25 @@
void SysrepoAccess::discardChanges()
{
try {
- m_session->session_switch_ds(targetToDs_set(m_target));
- m_session->discard_changes();
- } catch (sysrepo::sysrepo_exception& ex) {
+ m_session.switchDatastore(targetToDs_set(m_target));
+ m_session.discardChanges();
+ } catch (sysrepo::Error& ex) {
reportErrors();
}
}
DatastoreAccess::Tree SysrepoAccess::execute(const std::string& path, const Tree& input)
{
- auto inputNode = treeToRpcInput(m_session->get_context(), path, input);
- m_session->session_switch_ds(targetToDs_set(m_target));
- auto output = m_session->rpc_send(inputNode);
- return rpcOutputToTree(path, output);
+ auto inputNode = treeToRpcInput(m_session.getContext(), path, input);
+ m_session.switchDatastore(targetToDs_set(m_target));
+ auto output = m_session.sendRPC(inputNode);
+ return rpcOutputToTree(output);
}
void SysrepoAccess::copyConfig(const Datastore source, const Datastore destination)
{
- m_session->session_switch_ds(toSrDatastore(destination));
- m_session->copy_config(toSrDatastore(source), nullptr, OPERATION_TIMEOUT_MS, 1);
+ m_session.switchDatastore(toSrDatastore(destination));
+ m_session.copyConfig(toSrDatastore(source), nullptr, OPERATION_TIMEOUT_MS);
}
std::shared_ptr<Schema> SysrepoAccess::schema()
@@ -240,16 +190,14 @@
[[noreturn]] void SysrepoAccess::reportErrors() const
{
- // I only use get_error to get error info, since the error code from
- // sysrepo_exception doesn't really give any meaningful information. For
- // example an "invalid argument" error could mean a node isn't enabled, or
- // it could mean something totally different and there is no documentation
- // for that, so it's better to just use the message sysrepo gives me.
- auto srErrors = m_session->get_error();
std::vector<DatastoreError> res;
- for (size_t i = 0; i < srErrors->error_cnt(); i++) {
- res.emplace_back(srErrors->message(i), srErrors->xpath(i) ? std::optional<std::string>{srErrors->xpath(i)} : std::nullopt);
+ for (const auto& err : m_session.getErrors()) {
+ res.emplace_back(err.errorMessage);
+ }
+
+ for (const auto& err : m_session.getNetconfErrors()) {
+ res.emplace_back(err.message, err.path);
}
throw DatastoreException(res);
@@ -258,47 +206,23 @@
std::vector<ListInstance> SysrepoAccess::listInstances(const std::string& path)
{
std::vector<ListInstance> res;
- auto lists = getItems(path);
+ auto lists = m_session.getData(path.c_str());
+ if (!lists) {
+ return res;
+ }
- decltype(lists) instances;
- auto wantedTree = *(m_schema->dataNodeFromPath(path)->find_path(path.c_str())->data().begin());
- std::copy_if(lists.begin(), lists.end(), std::inserter(instances, instances.end()), [this, pathToCheck = wantedTree->schema()->path()](const auto& item) {
- // This filters out non-instances.
- if (item.second.type() != typeid(special_) || boost::get<special_>(item.second).m_value != SpecialValue::List) {
- return false;
- }
-
- // Now, getItems is recursive: it gives everything including nested lists. So I try create a tree from the instance...
- auto instanceTree = *(m_schema->dataNodeFromPath(item.first)->find_path(item.first.c_str())->data().begin());
- // And then check if its schema path matches the list we actually want. This filters out lists which are not the ones I requested.
- return instanceTree->schema()->path() == pathToCheck;
- });
-
- // If there are no instances, then just return
+ auto instances = lists->findXPath(path.c_str());
if (instances.empty()) {
return res;
}
- // I need to find out which keys does the list have. To do that, I create a
- // tree from the first instance. This is gives me some top level node,
- // which will be our list in case out list is a top-level node. In case it
- // isn't, we have call find_path on the top level node. After that, I just
- // retrieve the keys.
- auto topLevelTree = m_schema->dataNodeFromPath(instances.begin()->first);
- auto list = *(topLevelTree->find_path(path.c_str())->data().begin());
- auto keys = libyang::Schema_Node_List{list->schema()}.keys();
+ auto keys = instances.front().schema().asList().keys();
- // Creating a full tree at the same time from the values sysrepo gives me
- // would be a pain (and after sysrepo switches to libyang meaningless), so
- // I just use this algorithm to create data nodes one by one and get the
- // key values from them.
for (const auto& instance : instances) {
- auto wantedList = *(m_schema->dataNodeFromPath(instance.first)->find_path(path.c_str())->data().begin());
ListInstance instanceRes;
for (const auto& key : keys) {
- auto vec = wantedList->find_path(key->name())->data();
- auto leaf = std::make_shared<libyang::Data_Node_Leaf_List>(*(vec.begin()));
- instanceRes.emplace(key->name(), leafValueFromNode(leaf));
+ auto leaf = instance.findPath(key.name().data());
+ instanceRes.emplace(std::string{leaf->schema().name()}, leafValueFromNode(leaf->asTerm()));
}
res.emplace_back(instanceRes);
}
@@ -308,6 +232,11 @@
std::string SysrepoAccess::dump(const DataFormat format) const
{
- auto root = m_session->get_data("/*");
- return root->print_mem(format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
+ auto root = m_session.getData("/*");
+ auto str = root->printStr(format == DataFormat::Xml ? libyang::DataFormat::XML : libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
+ if (!str) {
+ return "";
+ }
+
+ return std::string{*str};
}
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index 7a02d8b..0f736d1 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -9,6 +9,7 @@
#pragma once
#include <string>
+#include <sysrepo-cpp/Connection.hpp>
#include "datastore_access.hpp"
/*! \class DatastoreAccess
@@ -47,7 +48,7 @@
std::vector<ListInstance> listInstances(const std::string& path) override;
[[noreturn]] void reportErrors() const;
- std::shared_ptr<sysrepo::Connection> m_connection;
- std::shared_ptr<sysrepo::Session> m_session;
+ sysrepo::Connection m_connection;
+ sysrepo::Session m_session;
std::shared_ptr<YangSchema> m_schema;
};
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
index 0555616..72e38e7 100644
--- a/src/yang_access.cpp
+++ b/src/yang_access.cpp
@@ -2,8 +2,8 @@
#include <experimental/iterator>
#include <fstream>
#include <iostream>
-#include <libyang/Tree_Data.hpp>
-#include <libyang/libyang.h>
+#include <libyang-cpp/DataNode.hpp>
+#include <libyang-cpp/Utils.hpp>
#include "UniqueResource.hpp"
#include "libyang_utils.hpp"
#include "utils.hpp"
@@ -11,35 +11,21 @@
#include "yang_schema.hpp"
namespace {
-template <typename Type> using lyPtrDeleter_type = void (*)(Type*);
-template <typename Type> const lyPtrDeleter_type<Type> lyPtrDeleter;
-template <> const auto lyPtrDeleter<ly_set> = ly_set_free;
-template <> const auto lyPtrDeleter<ly_ctx> = static_cast<lyPtrDeleter_type<ly_ctx>>([] (auto* ptr) {ly_ctx_destroy(ptr, nullptr);});
-template <> const auto lyPtrDeleter<lyd_node> = lyd_free_withsiblings;
-
-template <typename Type>
-auto lyWrap(Type* ptr)
-{
- return std::unique_ptr<Type, lyPtrDeleter_type<Type>>{ptr, lyPtrDeleter<Type>};
-}
-
// Convenient for functions that take m_datastore as an argument
-using DatastoreType = std::unique_ptr<lyd_node, lyPtrDeleter_type<lyd_node>>;
+using DatastoreType = std::optional<libyang::DataNode>;
}
YangAccess::YangAccess()
- : 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)
+ : m_ctx(nullptr, libyang::ContextOptions::DisableSearchCwd)
+ , m_datastore(std::nullopt)
+ , m_schema(std::make_shared<YangSchema>(m_ctx))
{
}
YangAccess::YangAccess(std::shared_ptr<YangSchema> schema)
- : m_ctx(schema->m_context->swig_ctx(), [](auto) {})
- , m_datastore(lyWrap<lyd_node>(nullptr))
+ : m_ctx(schema->m_context)
+ , m_datastore(std::nullopt)
, m_schema(schema)
- , m_validation_mode(LYD_OPT_RPC)
{
}
@@ -47,78 +33,69 @@
[[noreturn]] void YangAccess::getErrorsAndThrow() const
{
- auto errors = libyang::get_ly_errors(libyang::create_new_Context(m_ctx.get()));
std::vector<DatastoreError> errorsRes;
- for (const auto& error : errors) {
- using namespace std::string_view_literals;
- errorsRes.emplace_back(error->errmsg(), error->errpath() != ""sv ? std::optional{error->errpath()} : std::nullopt);
- }
+ for (const auto& err : m_ctx.getErrors()) {
+ errorsRes.emplace_back(err.message, err.path);
+ }
throw DatastoreException(errorsRes);
}
void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value)
{
- auto newNode = lyd_new_path(m_datastore.get(), m_ctx.get(), path.c_str(), value ? (void*)value->c_str() : nullptr, LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
- if (!newNode) {
+ try {
+ if (m_datastore) {
+ m_datastore->newPath(path.c_str(), value ? value->c_str() : nullptr, libyang::CreationOptions::Update);
+ } else {
+ m_datastore = m_ctx.newPath(path.c_str(), value ? value->c_str() : nullptr, libyang::CreationOptions::Update);
+ }
+ } catch (libyang::Error&) {
getErrorsAndThrow();
}
- if (!m_datastore) {
- m_datastore = lyWrap(newNode);
- }
}
namespace {
-void impl_unlink(DatastoreType& datastore, lyd_node* what)
+void impl_unlink(DatastoreType& datastore, libyang::DataNode what)
{
// If the node to be unlinked is the one our datastore variable points to, we need to find a new one to point to (one of its siblings)
- if (datastore.get() == what) {
- auto oldDatastore = datastore.release();
- if (oldDatastore->prev != oldDatastore) {
- datastore = lyWrap(oldDatastore->prev);
- } else {
- datastore = lyWrap(oldDatastore->next);
- }
+
+ if (datastore == what) {
+ auto oldDatastore = datastore;
+ do {
+ datastore = datastore->previousSibling();
+ if (datastore == oldDatastore) {
+ // We have gone all the way back to our original node, which means it's the only node in our
+ // datastore.
+ datastore = std::nullopt;
+ break;
+ }
+ } while (datastore->schema().module().name() == "ietf-yang-library");
}
- lyd_unlink(what);
+ what.unlink();
}
}
void YangAccess::impl_removeNode(const std::string& path)
{
- auto set = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
- if (!set || set->number == 0) {
- // Check if schema node exists - lyd_find_path first checks if the first argument is non-null before checking for path validity
- if (!ly_ctx_get_node(m_ctx.get(), nullptr, path.c_str(), 0)) {
- throw DatastoreException{{DatastoreError{"Schema node doesn't exist.", path}}};
- }
- // Check if libyang found another error
- if (ly_err_first(m_ctx.get())) {
- getErrorsAndThrow();
- }
-
+ if (!m_datastore) {
+ // Otherwise the datastore just doesn't contain the wanted node.
+ throw DatastoreException{{DatastoreError{"Datastore is empty.", path}}};
+ }
+ auto toRemove = m_datastore->findPath(path.c_str());
+ if (!toRemove) {
// Otherwise the datastore just doesn't contain the wanted node.
throw DatastoreException{{DatastoreError{"Data node doesn't exist.", path}}};
}
- auto toRemove = set->set.d[0];
-
- impl_unlink(m_datastore, toRemove);
-
- lyd_free(toRemove);
+ impl_unlink(m_datastore, *toRemove);
}
void YangAccess::validate()
{
- auto datastore = m_datastore.release();
-
- 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());
+ if (m_datastore) {
+ libyang::validateAll(m_datastore);
}
- m_datastore = lyWrap(datastore);
}
DatastoreAccess::Tree YangAccess::getItems(const std::string& path) const
@@ -128,10 +105,9 @@
return res;
}
- auto set = lyWrap(lyd_find_path(m_datastore.get(), path == "/" ? "/*" : path.c_str()));
- auto setWrapper = libyang::Set(set.get(), nullptr);
- std::optional<std::string> ignoredXPathPrefix;
- lyNodesToTree(res, setWrapper.data());
+ auto set = m_datastore->findXPath(path == "/" ? "/*" : path.c_str());
+
+ lyNodesToTree(res, set);
return res;
}
@@ -154,66 +130,60 @@
namespace {
struct impl_moveItem {
DatastoreType& m_datastore;
- lyd_node* m_sourceNode;
+ libyang::DataNode m_sourceNode;
void operator()(yang::move::Absolute absolute) const
{
- auto set = lyWrap(lyd_find_instance(m_sourceNode, m_sourceNode->schema));
- if (set->number == 1) { // m_sourceNode is the sole instance, do nothing
+ auto set = m_sourceNode.findXPath(m_sourceNode.schema().path().get().get());
+ if (set.size() == 1) { // m_sourceNode is the sole instance, do nothing
return;
}
- doUnlink();
switch (absolute) {
case yang::move::Absolute::Begin:
- if (set->set.d[0] == m_sourceNode) { // List is already at the beginning, do nothing
+ if (set.front() == m_sourceNode) { // List is already at the beginning, do nothing
return;
}
- lyd_insert_before(set->set.d[0], m_sourceNode);
- return;
+ set.front().insertBefore(m_sourceNode);
+ break;
case yang::move::Absolute::End:
- if (set->set.d[set->number - 1] == m_sourceNode) { // List is already at the end, do nothing
+ if (set.back() == m_sourceNode) { // List is already at the end, do nothing
return;
}
- lyd_insert_after(set->set.d[set->number - 1], m_sourceNode);
- return;
+ set.back().insertAfter(m_sourceNode);
+ break;
}
+ m_datastore = m_datastore->firstSibling();
}
void operator()(const yang::move::Relative& relative) const
{
- auto keySuffix = m_sourceNode->schema->nodetype == LYS_LIST ? instanceToString(relative.m_path)
+ auto keySuffix = m_sourceNode.schema().nodeType() == libyang::NodeType::List ? instanceToString(relative.m_path)
: leafDataToString(relative.m_path.at("."));
- lyd_node* destNode;
- lyd_find_sibling_val(m_sourceNode, m_sourceNode->schema, keySuffix.c_str(), &destNode);
+ auto destNode = m_sourceNode.findSiblingVal(m_sourceNode.schema(), keySuffix.c_str());
- doUnlink();
if (relative.m_position == yang::move::Relative::Position::After) {
- lyd_insert_after(destNode, m_sourceNode);
+ destNode->insertAfter(m_sourceNode);
} else {
- lyd_insert_before(destNode, m_sourceNode);
+ destNode->insertBefore(m_sourceNode);
}
}
-
-private:
- void doUnlink() const
- {
- impl_unlink(m_datastore, m_sourceNode);
- }
};
}
void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
{
- auto set = lyWrap(lyd_find_path(m_datastore.get(), source.c_str()));
- if (!set) { // Error, the node probably doesn't exist in the schema
- getErrorsAndThrow();
+ if (!m_datastore) {
+ throw DatastoreException{{DatastoreError{"Datastore is empty.", source}}};
}
- if (set->number == 0) {
- return;
+
+ auto sourceNode = m_datastore->findPath(source.c_str());
+
+ if (!sourceNode) {
+ // The datastore doesn't contain the wanted node.
+ throw DatastoreException{{DatastoreError{"Data node doesn't exist.", source}}};
}
- auto sourceNode = set->set.d[0];
- std::visit(impl_moveItem{m_datastore, sourceNode}, move);
+ std::visit(impl_moveItem{m_datastore, *sourceNode}, move);
}
void YangAccess::commitChanges()
@@ -227,16 +197,24 @@
[[noreturn]] DatastoreAccess::Tree YangAccess::execute(const std::string& path, const Tree& input)
{
- auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0));
- if (!root) {
- getErrorsAndThrow();
- }
+ auto root = [&path, this] {
+ try {
+ return m_ctx.newPath(path.c_str());
+ } catch (libyang::ErrorWithCode& err) {
+ getErrorsAndThrow();
+ }
+ }();
+
for (const auto& [k, v] : input) {
if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) {
continue;
}
- lyd_new_path(root.get(), m_ctx.get(), k.c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
+ try {
+ root.newPath(k.c_str(), leafDataToString(v).c_str(), libyang::CreationOptions::Update);
+ } catch (libyang::ErrorWithCode& err) {
+ getErrorsAndThrow();
+ }
}
throw std::logic_error("in-memory datastore doesn't support executing RPC/action");
}
@@ -244,7 +222,7 @@
void YangAccess::copyConfig(const Datastore source, const Datastore dest)
{
if (source == Datastore::Startup && dest == Datastore::Running) {
- m_datastore = nullptr;
+ m_datastore = std::nullopt;
}
}
@@ -260,16 +238,14 @@
return res;
}
- auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
- auto instancesWrapper = libyang::Set(instances.get(), nullptr);
- for (const auto& list : instancesWrapper.data()) {
+ auto instances = m_datastore->findXPath(path.c_str());
+ for (const auto& list : instances) {
ListInstance instance;
- for (const auto& child : list->child()->tree_for()) {
- if (child->schema()->nodetype() == LYS_LEAF) {
- libyang::Schema_Node_Leaf leafSchema(child->schema());
- if (leafSchema.is_key()) {
- auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(child);
- instance.insert({leafSchema.name(), leafValueFromNode(leafData)});
+ for (const auto& child : list.child()->siblings()) {
+ if (child.schema().nodeType() == libyang::NodeType::Leaf) {
+ auto leafSchema(child.schema().asLeaf());
+ if (leafSchema.isKey()) {
+ instance.insert({std::string{leafSchema.name()}, leafValueFromNode(child.asTerm())});
}
}
}
@@ -280,16 +256,16 @@
std::string YangAccess::dump(const DataFormat format) const
{
- char* output;
- lyd_print_mem(&output, m_datastore.get(), format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
- std::unique_ptr<char, decltype(&free)> deleter{output, free};
-
- if (output) {
- std::string res = output;
- return res;
+ if (!m_datastore) {
+ return "";
}
- return "";
+ auto str = m_datastore->firstSibling().printStr(format == DataFormat::Xml ? libyang::DataFormat::XML : libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
+ if (!str) {
+ return "";
+ }
+
+ return std::string{*str};
}
void YangAccess::loadModule(const std::string& name)
@@ -307,9 +283,9 @@
m_schema->addSchemaDirectory(path.c_str());
}
-void YangAccess::enableFeature(const std::string& module, const std::string& feature)
+void YangAccess::setEnabledFeatures(const std::string& module, const std::vector<std::string>& features)
{
- m_schema->enableFeature(module, feature);
+ m_schema->setEnabledFeatures(module, features);
}
void YangAccess::addDataFile(const std::string& path, const StrictDataParsing strict)
@@ -320,20 +296,16 @@
std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n";
- auto parseFlags = LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_TRUSTED;
- if (strict == StrictDataParsing::Yes) {
- parseFlags |= LYD_OPT_STRICT;
- }
- auto dataNode = lyd_parse_path(m_ctx.get(), path.c_str(), firstChar == '{' ? LYD_JSON : LYD_XML, parseFlags);
-
- if (!dataNode) {
- throw std::runtime_error("Supplied data file " + path + " couldn't be parsed.");
- }
+ auto dataNode = m_ctx.parseDataPath(
+ path.c_str(),
+ firstChar == '{' ? libyang::DataFormat::JSON : libyang::DataFormat::XML,
+ strict == StrictDataParsing::Yes ? std::optional{libyang::ParseOptions::Strict} : std::nullopt,
+ libyang::ValidationOptions::Present);
if (!m_datastore) {
- m_datastore = lyWrap(dataNode);
+ m_datastore = dataNode;
} else {
- lyd_merge(m_datastore.get(), dataNode, LYD_OPT_DESTRUCT);
+ m_datastore->merge(*dataNode);
}
validate();
diff --git a/src/yang_access.hpp b/src/yang_access.hpp
index 64538f0..aab5d81 100644
--- a/src/yang_access.hpp
+++ b/src/yang_access.hpp
@@ -7,6 +7,7 @@
#pragma once
+#include <libyang-cpp/Context.hpp>
#include "datastore_access.hpp"
/*! \class YangAccess
@@ -40,7 +41,7 @@
std::shared_ptr<Schema> schema() override;
- void enableFeature(const std::string& module, const std::string& feature);
+ void setEnabledFeatures(const std::string& module, const std::vector<std::string>& features);
[[nodiscard]] std::string dump(const DataFormat format) const override;
void loadModule(const std::string& name);
@@ -57,8 +58,7 @@
void impl_removeNode(const std::string& path);
void validate();
- std::unique_ptr<ly_ctx, void (*)(ly_ctx*)> m_ctx;
- std::unique_ptr<lyd_node, void (*)(lyd_node*)> m_datastore;
+ libyang::Context m_ctx;
+ std::optional<libyang::DataNode> 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 b964d31..f96aa46 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -6,9 +6,8 @@
*
*/
-#include <libyang/Libyang.hpp>
-#include <libyang/Tree_Data.hpp>
-#include <libyang/Tree_Schema.hpp>
+#include <libyang-cpp/Enum.hpp>
+#include <libyang-cpp/Utils.hpp>
#include <string_view>
#include "UniqueResource.hpp"
#include "utils.hpp"
@@ -33,11 +32,11 @@
};
YangSchema::YangSchema()
- : m_context(std::make_shared<libyang::Context>(nullptr, LY_CTX_DISABLE_SEARCHDIR_CWD))
+ : m_context(nullptr, libyang::ContextOptions::DisableSearchDirs | libyang::ContextOptions::SetPrivParsed)
{
}
-YangSchema::YangSchema(std::shared_ptr<libyang::Context> lyCtx)
+YangSchema::YangSchema(libyang::Context lyCtx)
: m_context(lyCtx)
{
}
@@ -46,29 +45,22 @@
void YangSchema::addSchemaString(const char* schema)
{
- if (!m_context->parse_module_mem(schema, LYS_IN_YANG)) {
- throw YangLoadError("Couldn't load schema");
- }
+ m_context.parseModuleMem(schema, libyang::SchemaFormat::YANG);
}
void YangSchema::addSchemaDirectory(const char* directoryName)
{
- if (m_context->set_searchdir(directoryName)) {
- throw YangLoadError("Couldn't add schema search directory");
- }
+ m_context.setSearchDir(directoryName);
}
void YangSchema::addSchemaFile(const char* filename)
{
- if (!m_context->parse_module_path(filename, LYS_IN_YANG)) {
- throw YangLoadError("Couldn't load schema");
- }
+ m_context.parseModulePath(filename, libyang::SchemaFormat::YANG);
}
bool YangSchema::isModule(const std::string& name) const
{
- const auto set = modules();
- return set.find(name) != set.end();
+ return m_context.getModuleImplemented(name.c_str()).has_value();
}
bool YangSchema::listHasKey(const schemaPath_& listPath, const std::string& key) const
@@ -80,49 +72,52 @@
bool YangSchema::leafIsKey(const std::string& leafPath) const
{
auto node = getSchemaNode(leafPath);
- if (!node || node->nodetype() != LYS_LEAF) {
+ if (!node || node->nodeType() != libyang::NodeType::Leaf) {
return false;
}
- return libyang::Schema_Node_Leaf{node}.is_key().get();
+ return node->asLeaf().isKey();
}
-libyang::S_Schema_Node YangSchema::impl_getSchemaNode(const std::string& node) const
+std::optional<libyang::SchemaNode> YangSchema::impl_getSchemaNode(const std::string& node) const
{
- // If no node is found find_path prints an error message, so we have to
- // disable logging
- // https://github.com/CESNET/libyang/issues/753
- {
- int oldOptions;
- auto logBlocker = make_unique_resource(
- [&oldOptions]() {
- oldOptions = libyang::set_log_options(0);
- },
- [&oldOptions]() {
- libyang::set_log_options(oldOptions);
- });
- 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);
+ // libyang::Context::findPath throws an exception, when no matching schema node is found. This exception has the
+ // ValidationFailure error code. We will catch that exception (and rethrow if it's not the correct error code.
+ //
+ // Also, we need to use findPath twice if we're trying to find output nodes.
+ try {
+ return m_context.findPath(node.c_str());
+ } catch (libyang::ErrorWithCode& err) {
+ if (err.code() != libyang::ErrorCode::ValidationFailure) {
+ throw;
}
- return res;
}
+ try {
+ return m_context.findPath(node.c_str(), libyang::OutputNodes::Yes);
+ } catch (libyang::ErrorWithCode& err) {
+ if (err.code() != libyang::ErrorCode::ValidationFailure) {
+ throw;
+ }
+ }
+
+ // We didn't find a matching node.
+ return std::nullopt;
}
-libyang::S_Schema_Node YangSchema::getSchemaNode(const std::string& node) const
+std::optional<libyang::SchemaNode> YangSchema::getSchemaNode(const std::string& node) const
{
return impl_getSchemaNode(node);
}
-libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
+std::optional<libyang::SchemaNode> YangSchema::getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const
{
std::string absPath = joinPaths(pathToSchemaString(location, Prefixes::Always), fullNodeName(location, node));
return impl_getSchemaNode(absPath);
}
-libyang::S_Schema_Node YangSchema::getSchemaNode(const schemaPath_& listPath) const
+std::optional<libyang::SchemaNode> YangSchema::getSchemaNode(const schemaPath_& listPath) const
{
std::string absPath = pathToSchemaString(listPath, Prefixes::Always);
return impl_getSchemaNode(absPath);
@@ -131,174 +126,121 @@
const std::set<std::string> YangSchema::listKeys(const schemaPath_& listPath) const
{
auto node = getSchemaNode(listPath);
- if (node->nodetype() != LYS_LIST) {
+ if (node->nodeType() != libyang::NodeType::List) {
return {};
}
- auto list = std::make_shared<libyang::Schema_Node_List>(node);
std::set<std::string> keys;
- const auto& keysVec = list->keys();
+ auto keysVec = node->asList().keys();
- std::transform(keysVec.begin(), keysVec.end(), std::inserter(keys, keys.begin()), [](const auto& it) { return it->name(); });
+ std::transform(keysVec.begin(), keysVec.end(), std::inserter(keys, keys.begin()), [](const auto& it) { return std::string{it.name()}; });
return keys;
}
-namespace {
-enum class ResolveMode {
- Enum,
- Identity
-};
-/** @brief Resolves a typedef to a type which defines values.
- * When we need allowed values of a type and that type is a typedef, we need to recurse into the typedef until we find a
- * type which defines values. These values are the allowed values.
- * Example:
- *
- * typedef MyOtherEnum {
- * type enumeration {
- * enum "A";
- * enum "B";
- * }
- * }
- *
- * typedef MyEnum {
- * type MyOtherEnum;
- * }
- *
- * If `toResolve` points to MyEnum, then just doing ->enums()->enm() returns nothing and that means that this particular
- * typedef (MyEnum) did not say which values are allowed. So, we need to dive into the parent enum (MyOtherEnum) with
- * ->der()->type(). This typedef (MyOtherEnum) DID specify allowed values and enums()->enm() WILL contain them. These
- * values are the only relevant values and we don't care about other parent typedefs. We return these values to the
- * caller.
- *
- * For enums, this function simply returns all allowed enums.
- * For identities, this function returns which bases `toResolve` has.
- */
-template <ResolveMode TYPE>
-auto resolveTypedef(const libyang::S_Type& toResolve)
+std::set<enum_> enumValues(const libyang::Type& type)
{
- auto type = toResolve;
- auto getValuesFromType = [] (const libyang::S_Type& type) {
- if constexpr (TYPE == ResolveMode::Identity) {
- return type->info()->ident()->ref();
- } else {
- return type->info()->enums()->enm();
- }
- };
- auto values = getValuesFromType(type);
- while (values.empty()) {
- type = type->der()->type();
- values = getValuesFromType(type);
- }
-
- return values;
-}
-
-std::set<enum_> enumValues(const libyang::S_Type& type)
-{
- auto values = resolveTypedef<ResolveMode::Enum>(type);
-
- std::vector<libyang::S_Type_Enum> enabled;
- std::copy_if(values.begin(), values.end(), std::back_inserter(enabled), [](const libyang::S_Type_Enum& it) {
- auto iffeatures = it->iffeature();
- return std::all_of(iffeatures.begin(), iffeatures.end(), [](auto it) { return it->value(); });
- });
-
+ auto enums = type.asEnum().items();
std::set<enum_> enumSet;
- std::transform(enabled.begin(), enabled.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return enum_{it->name()}; });
+ std::transform(enums.begin(), enums.end(), std::inserter(enumSet, enumSet.end()), [](auto it) { return enum_{std::string{it.name}}; });
return enumSet;
}
-std::set<identityRef_> validIdentities(const libyang::S_Type& type)
+std::set<identityRef_> validIdentities(const libyang::Type& type)
{
std::set<identityRef_> identSet;
- for (auto base : resolveTypedef<ResolveMode::Identity>(type)) { // Iterate over all bases
- // Iterate over derived identities (this is recursive!)
- if (auto der = base->der()) {
- for (auto derived : der->schema()) {
- identSet.emplace(derived->module()->name(), derived->name());
- }
+ std::function<void(const std::vector<libyang::Identity>&)> impl = [&identSet, &impl] (const std::vector<libyang::Identity>& idents) {
+ if (idents.empty()) {
+ return;
}
+
+ for (const auto& ident : idents) {
+ identSet.emplace(std::string{ident.module().name()}, std::string{ident.name()});
+ impl(ident.derived());
+ }
+ };
+
+ for (const auto& base : type.asIdentityRef().bases()) {
+ impl(base.derived());
}
return identSet;
}
-std::string leafrefPath(const libyang::S_Type& type)
+std::string leafrefPath(const libyang::Type& type)
{
- return type->info()->lref()->target()->path(LYS_PATH_FIRST_PREFIX);
-}
+ return std::string{type.asLeafRef().path()};
}
template <typename NodeType>
-yang::TypeInfo YangSchema::impl_leafType(const libyang::S_Schema_Node& node) const
+yang::TypeInfo YangSchema::impl_leafType(const NodeType& node) const
{
using namespace std::string_literals;
auto leaf = std::make_shared<NodeType>(node);
auto leafUnits = leaf->units();
- std::function<yang::TypeInfo(std::shared_ptr<libyang::Type>)> resolveType;
- resolveType = [this, &resolveType, leaf, leafUnits](std::shared_ptr<libyang::Type> type) -> yang::TypeInfo {
+ std::function<yang::TypeInfo(const libyang::Type&)> resolveType;
+ resolveType = [&resolveType, leaf, leafUnits](const libyang::Type& type) -> yang::TypeInfo {
yang::LeafDataType resType;
- switch (type->base()) {
- case LY_TYPE_STRING:
+ switch (type.base()) {
+ case libyang::LeafBaseType::String:
resType.emplace<yang::String>();
break;
- case LY_TYPE_DEC64:
+ case libyang::LeafBaseType::Dec64:
resType.emplace<yang::Decimal>();
break;
- case LY_TYPE_BOOL:
+ case libyang::LeafBaseType::Bool:
resType.emplace<yang::Bool>();
break;
- case LY_TYPE_INT8:
+ case libyang::LeafBaseType::Int8:
resType.emplace<yang::Int8>();
break;
- case LY_TYPE_INT16:
+ case libyang::LeafBaseType::Int16:
resType.emplace<yang::Int16>();
break;
- case LY_TYPE_INT32:
+ case libyang::LeafBaseType::Int32:
resType.emplace<yang::Int32>();
break;
- case LY_TYPE_INT64:
+ case libyang::LeafBaseType::Int64:
resType.emplace<yang::Int64>();
break;
- case LY_TYPE_UINT8:
+ case libyang::LeafBaseType::Uint8:
resType.emplace<yang::Uint8>();
break;
- case LY_TYPE_UINT16:
+ case libyang::LeafBaseType::Uint16:
resType.emplace<yang::Uint16>();
break;
- case LY_TYPE_UINT32:
+ case libyang::LeafBaseType::Uint32:
resType.emplace<yang::Uint32>();
break;
- case LY_TYPE_UINT64:
+ case libyang::LeafBaseType::Uint64:
resType.emplace<yang::Uint64>();
break;
- case LY_TYPE_BINARY:
+ case libyang::LeafBaseType::Binary:
resType.emplace<yang::Binary>();
break;
- case LY_TYPE_EMPTY:
+ case libyang::LeafBaseType::Empty:
resType.emplace<yang::Empty>();
break;
- case LY_TYPE_ENUM:
+ case libyang::LeafBaseType::Enum:
resType.emplace<yang::Enum>(enumValues(type));
break;
- case LY_TYPE_IDENT:
+ case libyang::LeafBaseType::IdentityRef:
resType.emplace<yang::IdentityRef>(validIdentities(type));
break;
- case LY_TYPE_LEAFREF:
- resType.emplace<yang::LeafRef>(::leafrefPath(type), std::make_unique<yang::TypeInfo>(leafType(::leafrefPath(type))));
+ case libyang::LeafBaseType::Leafref:
+ resType.emplace<yang::LeafRef>(::leafrefPath(type), std::make_unique<yang::TypeInfo>(resolveType(type.asLeafRef().resolvedType())));
break;
- case LY_TYPE_BITS: {
+ case libyang::LeafBaseType::Bits: {
auto resBits = yang::Bits{};
- for (const auto& bit : type->info()->bits()->bit()) {
- resBits.m_allowedValues.emplace(bit->name());
+ for (const auto& bit : type.asBits().items()) {
+ resBits.m_allowedValues.emplace(std::string{bit.name});
}
resType.emplace<yang::Bits>(std::move(resBits));
break;
}
- case LY_TYPE_UNION: {
+ case libyang::LeafBaseType::Union: {
auto resUnion = yang::Union{};
- for (auto unionType : type->info()->uni()->types()) {
+ for (auto unionType : type.asUnion().types()) {
resUnion.m_unionTypes.emplace_back(resolveType(unionType));
}
resType.emplace<yang::Union>(std::move(resUnion));
@@ -306,48 +248,26 @@
}
default:
using namespace std::string_literals;
- throw UnsupportedYangTypeException("the type of "s + leaf->name() + " is not supported: " + std::to_string(leaf->type()->base()));
+ throw UnsupportedYangTypeException("the type of "s +
+ std::string{leaf->name()} +
+ " is not supported: " +
+ std::to_string(std::underlying_type_t<libyang::LeafBaseType>(leaf->valueType().base())));
}
- std::optional<std::string> resUnits;
- if (leafUnits) {
- resUnits = leafUnits;
- } else {
- for (auto parentTypedef = type->der(); parentTypedef; parentTypedef = parentTypedef->type()->der()) {
- auto units = parentTypedef->units();
- if (units) {
- resUnits = units;
- break;
- }
- }
- }
-
- std::optional<std::string> resDescription;
-
- // checking for parentTypedef->type()->der() means I'm going to enter inside base types like "string". These
- // also have a description, but it isn't too helpful ("human-readable string")
- for (auto parentTypedef = type->der(); parentTypedef && parentTypedef->type()->der(); parentTypedef = parentTypedef->type()->der()) {
- auto dsc = parentTypedef->dsc();
- if (dsc) {
- resDescription = dsc;
- break;
- }
- }
-
- return yang::TypeInfo(resType, resUnits, resDescription);
+ return yang::TypeInfo(resType, std::optional<std::string>{leafUnits}, std::optional<std::string>{type.description()});
};
- return resolveType(leaf->type());
+ return resolveType(leaf->valueType());
}
yang::TypeInfo YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
{
auto lyNode = getSchemaNode(location, node);
- switch (lyNode->nodetype()) {
- case LYS_LEAF:
- return impl_leafType<libyang::Schema_Node_Leaf>(lyNode);
- case LYS_LEAFLIST:
- return impl_leafType<libyang::Schema_Node_Leaflist>(lyNode);
+ switch (lyNode->nodeType()) {
+ case libyang::NodeType::Leaf:
+ return impl_leafType(lyNode->asLeaf());
+ case libyang::NodeType::Leaflist:
+ return impl_leafType(lyNode->asLeafList());
default:
throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist");
}
@@ -356,11 +276,11 @@
yang::TypeInfo YangSchema::leafType(const std::string& path) const
{
auto lyNode = getSchemaNode(path);
- switch (lyNode->nodetype()) {
- case LYS_LEAF:
- return impl_leafType<libyang::Schema_Node_Leaf>(lyNode);
- case LYS_LEAFLIST:
- return impl_leafType<libyang::Schema_Node_Leaflist>(lyNode);
+ switch (lyNode->nodeType()) {
+ case libyang::NodeType::Leaf:
+ return impl_leafType(lyNode->asLeaf());
+ case libyang::NodeType::Leaflist:
+ return impl_leafType(lyNode->asLeafList());
default:
throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist");
}
@@ -368,23 +288,22 @@
std::optional<std::string> YangSchema::leafTypeName(const std::string& path) const
{
- libyang::Schema_Node_Leaf leaf(getSchemaNode(path));
- return leaf.type()->der().get() && leaf.type()->der()->type()->der().get() ? std::optional{leaf.type()->der()->name()} : std::nullopt;
+ auto leaf = getSchemaNode(path)->asLeaf();
+ return std::string{leaf.valueType().name()};
}
std::string YangSchema::leafrefPath(const std::string& leafrefPath) const
{
using namespace std::string_literals;
- libyang::Schema_Node_Leaf leaf(getSchemaNode(leafrefPath));
- return leaf.type()->info()->lref()->target()->path(LYS_PATH_FIRST_PREFIX);
+ return ::leafrefPath(getSchemaNode(leafrefPath)->asLeaf().valueType());
}
std::set<std::string> YangSchema::modules() const
{
- const auto& modules = m_context->get_module_iter();
+ const auto& modules = m_context.modules();
std::set<std::string> res;
- std::transform(modules.begin(), modules.end(), std::inserter(res, res.end()), [](const auto module) { return module->name(); });
+ std::transform(modules.begin(), modules.end(), std::inserter(res, res.end()), [](const auto module) { return std::string{module.name()}; });
return res;
}
@@ -392,39 +311,49 @@
{
using namespace std::string_view_literals;
std::set<ModuleNodePair> res;
- std::vector<libyang::S_Schema_Node> nodes;
+ std::vector<libyang::ChildInstanstiables> nodeCollections;
std::string topLevelModule;
if (path.type() == typeid(module_)) {
- nodes = m_context->get_module(boost::get<module_>(path).m_name.c_str())->data_instantiables(0);
+ nodeCollections.emplace_back(m_context.getModule(boost::get<module_>(path).m_name.c_str())->childInstantiables());
} else {
auto schemaPath = anyPathToSchemaPath(path);
if (schemaPath.m_nodes.empty()) {
- nodes = m_context->data_instantiables(0);
+ for (const auto& module : m_context.modules()) {
+ if (module.implemented()) {
+ nodeCollections.emplace_back(module.childInstantiables());
+ }
+ }
} else {
const auto pathString = pathToSchemaString(schemaPath, Prefixes::Always);
const auto node = getSchemaNode(pathString);
- nodes = node->child_instantiables(0);
+ nodeCollections.emplace_back(node->childInstantiables());
topLevelModule = schemaPath.m_nodes.begin()->m_prefix->m_name;
}
}
- for (const auto& node : nodes) {
- if (node->module()->name() == "ietf-yang-library"sv) {
- continue;
- }
+ for (const auto& coll : nodeCollections) {
+ for (const auto& node : coll) {
+ if (node.module().name() == "ietf-yang-library"sv) {
+ continue;
+ }
- if (recursion == Recursion::Recursive) {
- for (auto it : node->tree_dfs()) {
- res.insert(ModuleNodePair(boost::none, it->path(LYS_PATH_FIRST_PREFIX)));
+ if (node.module().name() == "ietf-yang-schema-mount"sv) {
+ continue;
}
- } else {
- ModuleNodePair toInsert;
- if (topLevelModule.empty() || topLevelModule != node->module()->name()) {
- toInsert.first = node->module()->type() == 0 ? node->module()->name() : libyang::Submodule(node->module()).belongsto()->name();
+
+ if (recursion == Recursion::Recursive) {
+ for (auto it : node.childrenDfs()) {
+ res.insert(ModuleNodePair(boost::none, it.path()));
+ }
+ } else {
+ ModuleNodePair toInsert;
+ if (topLevelModule.empty() || topLevelModule != node.module().name()) {
+ toInsert.first = std::string{node.module().name()};
+ }
+ toInsert.second = node.name();
+ res.insert(toInsert);
}
- toInsert.second = node->name();
- res.insert(toInsert);
}
}
@@ -433,71 +362,81 @@
void YangSchema::loadModule(const std::string& moduleName)
{
- m_context->load_module(moduleName.c_str());
+ m_context.loadModule(moduleName.c_str());
}
-void YangSchema::enableFeature(const std::string& moduleName, const std::string& featureName)
+void YangSchema::setEnabledFeatures(const std::string& moduleName, const std::vector<std::string>& features)
{
using namespace std::string_literals;
auto module = getYangModule(moduleName);
if (!module) {
throw std::runtime_error("Module \""s + moduleName + "\" doesn't exist.");
}
- if (module->feature_enable(featureName.c_str())) {
- throw std::runtime_error("Can't enable feature \""s + featureName + "\" for module \"" + moduleName + "\".");
+ try {
+ module->setImplemented(features);
+ } catch (libyang::ErrorWithCode&) {
+ throw std::runtime_error("Can't enable features for module \"" + moduleName + "\".");
}
}
void YangSchema::registerModuleCallback(const std::function<std::string(const char*, const char*, const char*, const char*)>& clb)
{
- auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) {
+ auto lambda = [clb](const char* mod_name, const char* mod_revision, const char* submod_name, const char* submod_revision) -> std::optional<libyang::ModuleInfo> {
(void)submod_revision;
auto moduleSource = clb(mod_name, mod_revision, submod_name, submod_revision);
if (moduleSource.empty()) {
- return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, nullptr};
+ return std::nullopt;
}
- return libyang::Context::mod_missing_cb_return{LYS_IN_YANG, strdup(moduleSource.c_str())};
+ return libyang::ModuleInfo {
+ .data = moduleSource.c_str(),
+ .format = libyang::SchemaFormat::YANG
+
+ };
};
- m_context->add_missing_module_callback(lambda, free);
+ m_context.registerModuleCallback(lambda);
}
-std::shared_ptr<libyang::Data_Node> YangSchema::dataNodeFromPath(const std::string& path, const std::optional<const std::string> value) const
+libyang::CreatedNodes YangSchema::dataNodeFromPath(const std::string& path, const std::optional<const std::string> value) const
{
- return std::make_shared<libyang::Data_Node>(m_context,
- path.c_str(),
- value ? value.value().c_str() : nullptr,
- LYD_ANYDATA_CONSTSTRING,
- LYD_PATH_OPT_EDIT);
+ auto options = [this, &path, &value] {
+ // If we're creating a node without a value and it's not the "empty" type, then we also need the Opaque flag.
+ auto schema = getSchemaNode(path);
+ if (schema->nodeType() == libyang::NodeType::Leaf &&
+ schema->asLeaf().valueType().base() != libyang::LeafBaseType::Empty &&
+ !value) {
+ return std::optional<libyang::CreationOptions>{libyang::CreationOptions::Opaque};
+ }
+
+ return std::optional<libyang::CreationOptions>{};
+ }();
+ return m_context.newPath2(path.c_str(), value ? value->c_str() : nullptr, options);
}
-std::shared_ptr<libyang::Module> YangSchema::getYangModule(const std::string& name)
+std::optional<libyang::Module> YangSchema::getYangModule(const std::string& name)
{
- return m_context->get_module(name.c_str());
+ return m_context.getModuleImplemented(name.c_str());
}
namespace {
-yang::NodeTypes impl_nodeType(const libyang::S_Schema_Node& node)
+yang::NodeTypes impl_nodeType(const libyang::SchemaNode& node)
{
- if (!node) {
- throw InvalidNodeException();
- }
- switch (node->nodetype()) {
- case LYS_CONTAINER:
- return libyang::Schema_Node_Container{node}.presence() ? yang::NodeTypes::PresenceContainer : yang::NodeTypes::Container;
- case LYS_LEAF:
+ switch (node.nodeType()) {
+ case libyang::NodeType::Container:
+ return node.asContainer().isPresence() ? yang::NodeTypes::PresenceContainer : yang::NodeTypes::Container;
+ case libyang::NodeType::Leaf:
return yang::NodeTypes::Leaf;
- case LYS_LIST:
+ case libyang::NodeType::List:
return yang::NodeTypes::List;
- case LYS_RPC:
+ case libyang::NodeType::RPC:
return yang::NodeTypes::Rpc;
- case LYS_ACTION:
+ case libyang::NodeType::Action:
return yang::NodeTypes::Action;
- case LYS_NOTIF:
+ case libyang::NodeType::Notification:
return yang::NodeTypes::Notification;
- case LYS_ANYXML:
+ case libyang::NodeType::AnyXML:
return yang::NodeTypes::AnyXml;
- case LYS_LEAFLIST:
+ case libyang::NodeType::Leaflist:
return yang::NodeTypes::LeafList;
default:
throw InvalidNodeException(); // FIXME: Implement all types.
@@ -507,56 +446,57 @@
yang::NodeTypes YangSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const
{
- return impl_nodeType(getSchemaNode(location, node));
+ return impl_nodeType(*getSchemaNode(location, node));
}
yang::NodeTypes YangSchema::nodeType(const std::string& path) const
{
- return impl_nodeType(getSchemaNode(path));
+ return impl_nodeType(*getSchemaNode(path));
}
std::optional<std::string> YangSchema::description(const std::string& path) const
{
- auto node = getSchemaNode(path.c_str());
- return node->dsc() ? std::optional{node->dsc()} : std::nullopt;
+ auto desc = getSchemaNode(path.c_str())->description();
+ return desc ? std::optional<std::string>{desc} : std::nullopt;
+
}
yang::Status YangSchema::status(const std::string& location) const
{
auto node = getSchemaNode(location.c_str());
- if (node->flags() & LYS_STATUS_DEPRC) {
+ switch (node->status()) {
+ case libyang::Status::Deprecated:
return yang::Status::Deprecated;
- } else if (node->flags() & LYS_STATUS_OBSLT) {
+ case libyang::Status::Obsolete:
return yang::Status::Obsolete;
- } else {
+ case libyang::Status::Current:
return yang::Status::Current;
}
+
+ __builtin_unreachable();
}
bool YangSchema::hasInputNodes(const std::string& path) const
{
auto node = getSchemaNode(path.c_str());
- if (auto type = node->nodetype(); type != LYS_ACTION && type != LYS_RPC) {
+ if (auto type = node->nodeType(); type != libyang::NodeType::Action && type != libyang::NodeType::RPC) {
throw std::logic_error("StaticSchema::hasInputNodes called with non-RPC/action path");
}
// The first child gives the /input node and then I check whether it has a child.
- return node->child()->child().get();
+ return node->child()->child().has_value();
}
bool YangSchema::isConfig(const std::string& path) const
{
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) {
+ try {
+ if (node->config() == libyang::Config::True) {
return true;
}
+ } catch (libyang::Error&) {
+ // For non-data nodes (like `rpc`), the config value can't be retrieved. In this case, we'll just default to
+ // "false".
}
return false;
@@ -564,22 +504,10 @@
std::optional<std::string> YangSchema::defaultValue(const std::string& leafPath) const
{
- libyang::Schema_Node_Leaf leaf(getSchemaNode(leafPath));
-
- if (auto leafDefault = leaf.dflt()) {
- return leafDefault;
- }
-
- for (auto type = leaf.type()->der(); type != nullptr; type = type->type()->der()) {
- if (auto defaultValue = type->dflt()) {
- return defaultValue;
- }
- }
-
- return std::nullopt;
+ return std::optional<std::string>{getSchemaNode(leafPath)->asLeaf().defaultValueStr()};
}
std::string YangSchema::dataPathToSchemaPath(const std::string& path)
{
- return getSchemaNode(path)->path(LYS_PATH_FIRST_PREFIX);
+ return std::string{getSchemaNode(path)->path()};
}
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index dc45097..82d9acf 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -9,26 +9,19 @@
#pragma once
#include <functional>
+#include <libyang-cpp/Context.hpp>
#include <optional>
#include <set>
#include "ast_path.hpp"
#include "schema.hpp"
-namespace libyang {
-class Context;
-class Schema_Node;
-class Schema_Node_Leaf;
-class Data_Node;
-class Module;
-}
-
/*! \class YangSchema
* \brief A schema class, which uses libyang for queries.
* */
class YangSchema : public Schema {
public:
YangSchema();
- YangSchema(std::shared_ptr<libyang::Context> lyCtx);
+ YangSchema(libyang::Context lyCtx);
~YangSchema() override;
[[nodiscard]] yang::NodeTypes nodeType(const std::string& path) const override;
@@ -54,8 +47,8 @@
/** @short Loads a module called moduleName. */
void loadModule(const std::string& moduleName);
- /** @short Enables a feature called featureName on a module called moduleName. */
- void enableFeature(const std::string& moduleName, const std::string& featureName);
+ /** @short Sets enabled features. */
+ void setEnabledFeatures(const std::string& moduleName, const std::vector<std::string>& features);
/** @short Adds a new module passed as a YANG string. */
void addSchemaString(const char* schema);
@@ -67,26 +60,26 @@
void addSchemaDirectory(const char* directoryName);
/** @short Creates a new data node from a path (to be used with NETCONF edit-config) */
- [[nodiscard]] std::shared_ptr<libyang::Data_Node> dataNodeFromPath(const std::string& path, const std::optional<const std::string> value = std::nullopt) const;
- std::shared_ptr<libyang::Module> getYangModule(const std::string& name);
+ [[nodiscard]] libyang::CreatedNodes dataNodeFromPath(const std::string& path, const std::optional<const std::string> value = std::nullopt) const;
+ std::optional<libyang::Module> getYangModule(const std::string& name);
[[nodiscard]] std::string dataPathToSchemaPath(const std::string& path);
private:
friend class YangAccess;
template <typename NodeType>
- [[nodiscard]] yang::TypeInfo impl_leafType(const std::shared_ptr<libyang::Schema_Node>& node) const;
+ [[nodiscard]] yang::TypeInfo impl_leafType(const NodeType& node) const;
[[nodiscard]] std::set<std::string> modules() const;
- /** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullptr. */
- [[nodiscard]] std::shared_ptr<libyang::Schema_Node> getSchemaNode(const std::string& node) const;
- /** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullptr. */
- [[nodiscard]] std::shared_ptr<libyang::Schema_Node> getSchemaNode(const schemaPath_& listPath) const;
+ /** @short Returns a single SchemaNode if the criteria matches only one, otherwise nullopt. */
+ [[nodiscard]] std::optional<libyang::SchemaNode> getSchemaNode(const std::string& node) const;
+ /** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullopt. */
+ [[nodiscard]] std::optional<libyang::SchemaNode> getSchemaNode(const schemaPath_& listPath) const;
/** @short Returns a single Schema_Node if the criteria matches only one, otherwise nullptr. */
- [[nodiscard]] std::shared_ptr<libyang::Schema_Node> getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const;
- std::shared_ptr<libyang::Context> m_context;
+ [[nodiscard]] std::optional<libyang::SchemaNode> getSchemaNode(const schemaPath_& location, const ModuleNodePair& node) const;
+ libyang::Context m_context;
- [[nodiscard]] std::shared_ptr<libyang::Schema_Node> impl_getSchemaNode(const std::string& node) const;
+ [[nodiscard]] std::optional<libyang::SchemaNode> impl_getSchemaNode(const std::string& node) const;
};
diff --git a/submodules/dependencies b/submodules/dependencies
index 61e31aa..0c71c1d 160000
--- a/submodules/dependencies
+++ b/submodules/dependencies
@@ -1 +1 @@
-Subproject commit 61e31aa3927fcf29612de53a790c44d77d56a1fa
+Subproject commit 0c71c1d0c36238515418c901e169a95dc2321936
diff --git a/tests/cleanup_datastore.bash.in b/tests/cleanup_datastore.bash.in
index f5f71ac..7fdd986 100755
--- a/tests/cleanup_datastore.bash.in
+++ b/tests/cleanup_datastore.bash.in
@@ -28,8 +28,8 @@
# https://stackoverflow.com/a/41613532
tail --pid="$NETOPEER_PID" -f /dev/null
- rm "$NETOPEER_SOCKET"
+ rm -f "$NETOPEER_SOCKET"
fi
-rm -r "$SYSREPO_REPOSITORY_PATH"
+rm -rf "$SYSREPO_REPOSITORY_PATH"
rm -rf "/dev/shm/$SYSREPO_SHM_PREFIX"*
diff --git a/tests/data_query.cpp b/tests/data_query.cpp
index 64250da..642a0e0 100644
--- a/tests/data_query.cpp
+++ b/tests/data_query.cpp
@@ -29,9 +29,8 @@
{
trompeloeil::sequence seq1;
{
- auto conn = std::make_shared<sysrepo::Connection>();
- auto sess = std::make_shared<sysrepo::Session>(conn);
- sess->copy_config(SR_DS_STARTUP, "example-schema", 1000, true);
+ auto sess = sysrepo::Connection{}.sessionStart();
+ sess.copyConfig(sysrepo::Datastore::Startup, "example-schema", std::chrono::milliseconds{1000});
}
SysrepoSubscription subscriptionExample("example-schema");
SysrepoSubscription subscriptionOther("other-module");
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index bbcae25..8a7a9f5 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -8,6 +8,8 @@
#include "trompeloeil_doctest.hpp"
#include <sysrepo-cpp/Session.hpp>
+#include <sysrepo-cpp/utils/exception.hpp>
+#include <sysrepo-cpp/utils/utils.hpp>
#include "proxy_datastore.hpp"
#include "yang_schema.hpp"
@@ -15,9 +17,9 @@
#include "sysrepo_access.hpp"
using OnInvalidSchemaPathCreate = DatastoreException;
using OnInvalidSchemaPathDelete = DatastoreException;
-using OnInvalidSchemaPathMove = sysrepo::sysrepo_exception;
+using OnInvalidSchemaPathMove = sysrepo::ErrorWithCode;
using OnInvalidRpcPath = std::runtime_error;
-using OnInvalidRpcInput = sysrepo::sysrepo_exception;
+using OnInvalidRpcInput = sysrepo::ErrorWithCode;
using OnKeyNotFound = void;
using OnExec = void;
#elif defined(netconf_BACKEND)
@@ -51,7 +53,7 @@
class MockRecorder : public trompeloeil::mock_interface<Recorder> {
public:
- IMPLEMENT_MOCK3(write);
+ IMPLEMENT_MOCK5(write);
};
class MockDataSupplier : public trompeloeil::mock_interface<DataSupplier> {
@@ -73,8 +75,8 @@
REQUIRE_THROWS_AS(what(), std::logic_error);
} else if constexpr (std::is_same<Exception, DatastoreException>()) {
REQUIRE_THROWS_AS(what(), DatastoreException);
- } else if constexpr (std::is_same<Exception, sysrepo::sysrepo_exception>()) {
- REQUIRE_THROWS_AS(what(), sysrepo::sysrepo_exception);
+ } else if constexpr (std::is_same<Exception, sysrepo::ErrorWithCode>()) {
+ REQUIRE_THROWS_AS(what(), sysrepo::ErrorWithCode);
} else {
static_assert(always_false<Exception>); // https://stackoverflow.com/a/53945549/2245623
}
@@ -111,17 +113,15 @@
TEST_CASE("setting/getting values")
{
- sr_log_stderr(SR_LL_DBG);
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
trompeloeil::sequence seq1;
MockRecorder mockRunning;
MockRecorder mockStartup;
- {
- auto conn = std::make_shared<sysrepo::Connection>();
- auto sess = std::make_shared<sysrepo::Session>(conn);
- sess->copy_config(SR_DS_STARTUP, "example-schema", 1000, true);
- }
+
+ sysrepo::Connection{}.sessionStart().copyConfig(sysrepo::Datastore::Startup, "example-schema", std::chrono::milliseconds(1000));
+
SysrepoSubscription subRunning("example-schema", &mockRunning);
- SysrepoSubscription subStartup("example-schema", &mockStartup, SR_DS_STARTUP);
+ SysrepoSubscription subStartup("example-schema", &mockStartup, sysrepo::Datastore::Startup);
#ifdef sysrepo_BACKEND
SysrepoAccess datastore;
@@ -139,83 +139,83 @@
SECTION("set leafInt8 to -128")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafInt8", std::nullopt, "-128"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafInt8", std::nullopt, "-128"s, std::nullopt));
datastore.setLeaf("/example-schema:leafInt8", int8_t{-128});
datastore.commitChanges();
}
SECTION("set leafInt16 to -32768")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafInt16", std::nullopt, "-32768"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafInt16", std::nullopt, "-32768"s, std::nullopt));
datastore.setLeaf("/example-schema:leafInt16", int16_t{-32768});
datastore.commitChanges();
}
SECTION("set leafInt32 to -2147483648")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafInt32", std::nullopt, "-2147483648"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafInt32", std::nullopt, "-2147483648"s, std::nullopt));
datastore.setLeaf("/example-schema:leafInt32", int32_t{-2147483648});
datastore.commitChanges();
}
SECTION("set leafInt64 to -50000000000")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafInt64", std::nullopt, "-50000000000"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafInt64", std::nullopt, "-50000000000"s, std::nullopt));
datastore.setLeaf("/example-schema:leafInt64", int64_t{-50000000000});
datastore.commitChanges();
}
SECTION("set leafUInt8 to 255")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafUInt8", std::nullopt, "255"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafUInt8", std::nullopt, "255"s, std::nullopt));
datastore.setLeaf("/example-schema:leafUInt8", uint8_t{255});
datastore.commitChanges();
}
SECTION("set leafUInt16 to 65535")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafUInt16", std::nullopt, "65535"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafUInt16", std::nullopt, "65535"s, std::nullopt));
datastore.setLeaf("/example-schema:leafUInt16", uint16_t{65535});
datastore.commitChanges();
}
SECTION("set leafUInt32 to 4294967295")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafUInt32", std::nullopt, "4294967295"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafUInt32", std::nullopt, "4294967295"s, std::nullopt));
datastore.setLeaf("/example-schema:leafUInt32", uint32_t{4294967295});
datastore.commitChanges();
}
SECTION("set leafUInt64 to 50000000000")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafUInt64", std::nullopt, "50000000000"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafUInt64", std::nullopt, "50000000000"s, std::nullopt));
datastore.setLeaf("/example-schema:leafUInt64", uint64_t{50000000000});
datastore.commitChanges();
}
SECTION("set leafEnum to coze")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafEnum", std::nullopt, "coze"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafEnum", std::nullopt, "coze"s, std::nullopt));
datastore.setLeaf("/example-schema:leafEnum", enum_{"coze"});
datastore.commitChanges();
}
SECTION("set leafDecimal to 123.544")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafDecimal", std::nullopt, "123.544"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafDecimal", std::nullopt, "123.544"s, std::nullopt));
datastore.setLeaf("/example-schema:leafDecimal", 123.544);
datastore.commitChanges();
}
SECTION("set a string, then delete it")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafString", std::nullopt, "blah"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafString", std::nullopt, "blah"s, std::nullopt));
datastore.setLeaf("/example-schema:leafString", "blah"s);
datastore.commitChanges();
DatastoreAccess::Tree expected{{"/example-schema:leafString", "blah"s}};
REQUIRE(datastore.getItems("/example-schema:leafString") == expected);
- REQUIRE_CALL(mockRunning, write("/example-schema:leafString", "blah"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:leafString", "blah"s, std::nullopt, std::nullopt));
datastore.deleteItem("/example-schema:leafString");
datastore.commitChanges();
expected.clear();
@@ -224,7 +224,7 @@
SECTION("set a string, then set it to something else without commiting")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafString", std::nullopt, "oops"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafString", std::nullopt, "oops"s, std::nullopt));
datastore.setLeaf("/example-schema:leafString", "blah"s);
datastore.setLeaf("/example-schema:leafString", "oops"s);
datastore.commitChanges();
@@ -243,7 +243,7 @@
SECTION("create presence container")
{
REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:pContainer") == std::string::npos);
- REQUIRE_CALL(mockRunning, write("/example-schema:pContainer", std::nullopt, ""s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:pContainer", std::nullopt, std::nullopt, std::nullopt));
datastore.createItem("/example-schema:pContainer");
datastore.commitChanges();
REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:pContainer") != std::string::npos);
@@ -252,14 +252,14 @@
SECTION("create/delete a list instance")
{
{
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Nguyen']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Nguyen']/name", std::nullopt, "Nguyen"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Nguyen']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Nguyen']/name", std::nullopt, "Nguyen"s, std::nullopt));
datastore.createItem("/example-schema:person[name='Nguyen']");
datastore.commitChanges();
}
{
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Nguyen']", ""s, std::nullopt));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Nguyen']/name", "Nguyen"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:person[name='Nguyen']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:person[name='Nguyen']/name", "Nguyen"s, std::nullopt, std::nullopt));
datastore.deleteItem("/example-schema:person[name='Nguyen']");
datastore.commitChanges();
}
@@ -288,12 +288,12 @@
SECTION("leafref pointing to a key of a list")
{
{
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Dan']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Dan']/name", std::nullopt, "Dan"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Elfi']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Elfi']/name", std::nullopt, "Elfi"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Kolafa']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Kolafa']/name", std::nullopt, "Kolafa"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Dan']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Dan']/name", std::nullopt, "Dan"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Elfi']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Elfi']/name", std::nullopt, "Elfi"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Kolafa']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Kolafa']/name", std::nullopt, "Kolafa"s, std::nullopt));
datastore.createItem("/example-schema:person[name='Dan']");
datastore.createItem("/example-schema:person[name='Elfi']");
datastore.createItem("/example-schema:person[name='Kolafa']");
@@ -318,15 +318,16 @@
datastore.setLeaf("/example-schema:bossPerson", value);
{
- REQUIRE_CALL(mockRunning, write("/example-schema:bossPerson", std::nullopt, value));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:bossPerson", std::nullopt, value, std::nullopt));
datastore.commitChanges();
}
REQUIRE(datastore.getItems("/example-schema:bossPerson") == DatastoreAccess::Tree{{"/example-schema:bossPerson", value}});
}
+
SECTION("bool values get correctly represented as bools")
{
{
- REQUIRE_CALL(mockRunning, write("/example-schema:down", std::nullopt, "true"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:down", std::nullopt, "true"s, std::nullopt));
datastore.setLeaf("/example-schema:down", bool{true});
datastore.commitChanges();
}
@@ -338,8 +339,8 @@
SECTION("getting items from the whole module")
{
{
- REQUIRE_CALL(mockRunning, write("/example-schema:up", std::nullopt, "true"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:down", std::nullopt, "false"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:up", std::nullopt, "true"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:down", std::nullopt, "false"s, std::nullopt));
datastore.setLeaf("/example-schema:up", bool{true});
datastore.setLeaf("/example-schema:down", bool{false});
datastore.commitChanges();
@@ -355,7 +356,7 @@
SECTION("getItems returns correct datatypes")
{
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafEnum", std::nullopt, "lol"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafEnum", std::nullopt, "lol"s, std::nullopt));
datastore.setLeaf("/example-schema:leafEnum", enum_{"lol"});
datastore.commitChanges();
}
@@ -367,12 +368,12 @@
SECTION("getItems on a list")
{
{
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Jan']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Jan']/name", std::nullopt, "Jan"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Michal']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Michal']/name", std::nullopt, "Michal"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Petr']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:person[name='Petr']/name", std::nullopt, "Petr"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Jan']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Jan']/name", std::nullopt, "Jan"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Michal']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Michal']/name", std::nullopt, "Michal"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Petr']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:person[name='Petr']/name", std::nullopt, "Petr"s, std::nullopt));
datastore.createItem("/example-schema:person[name='Jan']");
datastore.createItem("/example-schema:person[name='Michal']");
datastore.createItem("/example-schema:person[name='Petr']");
@@ -397,7 +398,7 @@
REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
{
- REQUIRE_CALL(mockRunning, write("/example-schema:pContainer", std::nullopt, ""s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:pContainer", std::nullopt, std::nullopt, std::nullopt));
datastore.createItem("/example-schema:pContainer");
datastore.commitChanges();
}
@@ -408,7 +409,7 @@
// Make sure it's not there after we delete it
{
- REQUIRE_CALL(mockRunning, write("/example-schema:pContainer", ""s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:pContainer", std::nullopt, std::nullopt, std::nullopt));
datastore.deleteItem("/example-schema:pContainer");
datastore.commitChanges();
}
@@ -438,7 +439,7 @@
// Make sure it's not there before we create it
REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
{
- REQUIRE_CALL(mockRunning, write("/example-schema:inventory/stuff", std::nullopt, ""s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:inventory/stuff", std::nullopt, std::nullopt, std::nullopt));
datastore.createItem("/example-schema:inventory/stuff");
datastore.commitChanges();
}
@@ -447,7 +448,7 @@
};
REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
{
- REQUIRE_CALL(mockRunning, write("/example-schema:inventory/stuff", ""s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:inventory/stuff", std::nullopt, std::nullopt, std::nullopt));
datastore.deleteItem("/example-schema:inventory/stuff");
datastore.commitChanges();
}
@@ -458,7 +459,7 @@
SECTION("floats")
{
datastore.setLeaf("/example-schema:leafDecimal", 123.4);
- REQUIRE_CALL(mockRunning, write("/example-schema:leafDecimal", std::nullopt, "123.4"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafDecimal", std::nullopt, "123.4"s, std::nullopt));
datastore.commitChanges();
DatastoreAccess::Tree expected{
{"/example-schema:leafDecimal", 123.4},
@@ -469,7 +470,7 @@
SECTION("unions")
{
datastore.setLeaf("/example-schema:unionIntString", int32_t{10});
- REQUIRE_CALL(mockRunning, write("/example-schema:unionIntString", std::nullopt, "10"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:unionIntString", std::nullopt, "10"s, std::nullopt));
datastore.commitChanges();
DatastoreAccess::Tree expected{
{"/example-schema:unionIntString", int32_t{10}},
@@ -480,7 +481,7 @@
SECTION("identityref")
{
datastore.setLeaf("/example-schema:beast", identityRef_{"example-schema", "Mammal"});
- REQUIRE_CALL(mockRunning, write("/example-schema:beast", std::nullopt, "example-schema:Mammal"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:beast", std::nullopt, "example-schema:Mammal"s, std::nullopt));
datastore.commitChanges();
DatastoreAccess::Tree expected{
{"/example-schema:beast", identityRef_{"example-schema", "Mammal"}},
@@ -488,7 +489,7 @@
REQUIRE(datastore.getItems("/example-schema:beast") == expected);
datastore.setLeaf("/example-schema:beast", identityRef_{"Whale"});
- REQUIRE_CALL(mockRunning, write("/example-schema:beast", "example-schema:Mammal", "example-schema:Whale"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Modified, "/example-schema:beast", "example-schema:Mammal", "example-schema:Whale"s, std::nullopt));
datastore.commitChanges();
expected = {
{"/example-schema:beast", identityRef_{"example-schema", "Whale"}},
@@ -499,7 +500,7 @@
SECTION("binary")
{
datastore.setLeaf("/example-schema:blob", binary_{"cHduegByIQ=="s});
- REQUIRE_CALL(mockRunning, write("/example-schema:blob", std::nullopt, "cHduegByIQ=="s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:blob", std::nullopt, "cHduegByIQ=="s, std::nullopt));
datastore.commitChanges();
DatastoreAccess::Tree expected{
{"/example-schema:blob", binary_{"cHduegByIQ=="s}},
@@ -510,7 +511,7 @@
SECTION("empty")
{
datastore.setLeaf("/example-schema:dummy", empty_{});
- REQUIRE_CALL(mockRunning, write("/example-schema:dummy", std::nullopt, ""s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:dummy", std::nullopt, ""s, std::nullopt));
datastore.commitChanges();
DatastoreAccess::Tree expected{
{"/example-schema:dummy", empty_{}},
@@ -521,7 +522,7 @@
SECTION("bits")
{
datastore.setLeaf("/example-schema:flags", bits_{{"sign", "carry"}});
- REQUIRE_CALL(mockRunning, write("/example-schema:flags", std::nullopt, "carry sign"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:flags", std::nullopt, "carry sign"s, std::nullopt));
datastore.commitChanges();
DatastoreAccess::Tree expected{
{"/example-schema:flags", bits_{{"carry", "sign"}}},
@@ -564,8 +565,8 @@
SECTION("leaf list")
{
DatastoreAccess::Tree expected;
- REQUIRE_CALL(mockRunning, write("/example-schema:addresses", std::nullopt, "0.0.0.0"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:addresses", std::nullopt, "127.0.0.1"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:addresses[.='0.0.0.0']", std::nullopt, "0.0.0.0"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:addresses[.='127.0.0.1']", std::nullopt, "127.0.0.1"s, std::nullopt));
datastore.createItem("/example-schema:addresses[.='0.0.0.0']");
datastore.createItem("/example-schema:addresses[.='127.0.0.1']");
datastore.commitChanges();
@@ -576,7 +577,7 @@
};
REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
- REQUIRE_CALL(mockRunning, write("/example-schema:addresses", "0.0.0.0"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:addresses[.='0.0.0.0']", "0.0.0.0"s, std::nullopt, std::nullopt));
datastore.deleteItem("/example-schema:addresses[.='0.0.0.0']");
datastore.commitChanges();
expected = {
@@ -585,7 +586,7 @@
};
REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
- REQUIRE_CALL(mockRunning, write("/example-schema:addresses", "127.0.0.1"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s, std::nullopt, std::nullopt));
datastore.deleteItem("/example-schema:addresses[.='127.0.0.1']");
datastore.commitChanges();
expected = {};
@@ -617,12 +618,12 @@
{
{
REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
- REQUIRE_CALL(mockRunning, write("/example-schema:leafInt16", std::nullopt, "123"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafInt16", std::nullopt, "123"s, std::nullopt));
datastore.setLeaf("/example-schema:leafInt16", int16_t{123});
datastore.commitChanges();
}
REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{{"/example-schema:leafInt16", int16_t{123}}});
- REQUIRE_CALL(mockRunning, write("/example-schema:leafInt16", "123"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Deleted, "/example-schema:leafInt16", "123"s, std::nullopt, std::nullopt));
datastore.copyConfig(Datastore::Startup, Datastore::Running);
REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
}
@@ -631,13 +632,9 @@
{
DatastoreAccess::Tree expected;
{
- REQUIRE_CALL(mockRunning, write("/example-schema:protocols", std::nullopt, "http"s));
- // FIXME: Why no notifications for these??
- // ... possibly because my subscription doesn't extract it properly?
- // REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "ftp"s));
- // REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "pop3"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:protocols", "http"s, "ftp"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:protocols", "ftp"s, "pop3"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:protocols[.='http']", "", "http"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:protocols[.='ftp']", "http"s, "ftp"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:protocols[.='pop3']", "ftp"s, "pop3"s, std::nullopt));
datastore.createItem("/example-schema:protocols[.='http']");
datastore.createItem("/example-schema:protocols[.='ftp']");
datastore.createItem("/example-schema:protocols[.='pop3']");
@@ -654,7 +651,7 @@
std::string sourcePath;
SECTION("begin")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:protocols", std::nullopt, "pop3"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='pop3']", ""s, "pop3"s, std::nullopt));
sourcePath = "/example-schema:protocols[.='pop3']";
datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
datastore.commitChanges();
@@ -670,7 +667,16 @@
SECTION("end")
{
sourcePath = "/example-schema:protocols[.='http']";
- REQUIRE_CALL(mockRunning, write("/example-schema:protocols", "pop3"s, "http"s));
+
+#if defined(yang_BACKEND) || defined(netconf_BACKEND)
+ // Due to the libyang diff algorithm being imperfect, the move operations differ between backends.
+ // The same applies for the stuff below.
+ // https://github.com/sysrepo/sysrepo/issues/2732
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='ftp']", ""s, "ftp"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='pop3']", "ftp"s, "pop3"s, std::nullopt));
+#else
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='http']", "pop3"s, "http"s, std::nullopt));
+#endif
datastore.moveItem(sourcePath, yang::move::Absolute::End);
datastore.commitChanges();
expected = {
@@ -685,7 +691,12 @@
SECTION("after")
{
sourcePath = "/example-schema:protocols[.='http']";
- REQUIRE_CALL(mockRunning, write("/example-schema:protocols", "ftp"s, "http"s));
+#if defined(yang_BACKEND) || defined(netconf_BACKEND)
+ // see the test for "end" for explanation if this #ifdef
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='ftp']", ""s, "ftp"s, std::nullopt));
+#else
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='http']", "ftp"s, "http"s, std::nullopt));
+#endif
datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{".", "ftp"s}}});
datastore.commitChanges();
expected = {
@@ -700,7 +711,12 @@
SECTION("before")
{
sourcePath = "/example-schema:protocols[.='http']";
- REQUIRE_CALL(mockRunning, write("/example-schema:protocols", "ftp"s, "http"s));
+#if defined(yang_BACKEND) || defined(netconf_BACKEND)
+ // see the test for "end" for explanation if this #ifdef
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='ftp']", ""s, "ftp"s, std::nullopt));
+#else
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:protocols[.='http']", "ftp"s, "http"s, std::nullopt));
+#endif
datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{".", "pop3"s}}});
datastore.commitChanges();
expected = {
@@ -725,12 +741,12 @@
{
DatastoreAccess::Tree expected;
{
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='John']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='John']/name", std::nullopt, "John"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='Eve']", ""s, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='Eve']/name", std::nullopt, "Eve"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='Adam']", ""s, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='Adam']/name", std::nullopt, "Adam"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:players[name='John']", std::nullopt, std::nullopt, ""s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:players[name='John']/name", std::nullopt, "John"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:players[name='Eve']", std::nullopt, std::nullopt, "[name='John']"));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:players[name='Eve']/name", std::nullopt, "Eve"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:players[name='Adam']", std::nullopt, std::nullopt, "[name='Eve']"));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:players[name='Adam']/name", std::nullopt, "Adam"s, std::nullopt));
datastore.createItem("/example-schema:players[name='John']");
datastore.createItem("/example-schema:players[name='Eve']");
datastore.createItem("/example-schema:players[name='Adam']");
@@ -750,7 +766,7 @@
SECTION("begin")
{
sourcePath = "/example-schema:players[name='Adam']";
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='Adam']", std::nullopt, ""s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='Adam']", std::nullopt, std::nullopt, ""s));
datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
datastore.commitChanges();
expected = {
@@ -767,7 +783,14 @@
SECTION("end")
{
sourcePath = "/example-schema:players[name='John']";
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='John']", ""s, ""s));
+#if defined(yang_BACKEND) || defined(netconf_BACKEND)
+ // TODO: see TODO comment in leaflist/end
+ // Although these make much less sense
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='Eve']", std::nullopt, std::nullopt, ""));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='Adam']", std::nullopt, std::nullopt, "[name='Eve']"));
+#else
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='John']", std::nullopt, std::nullopt, "[name='Adam']"));
+#endif
datastore.moveItem(sourcePath, yang::move::Absolute::End);
datastore.commitChanges();
expected = {
@@ -784,7 +807,13 @@
SECTION("after")
{
sourcePath = "/example-schema:players[name='John']";
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='John']", ""s, ""s));
+#if defined(yang_BACKEND) || defined(netconf_BACKEND)
+ // TODO: see TODO comment in leaflist/end
+ // Although these make much less sense
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='Eve']", std::nullopt, std::nullopt, ""));
+#else
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='John']", std::nullopt, std::nullopt, "[name='Eve']"));
+#endif
datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{"name", "Eve"s}}});
datastore.commitChanges();
expected = {
@@ -801,7 +830,13 @@
SECTION("before")
{
sourcePath = "/example-schema:players[name='John']";
- REQUIRE_CALL(mockRunning, write("/example-schema:players[name='John']", ""s, ""s));
+#if defined(yang_BACKEND) || defined(netconf_BACKEND)
+ // TODO: see TODO comment in leaflist/end
+ // Although these make much less sense
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='Eve']", std::nullopt, std::nullopt, ""));
+#else
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Moved, "/example-schema:players[name='John']", std::nullopt, std::nullopt, "[name='Eve']"));
+#endif
datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{"name", "Adam"s}}});
datastore.commitChanges();
expected = {
@@ -819,7 +854,7 @@
SECTION("getting /")
{
{
- REQUIRE_CALL(mockRunning, write("/example-schema:leafInt32", std::nullopt, "64"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:leafInt32", std::nullopt, "64"s, std::nullopt));
datastore.setLeaf("/example-schema:leafInt32", 64);
datastore.commitChanges();
}
@@ -841,9 +876,9 @@
SECTION("two key lists")
{
- REQUIRE_CALL(mockRunning, write("/example-schema:point[x='12'][y='10']", std::nullopt, ""s));
- REQUIRE_CALL(mockRunning, write("/example-schema:point[x='12'][y='10']/x", std::nullopt, "12"s));
- REQUIRE_CALL(mockRunning, write("/example-schema:point[x='12'][y='10']/y", std::nullopt, "10"s));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:point[x='12'][y='10']", std::nullopt, std::nullopt, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:point[x='12'][y='10']/x", std::nullopt, "12"s, std::nullopt));
+ REQUIRE_CALL(mockRunning, write(sysrepo::ChangeOperation::Created, "/example-schema:point[x='12'][y='10']/y", std::nullopt, "10"s, std::nullopt));
datastore.createItem("/example-schema:point[x='12'][y='10']");
datastore.commitChanges();
REQUIRE(datastore.dump(DataFormat::Json).find("example-schema:point") != std::string::npos);
@@ -853,69 +888,74 @@
}
struct ActionCb {
- int operator()(sysrepo::S_Session session,
- const char* xpath,
- [[maybe_unused]] const sysrepo::S_Vals input,
- [[maybe_unused]] sr_event_t event,
- [[maybe_unused]] uint32_t request_id,
- sysrepo::S_Vals_Holder output)
+ sysrepo::ErrorCode operator()(
+ [[maybe_unused]] sysrepo::Session session,
+ [[maybe_unused]] uint32_t subscriptionId,
+ std::string_view xpath,
+ [[maybe_unused]] const libyang::DataNode input,
+ [[maybe_unused]] sysrepo::Event event,
+ [[maybe_unused]] uint32_t requestId,
+ libyang::DataNode output)
{
- if (session->get_context()->get_node(nullptr, xpath)->path(LYS_PATH_FIRST_PREFIX) == "/example-schema:ports/shutdown") {
- auto buf = output->allocate(1);
- buf->val(0)->set(joinPaths(xpath, "success").c_str(), true);
- return SR_ERR_OK;
+ if (session.getContext().findPath(xpath.data()).path() == "/example-schema:ports/shutdown") {
+ // `xpath` holds the subscription xpath which won't have list keys. We need the path with list keys and
+ // we'll find that in the input.
+ auto inputPath = input.findXPath("/example-schema:ports/shutdown").front().path();
+ output.newPath(joinPaths(std::string{inputPath}, "success").c_str(), "true", libyang::CreationOptions::Output);
+ return sysrepo::ErrorCode::Ok;
}
throw std::runtime_error("unrecognized RPC");
}
};
struct RpcCb {
- int operator()([[maybe_unused]] sysrepo::S_Session session,
- const char* xpath,
- const sysrepo::S_Vals input,
- [[maybe_unused]] sr_event_t event,
- [[maybe_unused]] uint32_t request_id,
- sysrepo::S_Vals_Holder output)
+ sysrepo::ErrorCode operator()(
+ [[maybe_unused]] sysrepo::Session session,
+ [[maybe_unused]] uint32_t subscriptionId,
+ std::string_view xpath,
+ const libyang::DataNode input,
+ [[maybe_unused]] sysrepo::Event event,
+ [[maybe_unused]] uint32_t requestId,
+ libyang::DataNode output)
{
const auto nukes = "/example-schema:launch-nukes"s;
if (xpath == "/example-schema:noop"s || xpath == "/example-schema:fire"s) {
- return SR_ERR_OK;
+ return sysrepo::ErrorCode::Ok;
}
if (xpath == nukes) {
uint64_t kilotons = 0;
bool hasCities = false;
- for (size_t i = 0; i < input->val_cnt(); ++i) {
- const auto& val = input->val(i);
- if (val->xpath() == nukes + "/payload") {
+ for (const auto& inputNode : input.childrenDfs()) {
+ if (inputNode.path() == nukes) {
+ continue; // ignore, top-level RPC
+ }
+ if (inputNode.path() == nukes + "/payload") {
continue; // ignore, container
}
- if (val->xpath() == nukes + "/description") {
+ if (inputNode.path() == nukes + "/description") {
continue; // unused
}
- if (val->xpath() == nukes + "/payload/kilotons") {
- kilotons = val->data()->get_uint64();
- } else if (std::string_view{val->xpath()}.find(nukes + "/cities") == 0) {
+ if (inputNode.path() == nukes + "/payload/kilotons") {
+ kilotons = std::get<uint64_t>(inputNode.asTerm().value());
+ } else if (std::string_view{inputNode.path().get().get()}.find(nukes + "/cities") == 0) {
hasCities = true;
} else {
- throw std::runtime_error("RPC launch-nukes: unexpected input "s + val->xpath());
+ throw std::runtime_error("RPC launch-nukes: unexpected input "s + inputNode.path().get().get());
}
}
if (kilotons == 333'666) {
// magic, just do not generate any output. This is important because the NETCONF RPC returns just <ok/>.
- return SR_ERR_OK;
+ return sysrepo::ErrorCode::Ok;
}
- auto buf = output->allocate(2);
- size_t i = 0;
- buf->val(i++)->set((nukes + "/blast-radius").c_str(), uint32_t{33'666});
- buf->val(i++)->set((nukes + "/actual-yield").c_str(), static_cast<uint64_t>(1.33 * kilotons));
+ output.newPath((nukes + "/blast-radius").c_str(), "33666", libyang::CreationOptions::Output);
+ output.newPath((nukes + "/actual-yield").c_str(), std::to_string(static_cast<uint64_t>(1.33 * kilotons)).c_str(), libyang::CreationOptions::Output);
if (hasCities) {
- buf = output->reallocate(output->val_cnt() + 2);
- buf->val(i++)->set((nukes + "/damaged-places/targets[city='London']/city").c_str(), "London");
- buf->val(i++)->set((nukes + "/damaged-places/targets[city='Berlin']/city").c_str(), "Berlin");
+ output.newPath((nukes + "/damaged-places/targets[city='London']/city").c_str(), "London", libyang::CreationOptions::Output);
+ output.newPath((nukes + "/damaged-places/targets[city='Berlin']/city").c_str(), "Berlin", libyang::CreationOptions::Output);
}
- return SR_ERR_OK;
+ return sysrepo::ErrorCode::Ok;
}
throw std::runtime_error("unrecognized RPC");
}
@@ -938,18 +978,14 @@
#error "Unknown backend"
#endif
- auto srConn = std::make_shared<sysrepo::Connection>();
- auto srSession = std::make_shared<sysrepo::Session>(srConn);
- auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
- auto rpcCb = std::make_shared<RpcCb>();
- auto actionCb = std::make_shared<ActionCb>();
- sysrepo::Logs{}.set_stderr(SR_LL_INF);
+ sysrepo::setLogLevelStderr(sysrepo::LogLevel::Information);
+
+ auto srSubscription = sysrepo::Connection{}.sessionStart().onRPCAction("/example-schema:noop", RpcCb{});
+ srSubscription.onRPCAction("/example-schema:launch-nukes", RpcCb{});
+ srSubscription.onRPCAction("/example-schema:fire", RpcCb{});
+ srSubscription.onRPCAction("/example-schema:ports/shutdown", ActionCb{});
+
SysrepoSubscription subscription("example-schema", nullptr);
- // careful here, sysrepo insists on module_change CBs being registered before RPC CBs, otherwise there's a memleak
- srSubscription->rpc_subscribe("/example-schema:noop", RpcCb{}, 0, SR_SUBSCR_CTX_REUSE);
- srSubscription->rpc_subscribe("/example-schema:launch-nukes", RpcCb{}, 0, SR_SUBSCR_CTX_REUSE);
- srSubscription->rpc_subscribe("/example-schema:fire", RpcCb{}, 0, SR_SUBSCR_CTX_REUSE);
- srSubscription->rpc_subscribe("/example-schema:ports/shutdown", ActionCb{}, 0, SR_SUBSCR_CTX_REUSE);
SECTION("rpc")
{
@@ -1086,13 +1122,12 @@
{
const auto testNode = "/example-schema:leafInt32";
{
- auto conn = std::make_shared<sysrepo::Connection>();
- auto sess = std::make_shared<sysrepo::Session>(conn);
- sess->delete_item(testNode);
- sess->apply_changes(1000, 1);
- sess->session_switch_ds(SR_DS_STARTUP);
- sess->delete_item(testNode);
- sess->apply_changes(1000, 1);
+ auto sess = sysrepo::Connection{}.sessionStart();
+ sess.deleteItem(testNode);
+ sess.applyChanges(std::chrono::milliseconds{1000});
+ sess.switchDatastore(sysrepo::Datastore::Startup);
+ sess.deleteItem(testNode);
+ sess.applyChanges(std::chrono::milliseconds{1000});
}
MockRecorder mockRunning;
MockRecorder mockStartup;
diff --git a/tests/init_datastore.bash.in b/tests/init_datastore.bash.in
index 6eee340..d8eb2c3 100755
--- a/tests/init_datastore.bash.in
+++ b/tests/init_datastore.bash.in
@@ -22,7 +22,7 @@
shift
# Install the module
-"$SYSREPOCTL" --search-dirs "$YANG_DIR" --install "$MODULE" -a
+"$SYSREPOCTL" --search-dirs "$YANG_DIR" --install "$MODULE" -v3
BACKEND="$1"
shift
diff --git a/tests/mock/sysrepo_subscription.cpp b/tests/mock/sysrepo_subscription.cpp
index 8faffa5..d4827bb 100644
--- a/tests/mock/sysrepo_subscription.cpp
+++ b/tests/mock/sysrepo_subscription.cpp
@@ -21,29 +21,44 @@
{
}
- int operator()(
- sysrepo::S_Session sess,
- [[maybe_unused]] const char* module_name,
- [[maybe_unused]] const char* xpath,
- [[maybe_unused]] sr_event_t event,
- [[maybe_unused]] uint32_t request_id)
+ sysrepo::ErrorCode operator()(
+ sysrepo::Session sess,
+ uint32_t /* sub_id */,
+ std::string_view module_name,
+ std::optional<std::string_view> /* sub_xpath */,
+ sysrepo::Event event,
+ uint32_t /* request_id */)
{
using namespace std::string_literals;
- if (event == SR_EV_CHANGE) {
- return SR_ERR_OK;
+ if (event == sysrepo::Event::Change) {
+ return sysrepo::ErrorCode::Ok;
}
- auto it = sess->get_changes_iter(("/"s + module_name + ":*//.").c_str());
+ for (const auto& it : sess.getChanges(("/"s + module_name.data() + ":*//.").c_str())) {
+ auto xpath = it.node.path();
+ std::optional<std::string> oldValue;
+ std::optional<std::string> newValue;
+ if (it.operation == sysrepo::ChangeOperation::Deleted) {
+ oldValue = it.node.schema().nodeType() == libyang::NodeType::Leaf || it.node.schema().nodeType() == libyang::NodeType::Leaflist ?
+ std::optional<std::string>{it.node.asTerm().valueStr()} :
+ std::nullopt;
+ } else {
+ oldValue = std::optional<std::string>{it.previousValue};
+ newValue = it.node.schema().nodeType() == libyang::NodeType::Leaf || it.node.schema().nodeType() == libyang::NodeType::Leaflist ?
+ std::optional<std::string>{it.node.asTerm().valueStr()} :
+ std::nullopt;
- while (auto change = sess->get_change_next(it)) {
- auto xpath = (change->new_val() ? change->new_val() : change->old_val())->xpath();
+ }
+ std::optional<std::string> previousList;
- auto oldValue = change->old_val() ? std::optional{change->old_val()->val_to_string()} : std::nullopt;
- auto newValue = change->new_val() ? std::optional{change->new_val()->val_to_string()} : std::nullopt;
- m_recorder->write(xpath, oldValue, newValue);
+ if (it.previousList) {
+ previousList = std::string{*it.previousList};
+ }
+
+ m_recorder->write(it.operation, std::string{xpath}, oldValue, newValue, previousList);
}
- return SR_ERR_OK;
+ return sysrepo::ErrorCode::Ok;
}
private:
@@ -55,111 +70,39 @@
DataSupplier::~DataSupplier() = default;
-SysrepoSubscription::SysrepoSubscription(const std::string& moduleName, Recorder* rec, sr_datastore_t ds)
- : m_connection(std::make_shared<sysrepo::Connection>())
+SysrepoSubscription::SysrepoSubscription(const std::string& moduleName, Recorder* rec, sysrepo::Datastore ds)
+ : m_subscription([&moduleName, &rec, ds] { // This is an immediately invoked lambda.
+ return sysrepo::Connection{}.sessionStart(ds).onModuleChange(moduleName.c_str(),
+ rec ? sysrepo::ModuleChangeCb{MyCallback{moduleName, rec}}
+ : sysrepo::ModuleChangeCb{[](auto, auto, auto, auto, auto, auto) { return sysrepo::ErrorCode::Ok; }});
+ }())
{
- m_session = std::make_shared<sysrepo::Session>(m_connection, ds);
- m_subscription = std::make_shared<sysrepo::Subscribe>(m_session);
- sysrepo::ModuleChangeCb cb;
- if (rec) {
- cb = MyCallback{moduleName, rec};
- } else {
- cb = [](auto, auto, auto, auto, auto) { return SR_ERR_OK; };
- }
-
- m_subscription->module_change_subscribe(moduleName.c_str(), cb);
}
-
-struct leafDataToSysrepoVal {
- leafDataToSysrepoVal(sysrepo::S_Val v, const std::string& xpath)
- : v(v)
- , xpath(xpath)
- {
- }
-
- void operator()(const binary_& what)
- {
- v->set(xpath.c_str(), what.m_value.c_str(), SR_BINARY_T);
- }
-
- void operator()(const enum_& what)
- {
- v->set(xpath.c_str(), what.m_value.c_str(), SR_ENUM_T);
- }
-
- void operator()(const identityRef_& what)
- {
- v->set(xpath.c_str(), (what.m_prefix->m_name + what.m_value).c_str(), SR_IDENTITYREF_T);
- }
-
- void operator()(const empty_)
- {
- v->set(xpath.c_str(), nullptr, SR_LEAF_EMPTY_T);
- }
-
- void operator()(const std::string& what)
- {
- v->set(xpath.c_str(), what.c_str());
- }
-
- void operator()(const bits_& what)
- {
- std::stringstream ss;
- std::copy(what.m_bits.begin(), what.m_bits.end(), std::experimental::make_ostream_joiner(ss, " "));
- v->set(xpath.c_str(), ss.str().c_str());
- }
-
- template <typename Type>
- void operator()(const Type what)
- {
- v->set(xpath.c_str(), what);
- }
-
- void operator()([[maybe_unused]] const special_ what)
- {
- throw std::logic_error("Attempted to create a SR val from a special_ value");
- }
-
- ::sysrepo::S_Val v;
- std::string xpath;
-};
-
class OperationalDataCallback {
public:
OperationalDataCallback(const DataSupplier& dataSupplier)
: m_dataSupplier(dataSupplier)
{
}
- int operator()(
- [[maybe_unused]] sysrepo::S_Session sess,
- [[maybe_unused]] const char* module_name,
- const char* path,
- [[maybe_unused]] const char* request_xpath,
- [[maybe_unused]] uint32_t request_id,
- libyang::S_Data_Node& parent)
+ sysrepo::ErrorCode operator()(
+ sysrepo::Session session,
+ [[maybe_unused]] uint32_t subscriptionId,
+ [[maybe_unused]] std::string_view moduleName,
+ std::optional<std::string_view> subXPath,
+ [[maybe_unused]] std::optional<std::string_view> requestXPath,
+ [[maybe_unused]] uint32_t requestId,
+ std::optional<libyang::DataNode>& output)
{
- auto data = m_dataSupplier.get_data(path);
- libyang::S_Data_Node res;
+ auto data = m_dataSupplier.get_data(subXPath->data());
for (const auto& [p, v] : data) {
- if (!res) {
- res = std::make_shared<libyang::Data_Node>(
- sess->get_context(),
- p.c_str(),
- v.type() == typeid(empty_) ? nullptr : leafDataToString(v).c_str(),
- LYD_ANYDATA_CONSTSTRING,
- 0);
+ if (!output) {
+ output = session.getContext().newPath(p.c_str(), v.type() == typeid(empty_) ? nullptr : leafDataToString(v).c_str());
} else {
- res->new_path(
- sess->get_context(),
- p.c_str(),
- v.type() == typeid(empty_) ? nullptr : leafDataToString(v).c_str(),
- LYD_ANYDATA_CONSTSTRING,
- 0);
+ output->newPath(p.c_str(), v.type() == typeid(empty_) ? nullptr : leafDataToString(v).c_str());
}
}
- parent = res;
- return SR_ERR_OK;
+ return sysrepo::ErrorCode::Ok;
}
private:
@@ -167,9 +110,6 @@
};
OperationalDataSubscription::OperationalDataSubscription(const std::string& moduleName, const std::string& path, const DataSupplier& dataSupplier)
- : m_connection(std::make_shared<sysrepo::Connection>())
- , m_session(std::make_shared<sysrepo::Session>(m_connection))
- , m_subscription(std::make_shared<sysrepo::Subscribe>(m_session))
+ : m_subscription(sysrepo::Connection{}.sessionStart().onOperGet(moduleName.c_str(), OperationalDataCallback{dataSupplier}, path.c_str()))
{
- m_subscription->oper_get_items_subscribe(moduleName.c_str(), OperationalDataCallback{dataSupplier}, path.c_str());
}
diff --git a/tests/mock/sysrepo_subscription.hpp b/tests/mock/sysrepo_subscription.hpp
index fc2e499..85d048d 100644
--- a/tests/mock/sysrepo_subscription.hpp
+++ b/tests/mock/sysrepo_subscription.hpp
@@ -10,21 +10,15 @@
#include <memory>
#include <optional>
-#include <sysrepo-cpp/Session.hpp>
+#include <sysrepo-cpp/Connection.hpp>
#include "datastore_access.hpp"
-namespace sysrepo {
-class Callback;
-class Connection;
-class Session;
-class Subscribe;
-}
class YangSchema;
class Recorder {
public:
virtual ~Recorder();
- virtual void write(const std::string& xpath, const std::optional<std::string>& oldValue, const std::optional<std::string>& newValue) = 0;
+ virtual void write(const sysrepo::ChangeOperation operation, const std::string& xpath, const std::optional<std::string>& oldValue, const std::optional<std::string>& newValue, const std::optional<std::string> previousList) = 0;
};
class DataSupplier {
@@ -36,13 +30,10 @@
class SysrepoSubscription {
public:
- SysrepoSubscription(const std::string& moduleName, Recorder* rec = nullptr, sr_datastore_t ds = SR_DS_RUNNING);
+ SysrepoSubscription(const std::string& moduleName, Recorder* rec = nullptr, sysrepo::Datastore ds = sysrepo::Datastore::Running);
private:
- std::shared_ptr<sysrepo::Connection> m_connection;
- std::shared_ptr<sysrepo::Session> m_session;
- std::shared_ptr<YangSchema> m_schema;
- std::shared_ptr<sysrepo::Subscribe> m_subscription;
+ sysrepo::Subscription m_subscription;
};
class OperationalDataSubscription {
@@ -50,8 +41,6 @@
OperationalDataSubscription(const std::string& moduleName, const std::string& path, const DataSupplier& dataSupplier);
private:
- std::shared_ptr<sysrepo::Connection> m_connection;
- std::shared_ptr<sysrepo::Session> m_session;
std::shared_ptr<YangSchema> m_schema;
- std::shared_ptr<sysrepo::Subscribe> m_subscription;
+ sysrepo::Subscription m_subscription;
};
diff --git a/tests/utils.cpp b/tests/utils.cpp
index 187cdf3..c24f4de 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -7,6 +7,7 @@
*/
#include "trompeloeil_doctest.hpp"
+#include <libyang-cpp/Context.hpp>
#include "completion.hpp"
#include "leaf_data_helpers.hpp"
#include "libyang_utils.hpp"
@@ -206,12 +207,6 @@
}
}
- leaf leafRefNonPresent {
- type leafref {
- path ../stuff/name;
- }
- }
-
container users {
config false;
list userList {
@@ -238,7 +233,7 @@
"test-schema:enum": "A",
"test-schema:identityRef": "apple",
"test-schema:binary": "QUhPSgo=",
- "test-schema:empty": "",
+ "test-schema:empty": [null],
"test-schema:bits": "a AHOJ",
"test-schema:capabilities": "switch hub",
"test-schema:dec64": "43242.43260",
@@ -248,7 +243,6 @@
}
],
"test-schema:leafRefPresent": "Xaver",
- "test-schema:leafRefNonPresent": "Lucas",
"test-schema:users": {
"userList": [
{
@@ -268,9 +262,9 @@
TEST_CASE("libyang_utils")
{
- auto ctx = std::make_shared<libyang::Context>();
- ctx->parse_module_mem(schema, LYS_IN_YANG);
- auto dataNode = ctx->parse_data_mem(data, LYD_JSON, LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_NOEXTDEPS | LYD_OPT_STRICT);
+ libyang::Context ctx;
+ ctx.parseModuleMem(schema, libyang::SchemaFormat::YANG);
+ auto dataNode = ctx.parseDataMem(data, libyang::DataFormat::JSON, std::nullopt, libyang::ValidationOptions::Present);
SECTION("leafValueFromNode")
{
@@ -362,16 +356,9 @@
path = "test-schema:leafRefPresent";
expectedLeafData = std::string{"Xaver"};
}
- SECTION("test-schema:leafRefNonPresent")
- {
- path = "test-schema:leafRefNonPresent";
- expectedLeafData = std::string{"Lucas"};
- }
- auto leaf = dataNode->find_path(("/" + path).c_str());
- REQUIRE(leaf->number() == 1);
- auto firstLeaf = std::make_shared<libyang::Data_Node_Leaf_List>(leaf->data().front());
- REQUIRE(leafValueFromNode(firstLeaf) == expectedLeafData);
+ auto leaf = dataNode->findPath(("/" + path).c_str());
+ REQUIRE(leafValueFromNode(leaf->asTerm()) == expectedLeafData);
}
SECTION("lyNodesToTree")
@@ -397,7 +384,6 @@
{"/test-schema:stuff[name='Xaver']", special_{SpecialValue::List}},
{"/test-schema:stuff[name='Xaver']/name", std::string{"Xaver"}},
{"/test-schema:leafRefPresent", std::string{"Xaver"}},
- {"/test-schema:leafRefNonPresent", std::string{"Lucas"}},
{"/test-schema:users/userList[1]", special_{SpecialValue::List}},
{"/test-schema:users/userList[1]/name", std::string{"John"}},
{"/test-schema:users/userList[2]", special_{SpecialValue::List}},
@@ -407,7 +393,7 @@
};
DatastoreAccess::Tree tree;
- lyNodesToTree(tree, {dataNode->tree_for()});
+ lyNodesToTree(tree, dataNode->siblings());
REQUIRE(tree == expected);
}
}
diff --git a/tests/yang.cpp b/tests/yang.cpp
index ed4da88..205616c 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -683,7 +683,7 @@
}
SECTION("bigPizzas enabled")
{
- ys.enableFeature("example-schema", "bigPizzas");
+ ys.setEnabledFeatures("example-schema", {"bigPizzas"});
type = createEnum({"small", "medium", "large"});
}
}
@@ -727,7 +727,7 @@
node.first = "example-schema";
node.second = "activeNumber";
type.emplace<yang::LeafRef>(
- "/example-schema:_list/number",
+ "/_list/number",
std::make_unique<yang::TypeInfo>(ys.leafType("/example-schema:_list/number"))
);
}
@@ -744,22 +744,19 @@
}
SECTION("weird ports enabled")
{
- ys.enableFeature("example-schema", "weirdPortNames");
+ ys.setEnabledFeatures("example-schema", {"weirdPortNames"});
enums = createEnum({"WEIRD", "utf2", "utf3"});
}
type = yang::Union{{
yang::TypeInfo{createEnum({"wlan0", "wlan1"})},
yang::TypeInfo{yang::LeafRef{
- "/example-schema:portSettings/port",
+ "../portSettings/port",
std::make_unique<yang::TypeInfo>(createEnum({"eth0", "eth1", "eth2"}))
}},
yang::TypeInfo{yang::LeafRef{
- "/example-schema:activeMappedPort",
- std::make_unique<yang::TypeInfo>(yang::LeafRef{
- "/example-schema:portMapping/port",
- std::make_unique<yang::TypeInfo>(enums)
- })
+ "../activeMappedPort",
+ std::make_unique<yang::TypeInfo>(enums)
}},
yang::TypeInfo{yang::Empty{}},
}};
@@ -939,6 +936,7 @@
{"example-schema"s, "zero"},
{"example-schema"s, "subLeaf"}
};
+
expectedRecursive = {
{boost::none, "/example-schema:_list"},
{boost::none, "/example-schema:_list/contInList"},
@@ -966,10 +964,10 @@
{boost::none, "/example-schema:foodDrinkIdentLeaf"},
{boost::none, "/example-schema:foodDrinkIdentLeaf"},
{boost::none, "/example-schema:foodIdentLeaf"},
- {boost::none, "/example-schema:interface/caseEthernet/ethernet"},
- {boost::none, "/example-schema:interface/caseEthernet/ethernet/ip"},
- {boost::none, "/example-schema:interface/caseLoopback/loopback"},
- {boost::none, "/example-schema:interface/caseLoopback/loopback/ip"},
+ {boost::none, "/example-schema:ethernet"},
+ {boost::none, "/example-schema:ethernet/ip"},
+ {boost::none, "/example-schema:loopback"},
+ {boost::none, "/example-schema:loopback/ip"},
{boost::none, "/example-schema:interrupt"},
{boost::none, "/example-schema:leafBool"},
{boost::none, "/example-schema:leafDecimal"},
@@ -990,16 +988,10 @@
{boost::none, "/example-schema:leafUint8"},
{boost::none, "/example-schema:length"},
{boost::none, "/example-schema:myRpc"},
- {boost::none, "/example-schema:myRpc/input"},
- {boost::none, "/example-schema:myRpc/output"},
{boost::none, "/example-schema:rpcOneOutput"},
- {boost::none, "/example-schema:rpcOneOutput/input"},
- {boost::none, "/example-schema:rpcOneOutput/output"},
- {boost::none, "/example-schema:rpcOneOutput/output/ahoj"},
+ {boost::none, "/example-schema:rpcOneOutput/ahoj"},
{boost::none, "/example-schema:rpcOneInput"},
- {boost::none, "/example-schema:rpcOneInput/input"},
- {boost::none, "/example-schema:rpcOneInput/input/ahoj"},
- {boost::none, "/example-schema:rpcOneInput/output"},
+ {boost::none, "/example-schema:rpcOneInput/ahoj"},
{boost::none, "/example-schema:numberOrString"},
{boost::none, "/example-schema:obsoleteLeaf"},
{boost::none, "/example-schema:obsoleteLeafWithDeprecatedType"},
@@ -1010,10 +1002,6 @@
{boost::none, "/example-schema:portMapping/port"},
{boost::none, "/example-schema:portSettings"},
{boost::none, "/example-schema:portSettings/port"},
- {boost::none, "/example-schema:portSettings/shutdown"},
- {boost::none, "/example-schema:portSettings/shutdown/input"},
- {boost::none, "/example-schema:portSettings/shutdown/output"},
- {boost::none, "/example-schema:portSettings/shutdown/output/success"},
{boost::none, "/example-schema:systemStats"},
{boost::none, "/example-schema:systemStats/upTime"},
{boost::none, "/example-schema:subLeaf"},
@@ -1196,7 +1184,7 @@
SECTION("leafrefPath")
{
- REQUIRE(ys.leafrefPath("/example-schema:activeNumber") == "/example-schema:_list/number");
+ REQUIRE(ys.leafrefPath("/example-schema:activeNumber") == "/_list/number");
}
SECTION("isConfig")
@@ -1217,7 +1205,7 @@
SECTION("leafTypeName")
{
REQUIRE(ys.leafTypeName("/example-schema:leafEnumTypedefRestricted") == "enumTypedef");
- REQUIRE(ys.leafTypeName("/example-schema:leafInt32") == std::nullopt);
+ REQUIRE(ys.leafTypeName("/example-schema:leafInt32") == "int32");
}
SECTION("dataPathToSchemaPath")
@@ -1334,14 +1322,14 @@
REQUIRE_THROWS(ys.nodeType(path, node));
}
- SECTION("enableFeature - non existing module")
+ SECTION("setEnabledFeatures - non existing module")
{
- REQUIRE_THROWS_AS(ys.enableFeature("non-existing", "just-no"), std::runtime_error);
+ REQUIRE_THROWS_AS(ys.setEnabledFeatures("non-existing", {"just-no"}), std::runtime_error);
}
- SECTION("enableFeature - non existing feature")
+ SECTION("setEnabledFeatures - non existing feature")
{
- REQUIRE_THROWS_AS(ys.enableFeature("example-schema", "just-no"), std::runtime_error);
+ REQUIRE_THROWS_AS(ys.setEnabledFeatures("example-schema", {"just-no"}), std::runtime_error);
}
}
}