Bump sysrepo,lignetconf2, libyang and netopeer2

This update got pretty messy, since libyang had some
breaking changes. And unfortunatly rousette, libyang-cpp
and sysrepo-cpp has not made any new release yet.
This commit is contained in:
Mattias Walström
2025-12-16 18:30:03 +01:00
parent e69d53a7b5
commit 542fd4006a
79 changed files with 8340 additions and 187 deletions
@@ -1,7 +1,7 @@
From 22b41e7ed8268b94ccc139138e2037c390a3a616 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
Date: Thu, 10 Jul 2025 15:00:45 +0200
Subject: [PATCH] Implement `SchemaNode::actionRpcs()`
Subject: [PATCH 1/7] Implement `SchemaNode::actionRpcs()`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
@@ -0,0 +1,38 @@
From 1f8e502cf0308faa0336b1077308184de6a83ee0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Mon, 20 Oct 2025 11:45:06 +0200
Subject: [PATCH 2/7] CI: pin to libyang v3
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Change-Id: Idba085ad9c9c137874801f1d4089f8ee6bb390c7
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
.zuul.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.zuul.yaml b/.zuul.yaml
index 7b92766..fd89549 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -4,13 +4,13 @@
- f38-gcc-cover:
required-projects:
- name: github/CESNET/libyang
- override-checkout: devel
+ override-checkout: cesnet/2025-08-07
- name: github/doctest/doctest
override-checkout: v2.3.6
- f38-clang-asan-ubsan:
required-projects: &projects
- name: github/CESNET/libyang
- override-checkout: devel
+ override-checkout: cesnet/2025-08-07
- name: github/doctest/doctest
override-checkout: v2.4.11
- f38-clang-tsan:
--
2.43.0
@@ -0,0 +1,216 @@
From b27a65d5d5f6ffffbd2c26c1b5bea57a6de584d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
Date: Mon, 20 Oct 2025 18:46:38 +0200
Subject: [PATCH 3/7] wrap lyd_validate_op
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
This patch wraps lyd_validate_op from libyang. Downstream users can now
validate operations (RPC input, replies, and notifications).
Change-Id: Ib03070bbc3e1d0dccd6bf38eda82e944db1093b1
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/libyang-cpp/DataNode.hpp | 2 +
src/DataNode.cpp | 24 +++++++++++
tests/context.cpp | 2 +-
tests/data_node.cpp | 72 ++++++++++++++++++++++++++++++++
tests/example_schema.hpp | 22 ++++++++++
5 files changed, 121 insertions(+), 1 deletion(-)
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
index 50d6c0e..e202a96 100644
--- a/include/libyang-cpp/DataNode.hpp
+++ b/include/libyang-cpp/DataNode.hpp
@@ -59,6 +59,7 @@ template <typename Operation, typename Siblings>
void handleLyTreeOperation(DataNode* affectedNode, Operation operation, Siblings siblings, std::shared_ptr<internal_refcount> newRefs);
LIBYANG_CPP_EXPORT void validateAll(std::optional<libyang::DataNode>& node, const std::optional<ValidationOptions>& opts = std::nullopt);
+LIBYANG_CPP_EXPORT void validateOp(libyang::DataNode& input, const std::optional<libyang::DataNode>& opsTree, OperationType opType);
LIBYANG_CPP_EXPORT Set<DataNode> findXPathAt(
const std::optional<libyang::DataNode>& contextNode,
@@ -147,6 +148,7 @@ public:
friend LIBYANG_CPP_EXPORT lyd_node* getRawNode(DataNode node);
friend LIBYANG_CPP_EXPORT void validateAll(std::optional<libyang::DataNode>& node, const std::optional<ValidationOptions>& opts);
+ friend LIBYANG_CPP_EXPORT void validateOp(libyang::DataNode& input, const std::optional<libyang::DataNode>& opsTree, OperationType opType);
friend LIBYANG_CPP_EXPORT Set<DataNode> findXPathAt(const std::optional<libyang::DataNode>& contextNode, const libyang::DataNode& forest, const std::string& xpath);
bool operator==(const DataNode& node) const;
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
index 7e87917..e28ef30 100644
--- a/src/DataNode.cpp
+++ b/src/DataNode.cpp
@@ -1226,6 +1226,30 @@ void validateAll(std::optional<libyang::DataNode>& node, const std::optional<Val
}
}
+/** @brief Validate op after parsing with lyd_parse_op.
+ *
+ * Wraps `lyd_validate_op`.
+ *
+ * @param input The tree with the op to validate.
+ * @param opsTree The optional data tree to validate the input against.
+ * @param opType The operation type. Contrary to `lyd_validate_op`, we accept not only YANG but also NETCONF and RESTCONF operation types (and internally convert them to YANG).
+ */
+void validateOp(libyang::DataNode& input, const std::optional<libyang::DataNode>& opsTree, OperationType opType)
+{
+ if (opType == OperationType::RpcYang || opType == OperationType::RpcRestconf || opType == OperationType::RpcNetconf) {
+ opType = OperationType::RpcYang;
+ } else if (opType == OperationType::ReplyYang || opType == OperationType::ReplyRestconf || opType == OperationType::ReplyNetconf) {
+ opType = OperationType::ReplyYang;
+ } else if (opType == OperationType::NotificationYang || opType == OperationType::NotificationRestconf || opType == OperationType::NotificationNetconf) {
+ opType = OperationType::NotificationYang;
+ } else {
+ throw Error("validateOp: DataYang datatype is not supported");
+ }
+
+ auto ret = lyd_validate_op(input.m_node, opsTree ? opsTree->m_node : nullptr, utils::toOpType(opType), nullptr);
+ throwIfError(ret, "libyang:validateOp: lyd_validate_op failed");
+}
+
/** @short Find instances matching the provided XPath
*
* @param contextNode The node which serves as the "context node" for XPath evaluation. Use nullopt to start at root.
diff --git a/tests/context.cpp b/tests/context.cpp
index 6c5dde8..c0b7e09 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -154,7 +154,7 @@ TEST_CASE("context")
{
auto mod = ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
auto rpcs = mod.actionRpcs();
- REQUIRE(rpcs.size() == 1);
+ REQUIRE(rpcs.size() == 2);
REQUIRE(rpcs[0].module().name() == "example-schema");
REQUIRE(rpcs[0].name() == "myRpc");
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
index db5a28e..9215b12 100644
--- a/tests/data_node.cpp
+++ b/tests/data_node.cpp
@@ -2430,6 +2430,78 @@ TEST_CASE("Data Node manipulation")
"Can't parse into operation data tree: LY_EVALID", libyang::Error);
}
}
+
+ DOCTEST_SUBCASE("Validation")
+ {
+ DOCTEST_SUBCASE("Valid input")
+ {
+ std::string rpcInput;
+ std::string rpcPath;
+ std::string expected;
+ std::optional<libyang::DataNode> depTree;
+
+ DOCTEST_SUBCASE("RPC")
+ {
+ rpcInput = R"({"example-schema:input": { "number": 42 } })";
+ rpcPath = "/example-schema:rpc-with-choice";
+ expected = R"({
+ "example-schema:rpc-with-choice": {
+ "number": 42
+ }
+}
+)";
+ }
+
+ DOCTEST_SUBCASE("Action")
+ {
+ rpcInput = R"({ "example-schema:input": { "friend": "Kuba" } })";
+ rpcPath = "/example-schema:person[name='Franta']/poke-a-friend";
+ expected = R"({
+ "example-schema:poke-a-friend": {
+ "friend": "Kuba"
+ }
+}
+)";
+ depTree = ctx.newPath("/example-schema:person[name='Kuba']");
+ }
+
+ auto [parent, rpcTree] = ctx.newPath2(rpcPath);
+ auto rpcOp = rpcTree->parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf);
+
+ REQUIRE(!rpcOp.op);
+ REQUIRE(rpcOp.tree);
+
+ libyang::validateOp(*rpcTree, depTree, libyang::OperationType::RpcRestconf);
+ REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == expected);
+ }
+
+ DOCTEST_SUBCASE("Nodes in disjunctive cases defined together")
+ {
+ auto rpcInput = R"({ "example-schema:input": { "number": 42, "text": "The ultimate answer" } })";
+
+ auto rpcTree = ctx.newPath("/example-schema:rpc-with-choice");
+ auto rpcOp = rpcTree.parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf);
+
+ REQUIRE(!rpcOp.op);
+ REQUIRE(rpcOp.tree);
+
+ REQUIRE_THROWS_WITH_AS(libyang::validateOp(rpcTree, std::nullopt, libyang::OperationType::RpcRestconf), "libyang:validateOp: lyd_validate_op failed: LY_EVALID", libyang::Error);
+ }
+
+ DOCTEST_SUBCASE("Action without the leafref node")
+ {
+ auto rpcInput = R"({ "example-schema:input": { "friend": "Kuba" } })";
+ auto rpcPath = "/example-schema:person[name='Franta']/poke-a-friend";
+
+ auto [parent, rpcTree] = ctx.newPath2(rpcPath);
+ auto rpcOp = rpcTree->parseOp(rpcInput, dataTypeFor(rpcInput), libyang::OperationType::RpcRestconf);
+
+ REQUIRE(!rpcOp.op);
+ REQUIRE(rpcOp.tree);
+
+ REQUIRE_THROWS_WITH_AS(libyang::validateOp(*rpcTree, std::nullopt, libyang::OperationType::RpcRestconf), "libyang:validateOp: lyd_validate_op failed: LY_EVALID", libyang::Error);
+ }
+ }
}
DOCTEST_SUBCASE("comparing") {
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
index 02d3d74..acc6ecc 100644
--- a/tests/example_schema.hpp
+++ b/tests/example_schema.hpp
@@ -108,6 +108,16 @@ module example-schema {
}
action poke { }
+
+ action poke-a-friend {
+ input {
+ leaf friend {
+ type leafref {
+ path '../../../person/name';
+ }
+ }
+ }
+ }
}
leaf bossPerson {
@@ -284,6 +294,18 @@ module example-schema {
}
}
}
+ rpc rpc-with-choice {
+ input {
+ choice the-impossible-choice {
+ leaf text {
+ type string;
+ }
+ leaf number {
+ type int32;
+ }
+ }
+ }
+ }
anydata myData {
}
--
2.43.0
@@ -0,0 +1,435 @@
From 4c00c24dee6bbc694d62e7174b7ac14f8aa95f14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Thu, 30 Oct 2025 21:16:13 +0100
Subject: [PATCH 4/7] port to libyang v4
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Apart from the usual API/ABI changes, the default values of a `leaf` or
a `leaf-list` are no longer canonicalized.
This used to be missing from libyang v4, but it was added recently.
Sill, chances are that the form which was used in the YANG model's
source code is actually *the* intended presentation, so let's skip
canonicalization altogether.
Upstream has removed some flags and defines, so let's sync their list
with whatever is currently available in the `devel` branch.
Change-Id: I52dcec88eeee003b45dd6cf400f4d6875abbcc05
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
.zuul.yaml | 4 ++--
CMakeLists.txt | 4 ++--
README.md | 2 +-
include/libyang-cpp/Context.hpp | 6 +++++-
include/libyang-cpp/DataNode.hpp | 5 ++++-
include/libyang-cpp/Enum.hpp | 15 +++++++++++++--
src/Context.cpp | 12 ++++++++++--
src/DataNode.cpp | 11 +++++++++--
src/SchemaNode.cpp | 6 +++---
src/Type.cpp | 2 +-
src/utils/enum.hpp | 21 +++++++++++++++++++--
tests/context.cpp | 32 +++++++++++++++++---------------
tests/data_node.cpp | 2 +-
tests/schema_node.cpp | 4 ++--
14 files changed, 89 insertions(+), 37 deletions(-)
diff --git a/.zuul.yaml b/.zuul.yaml
index fd89549..7b92766 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -4,13 +4,13 @@
- f38-gcc-cover:
required-projects:
- name: github/CESNET/libyang
- override-checkout: cesnet/2025-08-07
+ override-checkout: devel
- name: github/doctest/doctest
override-checkout: v2.3.6
- f38-clang-asan-ubsan:
required-projects: &projects
- name: github/CESNET/libyang
- override-checkout: cesnet/2025-08-07
+ override-checkout: devel
- name: github/doctest/doctest
override-checkout: v2.4.11
- f38-clang-tsan:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c6db320..90f9aaa 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,7 +20,7 @@ add_custom_target(libyang-cpp-version-cmake
cmake/ProjectGitVersionRunner.cmake
)
include(cmake/ProjectGitVersion.cmake)
-set(LIBYANG_CPP_PKG_VERSION "4")
+set(LIBYANG_CPP_PKG_VERSION "5")
prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
find_package(Doxygen)
@@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
find_package(PkgConfig REQUIRED)
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET)
+pkg_check_modules(LIBYANG REQUIRED libyang>=4.1.0 IMPORTED_TARGET)
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
find_package(date)
diff --git a/README.md b/README.md
index 8291e00..0cef2ff 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
Object lifetimes are managed automatically via RAII.
## Dependencies
-- [libyang v3](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
+- [libyang v4](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
- CMake 3.19+
- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
diff --git a/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp
index ca89063..e695914 100644
--- a/include/libyang-cpp/Context.hpp
+++ b/include/libyang-cpp/Context.hpp
@@ -108,7 +108,11 @@ public:
std::optional<SubmoduleParsed> getSubmodule(const std::string& name, const std::optional<std::string>& revision) const;
void registerModuleCallback(std::function<ModuleCallback> callback);
- ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const;
+ ParsedOp parseOp(
+ const std::string& input,
+ const DataFormat format,
+ const OperationType opType,
+ const std::optional<ParseOptions> parseOpts = std::nullopt) const;
DataNode newPath(const std::string& path, const std::optional<std::string>& value = std::nullopt, const std::optional<CreationOptions> options = std::nullopt) const;
CreatedNodes newPath2(const std::string& path, const std::optional<std::string>& value = std::nullopt, const std::optional<CreationOptions> options = std::nullopt) const;
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
index e202a96..35e5936 100644
--- a/include/libyang-cpp/DataNode.hpp
+++ b/include/libyang-cpp/DataNode.hpp
@@ -123,7 +123,10 @@ public:
Collection<DataNode, IterationType::Sibling> siblings() const;
Collection<DataNode, IterationType::Sibling> immediateChildren() const;
- ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const;
+ ParsedOp parseOp(const std::string& input,
+ const DataFormat format,
+ const OperationType opType,
+ const std::optional<ParseOptions> parseOpts = std::nullopt) const;
void parseSubtree(
const std::string& data,
diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp
index 85dcc83..32142f9 100644
--- a/include/libyang-cpp/Enum.hpp
+++ b/include/libyang-cpp/Enum.hpp
@@ -136,6 +136,7 @@ enum class DuplicationOptions : uint32_t {
WithFlags = 0x08,
NoExt = 0x10,
WithPriv = 0x20,
+ NoLyds = 0x40,
};
enum class NodeType : uint16_t {
@@ -167,6 +168,13 @@ enum class ContextOptions : uint16_t {
PreferSearchDirs = 0x20,
SetPrivParsed = 0x40,
ExplicitCompile = 0x80,
+ EnableImpFeatures = 0x100,
+ CompileObsolete = 0x200,
+ LybHashes = 0x400,
+ LeafrefExtended = 0x800,
+ LeafrefLinking = 0x1000,
+ BuiltinPluginsOnly = 0x2000,
+ StaticPluginsOnly = 0x4000,
};
/**
@@ -203,7 +211,6 @@ enum class AnydataValueType : uint32_t {
String,
XML,
JSON,
- LYB
};
/**
@@ -252,6 +259,7 @@ enum class ValidationOptions {
MultiError = 0x0004,
Operational = 0x0008,
NoDefaults = 0x0010,
+ NotFinal = 0x0020,
};
/**
@@ -262,11 +270,14 @@ enum class ParseOptions {
Strict = 0x020000,
Opaque = 0x040000,
NoState = 0x080000,
- LybModUpdate = 0x100000,
+ LybSkipCtxCheck = 0x100000,
Ordered = 0x200000,
Subtree = 0x400000, /**< Do not use this one for parsing of data subtrees */
WhenTrue = 0x800000,
NoNew = 0x1000000,
+ StoreOnly = 0x2010000,
+ JsonNull = 0x4000000,
+ JsonStringDataTypes = 0x8000000,
};
/**
diff --git a/src/Context.cpp b/src/Context.cpp
index fec2f27..f5bdc71 100644
--- a/src/Context.cpp
+++ b/src/Context.cpp
@@ -239,7 +239,7 @@ std::optional<DataNode> Context::parseExtData(
* Note: to parse a NETCONF RPC reply, you MUST parse the original NETCONF RPC request (that is, you have to use
* this method with OperationType::RpcNetconf).
*/
-ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const
+ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional<ParseOptions> parseOpts) const
{
auto in = wrap_ly_in_new_memory(input);
@@ -251,7 +251,15 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con
case OperationType::NotificationYang: {
lyd_node* op = nullptr;
lyd_node* tree = nullptr;
- auto err = lyd_parse_op(m_ctx.get(), nullptr, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, &op);
+ auto err = lyd_parse_op(
+ m_ctx.get(),
+ nullptr,
+ in.get(),
+ utils::toLydFormat(format),
+ utils::toOpType(opType),
+ parseOpts ? utils::toParseOptions(*parseOpts) : 0,
+ &tree,
+ &op);
ParsedOp res;
res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt;
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
index e28ef30..edabf9b 100644
--- a/src/DataNode.cpp
+++ b/src/DataNode.cpp
@@ -391,7 +391,7 @@ DataNodeAny DataNode::asAny() const
*
* Wraps `lyd_parse_op`.
*/
-ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const
+ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional<ParseOptions> parseOpts) const
{
auto in = wrap_ly_in_new_memory(input);
@@ -401,7 +401,14 @@ ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, co
case OperationType::ReplyRestconf: {
lyd_node* op = nullptr;
lyd_node* tree = nullptr;
- auto err = lyd_parse_op(m_node->schema->module->ctx, m_node, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, nullptr);
+ auto err = lyd_parse_op(m_node->schema->module->ctx,
+ m_node,
+ in.get(),
+ utils::toLydFormat(format),
+ utils::toOpType(opType),
+ parseOpts ? utils::toParseOptions(*parseOpts) : 0,
+ &tree,
+ nullptr);
ParsedOp res{
.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt,
.op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
index ce24203..2e0351a 100644
--- a/src/SchemaNode.cpp
+++ b/src/SchemaNode.cpp
@@ -564,7 +564,7 @@ std::vector<std::string> LeafList::defaultValuesStr() const
auto dflts = reinterpret_cast<const lysc_node_leaflist*>(m_node)->dflts;
std::vector<std::string> res;
for (const auto& it : std::span(dflts, LY_ARRAY_COUNT(dflts))) {
- res.emplace_back(lyd_value_get_canonical(m_ctx.get(), it));
+ res.emplace_back(it.str);
}
return res;
}
@@ -660,8 +660,8 @@ bool LeafList::isUserOrdered() const
std::optional<std::string> Leaf::defaultValueStr() const
{
auto dflt = reinterpret_cast<const lysc_node_leaf*>(m_node)->dflt;
- if (dflt) {
- return lyd_value_get_canonical(m_ctx.get(), dflt);
+ if (dflt.str) {
+ return std::string{dflt.str};
} else {
return std::nullopt;
}
diff --git a/src/Type.cpp b/src/Type.cpp
index 01b2c4f..4d2db46 100644
--- a/src/Type.cpp
+++ b/src/Type.cpp
@@ -240,7 +240,7 @@ std::optional<std::string> Type::description() const
*/
std::string Type::internalPluginId() const
{
- return m_type->plugin->id;
+ return lysc_get_type_plugin(m_type->plugin_ref)->id;
}
/**
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
index 5e02d24..0b2e2d7 100644
--- a/src/utils/enum.hpp
+++ b/src/utils/enum.hpp
@@ -91,6 +91,7 @@ static_assert(LYD_DUP_WITH_FLAGS == toDuplicationOptions(DuplicationOptions::Wit
static_assert(LYD_DUP_WITH_PARENTS == toDuplicationOptions(DuplicationOptions::WithParents));
static_assert(LYD_DUP_NO_EXT == toDuplicationOptions(DuplicationOptions::NoExt));
static_assert(LYD_DUP_WITH_PRIV == toDuplicationOptions(DuplicationOptions::WithPriv));
+static_assert(LYD_DUP_NO_LYDS == toDuplicationOptions(DuplicationOptions::NoLyds));
static_assert((LYD_DUP_NO_META | LYD_DUP_NO_EXT) ==
toDuplicationOptions(DuplicationOptions::NoMeta | DuplicationOptions::NoExt));
@@ -131,6 +132,13 @@ static_assert(toContextOptions(ContextOptions::DisableSearchCwd) == LY_CTX_DISAB
static_assert(toContextOptions(ContextOptions::PreferSearchDirs) == LY_CTX_PREFER_SEARCHDIRS);
static_assert(toContextOptions(ContextOptions::SetPrivParsed) == LY_CTX_SET_PRIV_PARSED);
static_assert(toContextOptions(ContextOptions::ExplicitCompile) == LY_CTX_EXPLICIT_COMPILE);
+static_assert(toContextOptions(ContextOptions::EnableImpFeatures) == LY_CTX_ENABLE_IMP_FEATURES);
+static_assert(toContextOptions(ContextOptions::CompileObsolete) == LY_CTX_COMPILE_OBSOLETE);
+static_assert(toContextOptions(ContextOptions::LybHashes) == LY_CTX_LYB_HASHES);
+static_assert(toContextOptions(ContextOptions::LeafrefExtended) == LY_CTX_LEAFREF_EXTENDED);
+static_assert(toContextOptions(ContextOptions::LeafrefLinking) == LY_CTX_LEAFREF_LINKING);
+static_assert(toContextOptions(ContextOptions::BuiltinPluginsOnly) == LY_CTX_BUILTIN_PLUGINS_ONLY);
+static_assert(toContextOptions(ContextOptions::StaticPluginsOnly) == LY_CTX_STATIC_PLUGINS_ONLY);
constexpr uint16_t toLogOptions(const LogOptions options)
{
@@ -198,6 +206,10 @@ constexpr uint32_t toValidationOptions(const ValidationOptions opts)
static_assert(toValidationOptions(ValidationOptions::NoState) == LYD_VALIDATE_NO_STATE);
static_assert(toValidationOptions(ValidationOptions::Present) == LYD_VALIDATE_PRESENT);
+static_assert(toValidationOptions(ValidationOptions::MultiError) == LYD_VALIDATE_MULTI_ERROR);
+static_assert(toValidationOptions(ValidationOptions::Operational) == LYD_VALIDATE_OPERATIONAL);
+static_assert(toValidationOptions(ValidationOptions::NoDefaults) == LYD_VALIDATE_NO_DEFAULTS);
+static_assert(toValidationOptions(ValidationOptions::NotFinal) == LYD_VALIDATE_NOT_FINAL);
constexpr uint32_t toParseOptions(const ParseOptions opts)
{
@@ -208,8 +220,14 @@ static_assert(toParseOptions(ParseOptions::ParseOnly) == LYD_PARSE_ONLY);
static_assert(toParseOptions(ParseOptions::Strict) == LYD_PARSE_STRICT);
static_assert(toParseOptions(ParseOptions::Opaque) == LYD_PARSE_OPAQ);
static_assert(toParseOptions(ParseOptions::NoState) == LYD_PARSE_NO_STATE);
-static_assert(toParseOptions(ParseOptions::LybModUpdate) == LYD_PARSE_LYB_MOD_UPDATE);
+static_assert(toParseOptions(ParseOptions::LybSkipCtxCheck) == LYD_PARSE_LYB_SKIP_CTX_CHECK);
static_assert(toParseOptions(ParseOptions::Ordered) == LYD_PARSE_ORDERED);
+static_assert(toParseOptions(ParseOptions::Subtree) == LYD_PARSE_SUBTREE);
+static_assert(toParseOptions(ParseOptions::WhenTrue) == LYD_PARSE_WHEN_TRUE);
+static_assert(toParseOptions(ParseOptions::NoNew) == LYD_PARSE_NO_NEW);
+static_assert(toParseOptions(ParseOptions::StoreOnly) == LYD_PARSE_STORE_ONLY);
+static_assert(toParseOptions(ParseOptions::JsonNull) == LYD_PARSE_JSON_NULL);
+static_assert(toParseOptions(ParseOptions::JsonStringDataTypes) == LYD_PARSE_JSON_STRING_DATATYPES);
constexpr lyd_type toOpType(const OperationType type)
{
@@ -242,7 +260,6 @@ static_assert(toAnydataValueType(AnydataValueType::DataTree) == LYD_ANYDATA_DATA
static_assert(toAnydataValueType(AnydataValueType::String) == LYD_ANYDATA_STRING);
static_assert(toAnydataValueType(AnydataValueType::XML) == LYD_ANYDATA_XML);
static_assert(toAnydataValueType(AnydataValueType::JSON) == LYD_ANYDATA_JSON);
-static_assert(toAnydataValueType(AnydataValueType::LYB) == LYD_ANYDATA_LYB);
constexpr LYS_OUTFORMAT toLysOutFormat(const SchemaOutputFormat format)
{
diff --git a/tests/context.cpp b/tests/context.cpp
index c0b7e09..eaedebf 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -359,25 +359,27 @@ TEST_CASE("context")
ctx->loadModule("mod1", std::nullopt, {});
ctx->parseModule(valid_yang_model, libyang::SchemaFormat::YANG);
auto modules = ctx->modules();
- REQUIRE(modules.size() == 8);
+ REQUIRE(modules.size() == 9);
REQUIRE(modules.at(0).name() == "ietf-yang-metadata");
REQUIRE(modules.at(0).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-metadata");
REQUIRE(modules.at(1).name() == "yang");
REQUIRE(modules.at(1).ns() == "urn:ietf:params:xml:ns:yang:1");
- REQUIRE(modules.at(2).name() == "ietf-inet-types");
- REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types");
- REQUIRE(modules.at(3).name() == "ietf-yang-types");
- REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types");
- REQUIRE(modules.at(4).name() == "ietf-yang-schema-mount");
- REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount");
- REQUIRE(modules.at(5).name() == "ietf-yang-structure-ext");
- REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext");
- REQUIRE(modules.at(6).name() == "mod1");
- REQUIRE(modules.at(6).ns() == "http://example.com");
- REQUIRE(*modules.at(6).revision() == "2021-11-15");
- REQUIRE(modules.at(7).name() == "test");
+ REQUIRE(modules.at(2).name() == "default");
+ REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:netconf:default:1.0");
+ REQUIRE(modules.at(3).name() == "ietf-inet-types");
+ REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types");
+ REQUIRE(modules.at(4).name() == "ietf-yang-types");
+ REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types");
+ REQUIRE(modules.at(5).name() == "ietf-yang-schema-mount");
+ REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount");
+ REQUIRE(modules.at(6).name() == "ietf-yang-structure-ext");
+ REQUIRE(modules.at(6).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext");
+ REQUIRE(modules.at(7).name() == "mod1");
REQUIRE(modules.at(7).ns() == "http://example.com");
- REQUIRE(modules.at(7).revision() == std::nullopt);
+ REQUIRE(*modules.at(7).revision() == "2021-11-15");
+ REQUIRE(modules.at(8).name() == "test");
+ REQUIRE(modules.at(8).ns() == "http://example.com");
+ REQUIRE(modules.at(8).revision() == std::nullopt);
}
DOCTEST_SUBCASE("Module comparison")
@@ -704,7 +706,7 @@ TEST_CASE("context")
DOCTEST_SUBCASE("schema printing")
{
- std::optional<libyang::Context> ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed};
+ std::optional<libyang::Context> ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::CompileObsolete};
auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG);
REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
index 9215b12..88ca5b9 100644
--- a/tests/data_node.cpp
+++ b/tests/data_node.cpp
@@ -2426,7 +2426,7 @@ TEST_CASE("Data Node manipulation")
"WTF": "foo bar baz"
}
}
- )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf),
+ )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf, libyang::ParseOptions::Strict),
"Can't parse into operation data tree: LY_EVALID", libyang::Error);
}
}
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
index 65ef462..58152a4 100644
--- a/tests/schema_node.cpp
+++ b/tests/schema_node.cpp
@@ -19,7 +19,7 @@ using namespace std::string_literals;
TEST_CASE("SchemaNode")
{
std::optional<libyang::Context> ctx{std::in_place, std::nullopt,
- libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd};
+ libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::CompileObsolete};
std::optional<libyang::Context> ctxWithParsed{std::in_place, std::nullopt,
libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd};
ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
@@ -841,7 +841,7 @@ TEST_CASE("SchemaNode")
DOCTEST_SUBCASE("LeafList::defaultValuesStr")
{
- REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector<std::string>{"-1", "512", "1024", "2048"});
+ REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector<std::string>{"-1", "+512", "0x400", "04000"});
REQUIRE(ctx->findPath("/type_module:leafListBasic").asLeafList().defaultValuesStr().size() == 0);
}
--
2.43.0
@@ -0,0 +1,77 @@
From 51d7457bd281e34bac92ff8231978766c7784657 Mon Sep 17 00:00:00 2001
From: Martin Bohal <mb@mbohal.cz>
Date: Tue, 4 Nov 2025 14:26:05 +0100
Subject: [PATCH 5/7] Add SchemaNode::isOutput() method
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
This commit introduces the `isOutput()` method to the `SchemaNode`
class, which checks whether the schema node is part of an output
statement subtree.
It complements an already existing `isInput()` method.
Change-Id: I8fe75f2e607c0159d8295d685a59d301155ca92e
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/libyang-cpp/SchemaNode.hpp | 1 +
src/SchemaNode.cpp | 10 ++++++++++
tests/schema_node.cpp | 7 +++++++
3 files changed, 18 insertions(+)
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
index 9f49fd7..db5a63e 100644
--- a/include/libyang-cpp/SchemaNode.hpp
+++ b/include/libyang-cpp/SchemaNode.hpp
@@ -58,6 +58,7 @@ public:
Status status() const;
Config config() const;
bool isInput() const;
+ bool isOutput() const;
NodeType nodeType() const;
// It is possible to cast SchemaNode to another type via the following methods. The types are children classes of
// SchemaNode. No problems with slicing can occur, because these types are value-based and aren't constructible
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
index 2e0351a..9241b23 100644
--- a/src/SchemaNode.cpp
+++ b/src/SchemaNode.cpp
@@ -193,6 +193,16 @@ bool SchemaNode::isInput() const
return m_node->flags & LYS_INPUT;
}
+/**
+ * @brief Checks whether this node is inside a subtree of an output statement.
+ *
+ * Wraps `LYS_OUTPUT`.
+ */
+bool SchemaNode::isOutput() const
+{
+ return m_node->flags & LYS_OUTPUT;
+}
+
/**
* Returns the node type of this node (e.g. leaf, container...).
*
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
index 58152a4..01c2f19 100644
--- a/tests/schema_node.cpp
+++ b/tests/schema_node.cpp
@@ -135,6 +135,13 @@ TEST_CASE("SchemaNode")
REQUIRE(!ctx->findPath("/type_module:leafString").isInput());
}
+ DOCTEST_SUBCASE("SchemaNode::isOutput")
+ {
+ REQUIRE(ctx->findPath("/example-schema:myRpc/outputLeaf", libyang::InputOutputNodes::Output).isOutput());
+ REQUIRE(!ctx->findPath("/example-schema:myRpc/inputLeaf").isOutput());
+ REQUIRE(!ctx->findPath("/type_module:leafString").isOutput());
+ }
+
DOCTEST_SUBCASE("SchemaNode::module")
{
REQUIRE(ctx->findPath("/type_module:leafString").module().name() == "type_module");
--
2.43.0
@@ -0,0 +1,364 @@
From 38d5c10009df602594e1b9a53bac41111a9ba430 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Thu, 20 Nov 2025 15:32:03 +0100
Subject: [PATCH 6/7] adapt to changes in libyang v4.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
I was *so* tempted to leave the C++ enums as-is, just to save us from
this needless sed exercise, but hey, let's not start confusing any
possible downstream consumers now that upstream has changed these
identifiers.
Change-Id: I9b34585822b41be0ca1578051f1c0eb8588a0a51
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
CMakeLists.txt | 4 +--
README.md | 2 +-
include/libyang-cpp/Enum.hpp | 4 +--
src/utils/enum.hpp | 4 +--
tests/context.cpp | 10 +++----
tests/data_node.cpp | 58 ++++++++++++++++++------------------
6 files changed, 41 insertions(+), 41 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 90f9aaa..cbaf82a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,7 +20,7 @@ add_custom_target(libyang-cpp-version-cmake
cmake/ProjectGitVersionRunner.cmake
)
include(cmake/ProjectGitVersion.cmake)
-set(LIBYANG_CPP_PKG_VERSION "5")
+set(LIBYANG_CPP_PKG_VERSION "6")
prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
find_package(Doxygen)
@@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
find_package(PkgConfig REQUIRED)
-pkg_check_modules(LIBYANG REQUIRED libyang>=4.1.0 IMPORTED_TARGET)
+pkg_check_modules(LIBYANG REQUIRED libyang>=4.2.1 IMPORTED_TARGET)
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
find_package(date)
diff --git a/README.md b/README.md
index 0cef2ff..842573f 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
Object lifetimes are managed automatically via RAII.
## Dependencies
-- [libyang v4](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
+- [libyang v4.2+](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
- CMake 3.19+
- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp
index 32142f9..096e030 100644
--- a/include/libyang-cpp/Enum.hpp
+++ b/include/libyang-cpp/Enum.hpp
@@ -67,9 +67,9 @@ enum class OperationType : uint32_t {
*/
enum class PrintFlags : uint32_t {
WithDefaultsExplicit = 0x00,
- WithSiblings = 0x01,
+ Siblings = 0x01,
Shrink = 0x02,
- KeepEmptyCont = 0x04,
+ EmptyContainers = 0x04,
WithDefaultsTrim = 0x10,
WithDefaultsAll = 0x20,
WithDefaultsAllTag = 0x40,
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
index 0b2e2d7..a8902ff 100644
--- a/src/utils/enum.hpp
+++ b/src/utils/enum.hpp
@@ -33,7 +33,7 @@ constexpr uint32_t toPrintFlags(const PrintFlags flags)
}
// These tests ensure that I used the right numbers when defining my enum.
// TODO: add asserts for operator|(PrintFlags, PrintFlags)
-static_assert(LYD_PRINT_KEEPEMPTYCONT == toPrintFlags(PrintFlags::KeepEmptyCont));
+static_assert(LYD_PRINT_EMPTY_CONT == toPrintFlags(PrintFlags::EmptyContainers));
static_assert(LYD_PRINT_SHRINK == toPrintFlags(PrintFlags::Shrink));
static_assert(LYD_PRINT_WD_ALL == toPrintFlags(PrintFlags::WithDefaultsAll));
static_assert(LYD_PRINT_WD_ALL_TAG == toPrintFlags(PrintFlags::WithDefaultsAllTag));
@@ -41,7 +41,7 @@ static_assert(LYD_PRINT_WD_EXPLICIT == toPrintFlags(PrintFlags::WithDefaultsExpl
static_assert(LYD_PRINT_WD_IMPL_TAG == toPrintFlags(PrintFlags::WithDefaultsImplicitTag));
static_assert(LYD_PRINT_WD_MASK == toPrintFlags(PrintFlags::WithDefaultsMask));
static_assert(LYD_PRINT_WD_TRIM == toPrintFlags(PrintFlags::WithDefaultsTrim));
-static_assert(LYD_PRINT_WITHSIBLINGS == toPrintFlags(PrintFlags::WithSiblings));
+static_assert(LYD_PRINT_SIBLINGS == toPrintFlags(PrintFlags::Siblings));
#ifndef _MSC_VER
// MSVC doesn't respect the underlying enum size
diff --git a/tests/context.cpp b/tests/context.cpp
index eaedebf..355860b 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -502,7 +502,7 @@ TEST_CASE("context")
auto errorsNode = node->findXPath("/ietf-restconf:errors");
REQUIRE(errorsNode.size() == 1);
REQUIRE(errorsNode.begin()->path() == "/ietf-restconf:errors");
- REQUIRE(*errorsNode.begin()->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*errorsNode.begin()->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
"ietf-restconf:errors": {
"error": [
{
@@ -589,7 +589,7 @@ TEST_CASE("context")
auto firstValue = edits.begin()->findPath("value");
REQUIRE(firstValue);
- REQUIRE(*firstValue->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*firstValue->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
"ietf-yang-patch:value": {
"example-schema:person": {
"name": "John"
@@ -597,7 +597,7 @@ TEST_CASE("context")
}
}
)");
- REQUIRE(*firstValue->printStr(libyang::DataFormat::XML, libyang::PrintFlags::KeepEmptyCont) == R"(<value xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
+ REQUIRE(*firstValue->printStr(libyang::DataFormat::XML, libyang::PrintFlags::EmptyContainers) == R"(<value xmlns="urn:ietf:params:xml:ns:yang:ietf-yang-patch">
<person xmlns="http://example.com/coze">
<name>John</name>
</person>
@@ -607,8 +607,8 @@ TEST_CASE("context")
auto secondValueNode = (edits.begin() + 1)->findPath("value");
REQUIRE(secondValueNode);
auto secondValue = std::get<libyang::DataNode>(secondValueNode->asAny().releaseValue().value());
- REQUIRE(*secondValue.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == "{\n \"example-schema:dummy\": \"I am a dummy\"\n}\n");
- REQUIRE(*secondValue.printStr(libyang::DataFormat::XML, libyang::PrintFlags::KeepEmptyCont) == "<dummy xmlns=\"http://example.com/coze\">I am a dummy</dummy>\n");
+ REQUIRE(*secondValue.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == "{\n \"example-schema:dummy\": \"I am a dummy\"\n}\n");
+ REQUIRE(*secondValue.printStr(libyang::DataFormat::XML, libyang::PrintFlags::EmptyContainers) == "<dummy xmlns=\"http://example.com/coze\">I am a dummy</dummy>\n");
}
}
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
index 88ca5b9..3f874de 100644
--- a/tests/data_node.cpp
+++ b/tests/data_node.cpp
@@ -137,7 +137,7 @@ TEST_CASE("Data Node manipulation")
DOCTEST_SUBCASE("Printing")
{
auto node = ctx.parseData(data, libyang::DataFormat::JSON);
- auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
+ auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
const auto expected = R"({
"example-schema:leafInt32": 420,
"example-schema:first": {
@@ -158,7 +158,7 @@ TEST_CASE("Data Node manipulation")
REQUIRE(str == expected);
auto emptyCont = ctx.newPath("/example-schema:first");
- REQUIRE(emptyCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings) == std::nullopt);
+ REQUIRE(emptyCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings) == std::nullopt);
}
DOCTEST_SUBCASE("Overwriting a tree with a different tree")
@@ -504,7 +504,7 @@ TEST_CASE("Data Node manipulation")
{
auto node = std::optional{ctx.newPath("/example-schema:leafInt32", "420")};
libyang::validateAll(node, libyang::ValidationOptions::NoState);
- auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
+ auto str = node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers);
REQUIRE(str == data);
}
@@ -725,7 +725,7 @@ TEST_CASE("Data Node manipulation")
}
// The original tree should still be accesible.
- node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
+ node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings);
}
}
@@ -855,7 +855,7 @@ TEST_CASE("Data Node manipulation")
auto cont = ctx.newPath2("/example-schema2:contWithTwoNodes").createdNode;
data->unlinkWithSiblings();
cont->insertChild(*data);
- REQUIRE(*cont->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == R"({
+ REQUIRE(*cont->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == R"({
"example-schema2:contWithTwoNodes": {
"one": 333,
"two": 666
@@ -1501,9 +1501,9 @@ TEST_CASE("Data Node manipulation")
REQUIRE(std::holds_alternative<libyang::DataNode>(rawVal));
auto retrieved = std::get<libyang::DataNode>(rawVal);
REQUIRE(retrieved.path() == "/key");
- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== R"|({"key":"value"})|");
- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== R"|(<key>value</key>)|");
}
}
@@ -1525,9 +1525,9 @@ TEST_CASE("Data Node manipulation")
REQUIRE(std::holds_alternative<libyang::DataNode>(rawVal));
auto retrieved = std::get<libyang::DataNode>(rawVal);
REQUIRE(retrieved.path() == "/something");
- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== R"|(<something>lol</something>)|");
- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== R"|({"something":"lol"})|");
}
}
@@ -1572,9 +1572,9 @@ TEST_CASE("Data Node manipulation")
val = jsonAnyXmlNode.createdNode->asAny().releaseValue();
}
- REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== R"|({"example-schema:ax":[1,2,3]})|"s);
- REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== R"|(<ax xmlns="http://example.com/coze">)|"s + origJSON + "</ax>");
}
@@ -1603,9 +1603,9 @@ TEST_CASE("Data Node manipulation")
}
REQUIRE(root);
- REQUIRE(*root->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*root->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== origXML);
- REQUIRE(*root->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*root->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== origJSON);
auto node = root->findPath("/example-schema:ax");
@@ -1665,9 +1665,9 @@ TEST_CASE("Data Node manipulation")
auto retrieved = std::get<libyang::DataNode>(*val);
val.reset();
REQUIRE(retrieved.path() == "/a");
- REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== origXML);
- REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
+ REQUIRE(*retrieved.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::Siblings)
== origJSON);
}
}
@@ -1806,7 +1806,7 @@ TEST_CASE("Data Node manipulation")
nodeX.parseSubtree(data, libyang::DataFormat::JSON,
libyang::ParseOptions::Strict | libyang::ParseOptions::NoState | libyang::ParseOptions::ParseOnly);
- REQUIRE(*nodeX.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == R"({
+ REQUIRE(*nodeX.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == R"({
"example-schema5:x": {
"x_b": {
"x_b_leaf": 666
@@ -1948,7 +1948,7 @@ TEST_CASE("Data Node manipulation")
{
netconfDeletePresenceCont.newMeta(netconf, "operation", "delete");
netconfDeletePresenceCont.newMeta(ietfOrigin, "origin", "ietf-origin:default");
- REQUIRE(*netconfDeletePresenceCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings)
+ REQUIRE(*netconfDeletePresenceCont.printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings)
== R"(<presenceContainer xmlns="http://example.com/coze" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="delete" xmlns:or="urn:ietf:params:xml:ns:yang:ietf-origin" or:origin="or:default"/>)" "\n");
}
@@ -1957,7 +1957,7 @@ TEST_CASE("Data Node manipulation")
auto opaqueLeaf = ctx.newPath("/example-schema:leafInt32", std::nullopt, libyang::CreationOptions::Opaque);
REQUIRE_THROWS(opaqueLeaf.newMeta(netconf, "operation", "delete"));
opaqueLeaf.newAttrOpaqueJSON("ietf-netconf", "operation", "delete");
- REQUIRE(*opaqueLeaf.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
+ REQUIRE(*opaqueLeaf.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
== R"({
"example-schema:leafInt32": "",
"@example-schema:leafInt32": {
@@ -1976,7 +1976,7 @@ TEST_CASE("Data Node manipulation")
auto discard2 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", "sysrepo", "discard-items"}, libyang::JSON{"/example-schema:b"});
REQUIRE(!!discard2);
discard1->insertSibling(*discard2);
- REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
+ REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
== R"({
"sysrepo:discard-items": "/example-schema:a",
"sysrepo:discard-items": "/example-schema:b"
@@ -1991,7 +1991,7 @@ TEST_CASE("Data Node manipulation")
auto leafInt16 = ctx.newPath("/example-schema:leafInt16", "666");
leafInt16.insertSibling(*discard1);
- REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
== R"({
"example-schema:leafInt16": 666,
"sysrepo:discard-items": "/example-schema:a",
@@ -2020,7 +2020,7 @@ TEST_CASE("Data Node manipulation")
dummy.insertSibling(*discard3);
leafInt16.insertSibling(dummy);
- REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings)
== R"({
"example-schema:dummy": "blah",
"example-schema:leafInt16": 666,
@@ -2112,8 +2112,8 @@ TEST_CASE("Data Node manipulation")
data->unlinkWithSiblings();
out->insertChild(*data);
- REQUIRE(*out->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == expectedJson);
- REQUIRE(*out->printStr(libyang::DataFormat::XML, libyang::PrintFlags::WithSiblings) == expectedXml);
+ REQUIRE(*out->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == expectedJson);
+ REQUIRE(*out->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Siblings) == expectedXml);
}
DOCTEST_SUBCASE("libyang internal metadata")
@@ -2148,7 +2148,7 @@ TEST_CASE("Data Node manipulation")
auto node = ctx.newExtPath(ext, "/ietf-restconf:errors", std::nullopt, std::nullopt);
REQUIRE(node);
REQUIRE(node->schema().name() == "errors");
- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
"ietf-restconf:errors": {}
}
)");
@@ -2157,7 +2157,7 @@ TEST_CASE("Data Node manipulation")
REQUIRE(node->newPath("ietf-restconf:error[1]/error-tag", "invalid-attribute"));
REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[1]/error-message", "ahoj"));
REQUIRE_THROWS_WITH(node->newPath("ietf-restconf:error[1]/error-message", "duplicate create"), "Couldn't create a node with path 'ietf-restconf:error[1]/error-message': LY_EEXIST");
- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
"ietf-restconf:errors": {
"error": [
{
@@ -2173,7 +2173,7 @@ TEST_CASE("Data Node manipulation")
REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[2]/error-type", "transport"));
REQUIRE(node->newExtPath(ext, "/ietf-restconf:errors/error[2]/error-tag", "invalid-attribute"));
REQUIRE(node->newPath("ietf-restconf:error[2]/error-message", "aaa"));
- REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*node->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers) == R"({
"ietf-restconf:errors": {
"error": [
{
@@ -2397,8 +2397,8 @@ TEST_CASE("Data Node manipulation")
REQUIRE(response.tree->path() == "/example-schema:output");
REQUIRE(response.tree->isOpaque());
REQUIRE(!response.tree->child()); // nothing gets "parsed" here, the result is put into the tree that parseOp() operated on (!)
- CAPTURE(*response.tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
- CAPTURE(*replyTree.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
+ CAPTURE(*response.tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
+ CAPTURE(*replyTree.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings | libyang::PrintFlags::EmptyContainers));
node = replyTree.findPath("/example-schema:myRpc/outputLeaf", libyang::InputOutputNodes::Output);
REQUIRE(!!node);
@@ -2472,7 +2472,7 @@ TEST_CASE("Data Node manipulation")
REQUIRE(rpcOp.tree);
libyang::validateOp(*rpcTree, depTree, libyang::OperationType::RpcRestconf);
- REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == expected);
+ REQUIRE(*rpcTree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == expected);
}
DOCTEST_SUBCASE("Nodes in disjunctive cases defined together")
--
2.43.0
@@ -0,0 +1,112 @@
From af187d1bc27a7390b358d7ad3b9f8f44cabe41ad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Thu, 20 Nov 2025 12:46:49 +0100
Subject: [PATCH 7/7] don't segfault when listing module's features
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
This is especially relevant when working with the "printed context" from
sysrepo, because that context has all the parsed info removed. As a
result, it is not possible to query a module's list of features. Let's
stop dereferencing a null pointer in that case.
Bug: https://github.com/sysrepo/sysrepo/issues/3695
Change-Id: I6258f580b4e5aa02cae2d60260b84593aefcf587
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
CMakeLists.txt | 1 +
include/libyang-cpp/Utils.hpp | 1 +
src/Module.cpp | 3 +++
src/utils/exception.cpp | 5 +++++
tests/context.cpp | 12 ++++++++++++
5 files changed, 22 insertions(+)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cbaf82a..b9da66b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -118,6 +118,7 @@ if(BUILD_TESTING)
endfunction()
libyang_cpp_test(context)
+ target_link_libraries(test_context PkgConfig::LIBYANG)
libyang_cpp_test(data_node)
target_link_libraries(test_data_node PkgConfig::LIBYANG)
libyang_cpp_test(schema_node)
diff --git a/include/libyang-cpp/Utils.hpp b/include/libyang-cpp/Utils.hpp
index 934714d..428b265 100644
--- a/include/libyang-cpp/Utils.hpp
+++ b/include/libyang-cpp/Utils.hpp
@@ -35,6 +35,7 @@ public:
class LIBYANG_CPP_EXPORT ParsedInfoUnavailable : public Error {
public:
explicit ParsedInfoUnavailable();
+ explicit ParsedInfoUnavailable(const std::string& what);
};
/**
diff --git a/src/Module.cpp b/src/Module.cpp
index d6d4023..8eb8fb4 100644
--- a/src/Module.cpp
+++ b/src/Module.cpp
@@ -155,6 +155,9 @@ void Module::setImplemented(const AllFeatures)
*/
std::vector<Feature> Module::features() const
{
+ if (!m_module->parsed) {
+ throw ParsedInfoUnavailable{"Module::features: lys_module::parsed is not available"};
+ }
std::vector<Feature> res;
for (const auto& feature : std::span(m_module->parsed->features, LY_ARRAY_COUNT(m_module->parsed->features))) {
res.emplace_back(Feature{&feature, m_ctx});
diff --git a/src/utils/exception.cpp b/src/utils/exception.cpp
index ecd2b85..a8d908b 100644
--- a/src/utils/exception.cpp
+++ b/src/utils/exception.cpp
@@ -22,6 +22,11 @@ ParsedInfoUnavailable::ParsedInfoUnavailable()
{
}
+ParsedInfoUnavailable::ParsedInfoUnavailable(const std::string& what)
+ : Error(what)
+{
+}
+
ErrorCode ErrorWithCode::code() const
{
return m_errCode;
diff --git a/tests/context.cpp b/tests/context.cpp
index 355860b..5fce0f4 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -10,6 +10,7 @@
#include <fstream>
#include <libyang-cpp/Context.hpp>
#include <libyang-cpp/Utils.hpp>
+#include <libyang/context.h>
#include "example_schema.hpp"
#include "pretty_printers.hpp"
#include "test_vars.hpp"
@@ -333,6 +334,17 @@ TEST_CASE("context")
REQUIRE(enabledFeatures == expectedEnabledFeatures);
}
+ DOCTEST_SUBCASE("printed context")
+ {
+ ctx->setSearchDir(TESTS_DIR / "yang");
+ auto mod = ctx->loadModule("mod1", std::nullopt, {"*"});
+ REQUIRE(mod.features().size() == 3);
+ ly_ctx_free_parsed(retrieveContext(*ctx));
+ REQUIRE_THROWS_WITH_AS(mod.features(),
+ "Module::features: lys_module::parsed is not available",
+ libyang::ParsedInfoUnavailable);
+ }
+
DOCTEST_SUBCASE("Module::setImplemented")
{
ctx->setSearchDir(TESTS_DIR / "yang");
--
2.43.0