Remove local libyang-cpp and sysrepo-cpp

It has been included in buildroot
This commit is contained in:
Mattias Walström
2026-03-04 09:53:03 +01:00
parent 000c04cc8c
commit e7cb85edf3
23 changed files with 0 additions and 2254 deletions
-2
View File
@@ -38,8 +38,6 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/python-libyang/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/python-yangdoc/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/skeleton-init-finit/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/tetris/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/libyang-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/sysrepo-cpp/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/rousette/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/nghttp2-asio/Config.in"
source "$BR2_EXTERNAL_INFIX_PATH/package/date-cpp/Config.in"
@@ -1,135 +0,0 @@
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 1/7] Implement `SchemaNode::actionRpcs()`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
This function allows to access list of actions
within schema node.
It is implemented same as `Module::actionRpcs`
to follow same approach, so there is no need to use
collections.
Change-Id: Ie99908cfdd334433fd9ae6f1a909f196e61139fd
Signed-off-by: Jan Kundrát <jan.kundrat@cesnet.cz>
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/libyang-cpp/SchemaNode.hpp | 1 +
src/SchemaNode.cpp | 12 ++++++++++++
tests/example_schema.hpp | 28 ++++++++++++++++++++++++++++
tests/schema_node.cpp | 21 +++++++++++++++++++++
4 files changed, 62 insertions(+)
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
index 0f1a4c4..9f49fd7 100644
--- a/include/libyang-cpp/SchemaNode.hpp
+++ b/include/libyang-cpp/SchemaNode.hpp
@@ -79,6 +79,7 @@ public:
Collection<SchemaNode, IterationType::Sibling> siblings() const;
Collection<SchemaNode, IterationType::Sibling> immediateChildren() const;
std::vector<ExtensionInstance> extensionInstances() const;
+ std::vector<SchemaNode> actionRpcs() const;
std::vector<When> when() const;
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
index 26b5099..ce24203 100644
--- a/src/SchemaNode.cpp
+++ b/src/SchemaNode.cpp
@@ -117,6 +117,18 @@ Collection<SchemaNode, IterationType::Sibling> SchemaNode::immediateChildren() c
return c ? c->siblings() : Collection<SchemaNode, IterationType::Sibling>{nullptr, nullptr};
}
+/**
+ * @brief Returns a collection of action nodes (not RPC nodes) as SchemaNode
+ */
+std::vector<SchemaNode> SchemaNode::actionRpcs() const
+{
+ std::vector<SchemaNode> res;
+ for (auto action = reinterpret_cast<const struct lysc_node*>(lysc_node_actions(m_node)); action; action = action->next) {
+ res.emplace_back(SchemaNode{action, m_ctx});
+ }
+ return res;
+}
+
/**
* Returns the YANG description of the node.
*
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
index 0d8acb9..02d3d74 100644
--- a/tests/example_schema.hpp
+++ b/tests/example_schema.hpp
@@ -214,6 +214,34 @@ module example-schema {
}
container bigTree {
+ action firstAction {
+ input {
+ leaf inputLeaf1 {
+ type string;
+ }
+ }
+
+ output {
+ leaf outputLeaf1 {
+ type string;
+ }
+ }
+ }
+
+ action secondAction {
+ input {
+ leaf inputLeaf2 {
+ type string;
+ }
+ }
+
+ output {
+ leaf outputLeaf2 {
+ type string;
+ }
+ }
+ }
+
container one {
leaf myLeaf {
type string;
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
index 0001377..65ef462 100644
--- a/tests/schema_node.cpp
+++ b/tests/schema_node.cpp
@@ -544,6 +544,27 @@ TEST_CASE("SchemaNode")
REQUIRE(elem.extensionInstances()[2].argument() == "last-modified");
}
+ DOCTEST_SUBCASE("SchemaNode::actionRpcs")
+ {
+ DOCTEST_SUBCASE("no actions")
+ {
+ auto actions = ctx->findPath("/example-schema:presenceContainer").actionRpcs();
+ REQUIRE(actions.size() == 0);
+ }
+
+ DOCTEST_SUBCASE("two actions")
+ {
+ auto actions = ctx->findPath("/example-schema:bigTree").actionRpcs();
+ REQUIRE(actions.size() == 2);
+ REQUIRE(actions[0].asActionRpc().name() == "firstAction");
+ REQUIRE(actions[0].asActionRpc().input().child()->name() == "inputLeaf1");
+ REQUIRE(actions[0].asActionRpc().output().child()->name() == "outputLeaf1");
+ REQUIRE(actions[1].asActionRpc().name() == "secondAction");
+ REQUIRE(actions[1].asActionRpc().input().child()->name() == "inputLeaf2");
+ REQUIRE(actions[1].asActionRpc().output().child()->name() == "outputLeaf2");
+ }
+ }
+
DOCTEST_SUBCASE("SchemaNode::operator==")
{
auto a = ctx->findPath("/type_module:leafString");
--
2.43.0
@@ -1,38 +0,0 @@
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
@@ -1,216 +0,0 @@
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
@@ -1,435 +0,0 @@
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
@@ -1,77 +0,0 @@
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
@@ -1,364 +0,0 @@
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
@@ -1,112 +0,0 @@
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
-6
View File
@@ -1,6 +0,0 @@
config BR2_PACKAGE_LIBYANG_CPP
bool "libyang-cpp"
depends on BR2_USE_MMU
select BR2_PACKAGE_LIBYANG
help
Libyang CPP bindings
-3
View File
@@ -1,3 +0,0 @@
# Locally calculated
sha256 82e3758011ec44c78e98d0777799d6e12aec5b8a64b32ebb20d0fe50e32488bb LICENSE
sha256 70fd0df940026fb930d5407abe679e064caf4701bff35d79465b9ad2c0915808 libyang-cpp-v4.tar.gz
-13
View File
@@ -1,13 +0,0 @@
################################################################################
#
# CPP bindings for libyang
#
################################################################################
LIBYANG_CPP_VERSION = v4
LIBYANG_CPP_SITE = $(call github,CESNET,libyang-cpp,$(LIBYANG_CPP_VERSION))
LIBYANG_CPP_LICENSE = BSD-3-Clause
LIBYANG_CPP_LICENSE_FILES = LICENSE
LIBYANG_CPP_DEPENDENCIES = libyang
LIBYANG_CPP_INSTALL_STAGING = YES
$(eval $(cmake-package))
@@ -1,47 +0,0 @@
From ee0ce2ea31f3ecba4a4753a761df08f0698c2726 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:47:26 +0200
Subject: [PATCH 1/9] CI: pin to libyang v3
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8978
Change-Id: I2521ad3a62d6197e757e636b88478deeb3bb5592
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
.zuul.yaml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/.zuul.yaml b/.zuul.yaml
index 7ada6f7..cef174f 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -4,9 +4,9 @@
- f38-gcc-cover:
required-projects:
- name: github/CESNET/libyang
- override-checkout: devel
+ override-checkout: cesnet/2025-07-08
- name: github/sysrepo/sysrepo
- override-checkout: devel
+ override-checkout: cesnet/2025-06-02
- name: github/doctest/doctest
override-checkout: v2.4.8
- name: github/rollbear/trompeloeil
@@ -15,9 +15,9 @@
- f38-clang-asan-ubsan:
required-projects: &projects
- name: github/CESNET/libyang
- override-checkout: devel
+ override-checkout: cesnet/2025-07-08
- name: github/sysrepo/sysrepo
- override-checkout: devel
+ override-checkout: cesnet/2025-06-02
- name: github/doctest/doctest
override-checkout: v2.4.11
- name: github/rollbear/trompeloeil
--
2.43.0
@@ -1,33 +0,0 @@
From e49463aba8b44b932fdd342d1c1d117e70414aba Mon Sep 17 00:00:00 2001
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
Date: Fri, 17 Oct 2025 18:08:47 +0200
Subject: [PATCH 2/9] fix: implicit dependency on optional
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
chrono imports optional in gcc >=13.
added the explicit include to compile in gcc <=12.
Change-Id: I18cc1a45ab522ee6738d4bc7080696cba8284903
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/sysrepo-cpp/Connection.hpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/sysrepo-cpp/Connection.hpp b/include/sysrepo-cpp/Connection.hpp
index bf72bcd..a469a2c 100644
--- a/include/sysrepo-cpp/Connection.hpp
+++ b/include/sysrepo-cpp/Connection.hpp
@@ -7,6 +7,7 @@
*/
#pragma once
+#include <optional>
#include <chrono>
#include <memory>
#include <string>
--
2.43.0
@@ -1,31 +0,0 @@
From b8e349aacae4c7063517a1bc652e9e93f90e28ba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Fri, 24 Oct 2025 17:05:52 +0200
Subject: [PATCH 3/9] fix #include for for our public headers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Change-Id: Idcbfe38348e3ca2e4331fc1fad542ae759b8d0f1
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
src/utils/utils.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
index ff4a32c..c13b394 100644
--- a/src/utils/utils.cpp
+++ b/src/utils/utils.cpp
@@ -7,7 +7,7 @@
*/
#include <sysrepo-cpp/Connection.hpp>
-#include "sysrepo-cpp/utils/exception.hpp"
+#include <sysrepo-cpp/utils/exception.hpp>
extern "C" {
#include <sysrepo.h>
}
--
2.43.0
@@ -1,82 +0,0 @@
From 753a3e6eb346f2a9f6263b2d9fcd1c6dbb508247 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Fri, 24 Oct 2025 17:06:56 +0200
Subject: [PATCH 4/9] reduce number of files which are named "utils.hpp"
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
We used to have three (one in publicheaders, one in src/utils/, one
below tests/). Let's cut that by one to increase our collective sanity.
Change-Id: I9e3d93c65dffb29ba18b3ae325ddd7712a8be7a4
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
src/Connection.cpp | 2 +-
src/Session.cpp | 2 +-
src/Subscription.cpp | 2 +-
src/utils/{utils.hpp => misc.hpp} | 0
src/utils/utils.cpp | 2 +-
5 files changed, 4 insertions(+), 4 deletions(-)
rename src/utils/{utils.hpp => misc.hpp} (100%)
diff --git a/src/Connection.cpp b/src/Connection.cpp
index 0297ebe..1717b9a 100644
--- a/src/Connection.cpp
+++ b/src/Connection.cpp
@@ -13,7 +13,7 @@ extern "C" {
#include <sysrepo-cpp/utils/exception.hpp>
#include "utils/enum.hpp"
#include "utils/exception.hpp"
-#include "utils/utils.hpp"
+#include "utils/misc.hpp"
namespace sysrepo {
diff --git a/src/Session.cpp b/src/Session.cpp
index dfe1044..994ce87 100644
--- a/src/Session.cpp
+++ b/src/Session.cpp
@@ -21,7 +21,7 @@ extern "C" {
#include <utility>
#include "utils/enum.hpp"
#include "utils/exception.hpp"
-#include "utils/utils.hpp"
+#include "utils/misc.hpp"
using namespace std::string_literals;
namespace sysrepo {
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
index 303b54c..5bb724e 100644
--- a/src/Subscription.cpp
+++ b/src/Subscription.cpp
@@ -16,7 +16,7 @@ extern "C" {
}
#include "utils/enum.hpp"
#include "utils/exception.hpp"
-#include "utils/utils.hpp"
+#include "utils/misc.hpp"
namespace sysrepo {
/**
diff --git a/src/utils/utils.hpp b/src/utils/misc.hpp
similarity index 100%
rename from src/utils/utils.hpp
rename to src/utils/misc.hpp
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
index c13b394..2e8d8b4 100644
--- a/src/utils/utils.cpp
+++ b/src/utils/utils.cpp
@@ -12,7 +12,7 @@ extern "C" {
#include <sysrepo.h>
}
#include "enum.hpp"
-#include "utils.hpp"
+#include "misc.hpp"
namespace sysrepo {
/**
--
2.43.0
@@ -1,60 +0,0 @@
From dc118efa44b13508e1af47d924ad02ce58c58cea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Fri, 24 Oct 2025 17:18:49 +0200
Subject: [PATCH 5/9] Fix a different function return type
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
The prototype that was specified in the header file did not match what
was used in the corresponding .cpp implementation. This has not been a
problem because that implementation .cpp file was not including the
actual header, so this was not detected. Then, at runtime, this was not
a problem due to the way how derived types are laid out in memory.
Fixes: 6ed5442 Utilities for working with existing sysrepo:discard-items nodes
Change-Id: Ie4cf8c0e39e9c8486a6289c0328510ad6f7360fb
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
src/utils/utils.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
index 2e8d8b4..61d0c7f 100644
--- a/src/utils/utils.cpp
+++ b/src/utils/utils.cpp
@@ -8,6 +8,7 @@
#include <sysrepo-cpp/Connection.hpp>
#include <sysrepo-cpp/utils/exception.hpp>
+#include <sysrepo-cpp/utils/utils.hpp>
extern "C" {
#include <sysrepo.h>
}
@@ -78,7 +79,7 @@ void checkNoThreadFlag(const SubscribeOptions opts, const std::optional<FDHandli
* @see Session::operationalChanges()
* @see Session::dropForeignOperationalContent()
*/
-std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
+std::optional<libyang::DataNodeOpaque> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
{
auto discard = root.firstOpaqueSibling();
while (discard) {
@@ -97,11 +98,11 @@ std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, con
/**
* @short Find all sysrepo:discard-items nodes which match the given XPath or the descendants of this XPath
*/
-std::vector<libyang::DataNode> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
+std::vector<libyang::DataNodeOpaque> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
{
auto withSlash = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '/') ? xpathPrefix : xpathPrefix + '/';
auto withBracket = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '[') ? xpathPrefix : xpathPrefix + '[';
- std::vector<libyang::DataNode> res;
+ std::vector<libyang::DataNodeOpaque> res;
auto discard = root.firstOpaqueSibling();
while (discard) {
if (discard->name().matches("sysrepo", "discard-items")) {
--
2.43.0
@@ -1,61 +0,0 @@
From b943faea3cb00c5e86f21710647a2ca370e41f33 Mon Sep 17 00:00:00 2001
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
Date: Fri, 24 Oct 2025 13:45:00 +0200
Subject: [PATCH 6/9] fix: raw strings and macros break gcc < 13
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
see bug report:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55971
Change-Id: I04afc2b3bee86a7e4b038b277aad0e3a150a1940
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
tests/subscriptions-dynamic.cpp | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
index 5ed173b..561df0c 100644
--- a/tests/subscriptions-dynamic.cpp
+++ b/tests/subscriptions-dynamic.cpp
@@ -39,21 +39,28 @@
REQUIRE(pipeStatus((SUBSCRIPTION).fd(), -1) == PipeStatus::DataReady); \
(SUBSCRIPTION).processEvent(cbNotif);
-#define SUBSCRIPTION_TERMINATED(SUBSCRIPTION) R"({
+auto SUBSCRIPTION_TERMINATED(const auto& SUBSCRIPTION)
+{
+ return R"({
"ietf-subscribed-notifications:subscription-terminated": {
- "id": )" + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(,
+ "id": )"
+ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(,
"reason": "no-such-subscription"
}
}
-)"
+)";
+};
-#define REPLAY_COMPLETED(SUBSCRIPTION) R"({
+auto REPLAY_COMPLETED(const auto& SUBSCRIPTION)
+{
+ return R"({
"ietf-subscribed-notifications:replay-completed": {
- "id": )" \
- + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(
+ "id": )"
+ + std::to_string((SUBSCRIPTION).subscriptionId()) + R"(
}
}
-)"
+)";
+};
#define REQUIRE_YANG_PUSH_UPDATE(SUBSCRIPTION, NOTIFICATION) \
TROMPELOEIL_REQUIRE_CALL(rec, recordYangPushUpdate((SUBSCRIPTION).subscriptionId(), NOTIFICATION)).IN_SEQUENCE(seq);
--
2.43.0
@@ -1,305 +0,0 @@
From 5fd8cf4fb8db1d65b6a20d2777bad22647e7f5e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Thu, 23 Oct 2025 16:04:10 +0200
Subject: [PATCH 7/9] Port to libyang v4
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
I'm rather disappointed with the upstream changes that implement that
shared libyang "printed context" mapped to a fixed address, I think it's
effectively a step backwards. Upstream is happy with their solution, so
here we are.
Change-Id: I6b89422ec66bc694dc2c6ecd0d40aa1e7f76a741
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8984
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
.zuul.yaml | 8 +++---
CMakeLists.txt | 6 ++---
ci/build.sh | 4 +++
include/sysrepo-cpp/Enum.hpp | 18 ++++++++++---
include/sysrepo-cpp/utils/utils.hpp | 5 ++++
src/Connection.cpp | 3 +--
src/Session.cpp | 2 +-
src/utils/enum.hpp | 18 +++++++------
src/utils/utils.cpp | 18 +++++++++++++
tests/session.cpp | 39 +++++++++++++++++++++--------
10 files changed, 90 insertions(+), 31 deletions(-)
diff --git a/.zuul.yaml b/.zuul.yaml
index cef174f..7ada6f7 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -4,9 +4,9 @@
- f38-gcc-cover:
required-projects:
- name: github/CESNET/libyang
- override-checkout: cesnet/2025-07-08
+ override-checkout: devel
- name: github/sysrepo/sysrepo
- override-checkout: cesnet/2025-06-02
+ override-checkout: devel
- name: github/doctest/doctest
override-checkout: v2.4.8
- name: github/rollbear/trompeloeil
@@ -15,9 +15,9 @@
- f38-clang-asan-ubsan:
required-projects: &projects
- name: github/CESNET/libyang
- override-checkout: cesnet/2025-07-08
+ override-checkout: devel
- name: github/sysrepo/sysrepo
- override-checkout: cesnet/2025-06-02
+ override-checkout: devel
- name: github/doctest/doctest
override-checkout: v2.4.11
- name: github/rollbear/trompeloeil
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f2a9b09..49338dd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -19,7 +19,7 @@ add_custom_target(sysrepo-cpp-version-cmake
cmake/ProjectGitVersionRunner.cmake
)
include(cmake/ProjectGitVersion.cmake)
-set(SYSREPO_CPP_PKG_VERSION "6")
+set(SYSREPO_CPP_PKG_VERSION "7")
prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION})
find_package(Doxygen)
@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
option(WITH_EXAMPLES "Build examples" ON)
find_package(PkgConfig)
-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.7.4 IMPORTED_TARGET)
+pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET)
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
diff --git a/ci/build.sh b/ci/build.sh
index 785a811..67bc7d3 100755
--- a/ci/build.sh
+++ b/ci/build.sh
@@ -47,6 +47,10 @@ if [[ $ZUUL_JOB_NAME =~ .*-tsan ]]; then
# Our TSAN does not have interceptors for a variety of "less common" functions such as pthread_mutex_clocklock.
# Disable all functions which are optional in sysrepo/libnetconf2/Netopeer2.
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DHAVE_PTHREAD_MUTEX_TIMEDLOCK=OFF -DHAVE_PTHREAD_MUTEX_CLOCKLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKRDLOCK=OFF -DHAVE_PTHREAD_RWLOCK_CLOCKWRLOCK=OFF -DHAVE_PTHREAD_COND_CLOCKWAIT=OFF"
+
+ # This is currently broken on TSAN ("Failed to map the printed context (Operation not permitted).").
+ # Even if it wasn't broken, I want at least one build in the matrix with this (mis)feature disabled.
+ CMAKE_OPTIONS="${CMAKE_OPTIONS} -DPRINTED_CONTEXT_ADDRESS=0"
fi
if [[ $ZUUL_JOB_NAME =~ .*-cover.* ]]; then
diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp
index 48467c6..505797a 100644
--- a/include/sysrepo-cpp/Enum.hpp
+++ b/include/sysrepo-cpp/Enum.hpp
@@ -176,12 +176,24 @@ enum class NotificationType : uint32_t {
};
/**
- * Wraps `sr_conn_flag_e`.
+ * Wraps `sr_context_flag_t`.
+ */
+enum class ContextFlags : uint32_t {
+ Default = 0x00, /**< SR_CTX_DEFAULT */
+ NoPrinted = 0x01, /**< SR_CTX_NO_PRINTED */
+ LibYangPrivParsed = 0x02, /**< SR_CTX_SET_PRIV_PARSED */
+};
+
+constexpr ContextFlags operator|(const ContextFlags a, const ContextFlags b)
+{
+ return implEnumBitOr(a, b);
+}
+
+/**
+ * Wraps `sr_conn_flag_t`.
*/
enum class ConnectionFlags : uint32_t {
Default = 0x00, /**< SR_CONN_DEFAULT */
- CacheRunning = 0x01, /**< SR_CONN_CACHE_RUNNING */
- LibYangPrivParsed = 0x02, /**< SR_CONN_CTX_SET_PRIV_PARSED */
};
constexpr ConnectionFlags operator|(const ConnectionFlags a, const ConnectionFlags b)
diff --git a/include/sysrepo-cpp/utils/utils.hpp b/include/sysrepo-cpp/utils/utils.hpp
index 64b1c2e..6f2446f 100644
--- a/include/sysrepo-cpp/utils/utils.hpp
+++ b/include/sysrepo-cpp/utils/utils.hpp
@@ -12,6 +12,11 @@
namespace sysrepo {
Session wrapUnmanagedSession(sr_session_ctx_s* session);
void setLogLevelStderr(const LogLevel);
+enum class GlobalContextEffect {
+ Lazy, /**< Set these flags the next time the shared context is applied */
+ Immediate, /**< Apply immediately */
+};
+ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when);
std::optional<libyang::DataNodeOpaque> findMatchingDiscard(libyang::DataNode root, const std::string& xpath);
std::vector<libyang::DataNodeOpaque> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix);
void unlinkFromForest(std::optional<libyang::DataNode>& root, libyang::DataNode node);
diff --git a/src/Connection.cpp b/src/Connection.cpp
index 1717b9a..96e340c 100644
--- a/src/Connection.cpp
+++ b/src/Connection.cpp
@@ -26,7 +26,7 @@ Connection::Connection(const ConnectionFlags options)
: ctx(nullptr)
{
sr_conn_ctx_t* ctx;
- auto res = sr_connect(static_cast<sr_conn_options_t>(options), &ctx);
+ auto res = sr_connect(static_cast<sr_conn_flag_t>(options), &ctx);
throwIfError(res, "Couldn't connect to sysrepo");
this->ctx = std::shared_ptr<sr_conn_ctx_t>(ctx, sr_disconnect);
@@ -96,5 +96,4 @@ uint32_t Connection::getId() const
{
return sr_get_cid(ctx.get());
}
-
}
diff --git a/src/Session.cpp b/src/Session.cpp
index 994ce87..95b8f24 100644
--- a/src/Session.cpp
+++ b/src/Session.cpp
@@ -241,7 +241,7 @@ std::optional<libyang::DataNode> Session::operationalChanges(const std::optional
void Session::discardOperationalChanges(const std::optional<std::string>& moduleName, std::chrono::milliseconds timeout)
{
SYSREPO_CPP_SESSION_MTX;
- auto res = sr_discard_oper_changes(nullptr, m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count());
+ auto res = sr_discard_oper_changes(m_sess.get(), moduleName ? nullptr : moduleName->c_str(), timeout.count());
throwIfError(res, "Session::discardOoperationalChanges: Couldn't discard "s + (moduleName ? "for module \"" + *moduleName + "\"" : "globally"s), m_sess.get());
}
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
index 2d3a298..e917b21 100644
--- a/src/utils/enum.hpp
+++ b/src/utils/enum.hpp
@@ -151,13 +151,16 @@ static_assert(toNotificationType(SR_EV_NOTIF_MODIFIED) == NotificationType::Modi
static_assert(toNotificationType(SR_EV_NOTIF_SUSPENDED) == NotificationType::Suspended);
static_assert(toNotificationType(SR_EV_NOTIF_RESUMED) == NotificationType::Resumed);
-static_assert(std::is_same_v<sr_conn_options_t, std::underlying_type_t<ConnectionFlags>>);
+static_assert(std::is_same_v<std::underlying_type_t<sr_conn_flag_t>, std::underlying_type_t<ConnectionFlags>>);
static_assert(static_cast<ConnectionFlags>(SR_CONN_DEFAULT) == ConnectionFlags::Default);
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CACHE_RUNNING) == ConnectionFlags::CacheRunning);
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CTX_SET_PRIV_PARSED) == ConnectionFlags::LibYangPrivParsed);
-static_assert(static_cast<ConnectionFlags>(SR_CONN_CACHE_RUNNING | SR_CONN_CTX_SET_PRIV_PARSED) == (ConnectionFlags::CacheRunning | ConnectionFlags::LibYangPrivParsed));
-static_assert(std::is_same_v<sr_get_options_t, std::underlying_type_t<GetOptions>>);
+static_assert(std::is_same_v<std::underlying_type_t<sr_context_flag_t>, std::underlying_type_t<ContextFlags>>);
+static_assert(static_cast<ContextFlags>(SR_CTX_DEFAULT) == ContextFlags::Default);
+static_assert(static_cast<ContextFlags>(SR_CTX_SET_PRIV_PARSED) == ContextFlags::LibYangPrivParsed);
+static_assert(static_cast<ContextFlags>(SR_CTX_NO_PRINTED) == ContextFlags::NoPrinted);
+static_assert(static_cast<ContextFlags>(SR_CTX_SET_PRIV_PARSED | SR_CTX_NO_PRINTED) == (ContextFlags::LibYangPrivParsed | ContextFlags::NoPrinted));
+
+// upstream project provides no dedicated type for these
static_assert(static_cast<GetOptions>(SR_OPER_DEFAULT) == GetOptions::Default);
static_assert(static_cast<GetOptions>(SR_OPER_NO_STATE) == GetOptions::OperNoState);
static_assert(static_cast<GetOptions>(SR_OPER_NO_CONFIG) == GetOptions::OperNoConfig);
@@ -168,9 +171,10 @@ static_assert(static_cast<GetOptions>(SR_OPER_NO_POLL_CACHED) == GetOptions::Ope
static_assert(static_cast<GetOptions>(SR_OPER_NO_RUN_CACHED) == GetOptions::OperNoRunningCached);
static_assert(static_cast<GetOptions>(SR_GET_NO_FILTER) == GetOptions::NoFilter);
-constexpr sr_get_options_t toGetOptions(const GetOptions opts)
+// upstream project provides no dedicated type for these
+constexpr uint32_t toGetOptions(const GetOptions opts)
{
- return static_cast<sr_get_options_t>(opts);
+ return static_cast<uint32_t>(opts);
}
static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create);
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
index 61d0c7f..dbbc5ce 100644
--- a/src/utils/utils.cpp
+++ b/src/utils/utils.cpp
@@ -13,6 +13,7 @@ extern "C" {
#include <sysrepo.h>
}
#include "enum.hpp"
+#include "exception.hpp"
#include "misc.hpp"
namespace sysrepo {
@@ -37,6 +38,23 @@ void setLogLevelStderr(const LogLevel level)
sr_log_stderr(toLogLevel(level));
}
+/**
+ * @brief Set global sysrepo-level context options
+ *
+ * Be advised of consequences of manipulating a shared global state, especially when using multiple connections.
+ *
+ * Wraps `sr_context_options`.
+ */
+ContextFlags setGlobalContextOptions(const ContextFlags flags, const GlobalContextEffect when)
+{
+ uint32_t old;
+ throwIfError(
+ sr_context_options(static_cast<uint32_t>(flags), when == GlobalContextEffect::Immediate, &old),
+ "sr_context_options failed",
+ nullptr);
+ return static_cast<ContextFlags>(old);
+}
+
std::timespec toTimespec(std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> tp)
{
// https://embeddedartistry.com/blog/2019/01/31/converting-between-timespec-stdchrono#-std-chrono-time_point-to-timespec-
diff --git a/tests/session.cpp b/tests/session.cpp
index 47b5631..a709a09 100644
--- a/tests/session.cpp
+++ b/tests/session.cpp
@@ -627,17 +627,34 @@ TEST_CASE("session")
DOCTEST_SUBCASE("libyang context flags")
{
- sess.setItem("/test_module:popelnice/s", "666");
- REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666");
- // Parsed type info is not preserved by libyang unless its context is constructed with a flag,
- // and that flag is not used by sysrepo by default...
- REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable);
-
- // ...unless we pass that flag explicitly as a parameter to the connection.
- auto sess2 = sysrepo::Connection{sysrepo::ConnectionFlags::LibYangPrivParsed}.sessionStart();
- sess2.setItem("/test_module:popelnice/s", "333");
- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333");
- REQUIRE(sess2.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10);
+ DOCTEST_SUBCASE("default")
+ {
+ sess.setItem("/test_module:popelnice/s", "666");
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "666");
+ // Parsed type info is not preserved by libyang unless its context is constructed with a flag,
+ // and that flag is not used by sysrepo by default.
+ REQUIRE_THROWS_AS(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length(), libyang::ParsedInfoUnavailable);
+
+ // pending changes hold the context lock, so we cannot change the global context options
+ REQUIRE_THROWS_WITH_AS(
+ sysrepo::setGlobalContextOptions(
+ sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted,
+ sysrepo::GlobalContextEffect::Immediate),
+ "sr_context_options failed: SR_ERR_TIME_OUT",
+ sysrepo::ErrorWithCode);
+ }
+
+ // Since this is actually a shared global state, changing it is "tricky". The best bet is to actually
+ // ensure that there are no other connections (which we cannot do here, we already have a connection opened
+ // from the common code at the very top of this test case), and especially no internal sysrepo-level locks
+ // on the shared libyang context. For example, no uncommited changes -- hence a new subcase.
+ DOCTEST_SUBCASE("Connection::setGlobalContextOptions LibYangPrivParsed")
+ {
+ sysrepo::setGlobalContextOptions(sysrepo::ContextFlags::LibYangPrivParsed | sysrepo::ContextFlags::NoPrinted, sysrepo::GlobalContextEffect::Immediate);
+ sess.setItem("/test_module:popelnice/s", "333");
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").asTerm().valueStr() == "333");
+ REQUIRE(sess.getOneNode("/test_module:popelnice/s").schema().asLeaf().valueType().asString().length().parts[0].max == 10);
+ }
}
DOCTEST_SUBCASE("replay support")
--
2.43.0
@@ -1,90 +0,0 @@
From 6e9dbd8513ce75e51f2f714df69c08af5b556d1d Mon Sep 17 00:00:00 2001
From: Edoardo Bortolozzo <edoardo.bortolozzo@thinkquantum.com>
Date: Tue, 28 Oct 2025 13:24:13 +0100
Subject: [PATCH 8/9] fix: onOperGet unconsistent with C library.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Fixes: https://github.com/sysrepo/sysrepo-cpp/issues/29
Change-Id: I5a21069889c59466df8e9457c3a316b58e2571bb
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/sysrepo-cpp/Session.hpp | 2 +-
include/sysrepo-cpp/Subscription.hpp | 2 +-
src/Session.cpp | 4 ++--
src/Subscription.cpp | 4 ++--
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
index aae7068..3af4b10 100644
--- a/include/sysrepo-cpp/Session.hpp
+++ b/include/sysrepo-cpp/Session.hpp
@@ -127,7 +127,7 @@ public:
[[nodiscard]] Subscription onOperGet(
const std::string& moduleName,
OperGetCb cb,
- const std::optional<std::string>& xpath = std::nullopt,
+ const std::string& path,
const SubscribeOptions opts = SubscribeOptions::Default,
ExceptionHandler handler = nullptr,
const std::optional<FDHandling>& callbacks = std::nullopt);
diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp
index c8f3ff8..14ecce4 100644
--- a/include/sysrepo-cpp/Subscription.hpp
+++ b/include/sysrepo-cpp/Subscription.hpp
@@ -32,7 +32,7 @@ public:
Subscription& operator=(Subscription&&) noexcept;
void onModuleChange(const std::string& moduleName, ModuleChangeCb cb, const std::optional<std::string>& xpath = std::nullopt, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default);
- void onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional<std::string>& xpath, const SubscribeOptions opts = SubscribeOptions::Default);
+ void onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts = SubscribeOptions::Default);
void onRPCAction(const std::string& xpath, RpcActionCb cb, uint32_t priority = 0, const SubscribeOptions opts = SubscribeOptions::Default);
void onNotification(
const std::string& moduleName,
diff --git a/src/Session.cpp b/src/Session.cpp
index 95b8f24..6f0327d 100644
--- a/src/Session.cpp
+++ b/src/Session.cpp
@@ -505,14 +505,14 @@ Subscription Session::onModuleChange(
Subscription Session::onOperGet(
const std::string& moduleName,
OperGetCb cb,
- const std::optional<std::string>& xpath,
+ const std::string& path,
const SubscribeOptions opts,
ExceptionHandler handler,
const std::optional<FDHandling>& callbacks)
{
checkNoThreadFlag(opts, callbacks);
auto sub = Subscription{*this, handler, callbacks};
- sub.onOperGet(moduleName, cb, xpath, opts);
+ sub.onOperGet(moduleName, cb, path, opts);
return sub;
}
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
index 5bb724e..dca1176 100644
--- a/src/Subscription.cpp
+++ b/src/Subscription.cpp
@@ -215,14 +215,14 @@ void Subscription::onModuleChange(const std::string& moduleName, ModuleChangeCb
* @param xpath XPath that identifies which data this subscription is able to provide.
* @param opts Options further changing the behavior of this method.
*/
-void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::optional<std::string>& xpath, const SubscribeOptions opts)
+void Subscription::onOperGet(const std::string& moduleName, OperGetCb cb, const std::string& path, const SubscribeOptions opts)
{
SYSREPO_CPP_SESSION_MTX_OF(m_sess);
checkNoThreadFlag(opts, m_customEventLoopCbs);
auto& privRef = m_operGetCbs.emplace_back(PrivData{cb, m_exceptionHandler.get()});
sr_subscription_ctx_s* ctx = m_sub.get();
- auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), xpath ? xpath->c_str() : nullptr, operGetItemsCb, reinterpret_cast<void*>(&privRef), toSubscribeOptions(opts), &ctx);
+ auto res = sr_oper_get_subscribe(m_sess.m_sess.get(), moduleName.c_str(), path.c_str(), operGetItemsCb, reinterpret_cast<void*>(&privRef), toSubscribeOptions(opts), &ctx);
throwIfError(res, "Couldn't create operational get items subscription", m_sess.m_sess.get());
saveContext(ctx);
--
2.43.0
@@ -1,121 +0,0 @@
From 60d729e4791e5447cf0d969be6da9f8f1878af64 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:38:04 +0100
Subject: [PATCH 9/9] Update to libyang v4.2
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Wires
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/9009
Change-Id: Ia4c53dba0befa8dade38e728489edc6473aa5f4f
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
CMakeLists.txt | 4 ++--
tests/session.cpp | 14 +++++++-------
tests/subscriptions-dynamic.cpp | 4 ++--
3 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 49338dd..8234464 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,8 +27,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
option(WITH_EXAMPLES "Build examples" ON)
find_package(PkgConfig)
-pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=5 IMPORTED_TARGET)
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.4 IMPORTED_TARGET)
+pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=6 IMPORTED_TARGET)
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=4.2.8 IMPORTED_TARGET)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
diff --git a/tests/session.cpp b/tests/session.cpp
index a709a09..0e92e65 100644
--- a/tests/session.cpp
+++ b/tests/session.cpp
@@ -105,7 +105,7 @@ TEST_CASE("session")
auto data = sess.getData("/test_module:popelnice", 0);
REQUIRE(data);
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
"test_module:popelnice": {
"content": {
"trash": [
@@ -126,14 +126,14 @@ TEST_CASE("session")
data = sess.getData("/test_module:popelnice", 1);
REQUIRE(data);
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
"test_module:popelnice": {}
}
)");
data = sess.getData("/test_module:popelnice", 2);
REQUIRE(data);
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
"test_module:popelnice": {
"content": {}
}
@@ -143,7 +143,7 @@ TEST_CASE("session")
// If a list should be returned, its keys are always returned as well.
data = sess.getData("/test_module:popelnice", 3);
REQUIRE(data);
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
"test_module:popelnice": {
"content": {
"trash": [
@@ -161,7 +161,7 @@ TEST_CASE("session")
data = sess.getData("/test_module:popelnice", 4);
REQUIRE(data);
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::EmptyContainers) == R"({
"test_module:popelnice": {
"content": {
"trash": [
@@ -497,7 +497,7 @@ TEST_CASE("session")
{
sess.switchDatastore(sysrepo::Datastore::FactoryDefault);
auto data = sess.getData("/*");
- REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) == "{\n\n}\n");
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) == "{\n\n}\n");
REQUIRE_THROWS_AS(sess.setItem(leaf, "123"), sysrepo::ErrorWithCode);
}
@@ -622,7 +622,7 @@ TEST_CASE("session")
}
// the original tree is not corrupted
- REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings) != "");
+ REQUIRE(*conf->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings) != "");
}
DOCTEST_SUBCASE("libyang context flags")
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
index 561df0c..0d4ce7c 100644
--- a/tests/subscriptions-dynamic.cpp
+++ b/tests/subscriptions-dynamic.cpp
@@ -140,12 +140,12 @@ TEST_CASE("Dynamic subscriptions")
const auto subId = std::get<uint32_t>(idNode->asTerm().value());
idNode->unlink();
- rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
+ rec.recordYangPushUpdate(subId, tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
};
auto cbNotif = [&](const std::optional<libyang::DataNode>& tree, auto) {
REQUIRE(tree);
- rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
+ rec.recordNotification(tree->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Siblings));
};
// write some initial data
--
2.43.0
-7
View File
@@ -1,7 +0,0 @@
config BR2_PACKAGE_SYSREPO_CPP
bool "sysrepo-cpp"
depends on BR2_USE_MMU
select BR2_PACKAGE_LIBYANG_CPP
select BR2_PACKAGE_SYSREPO
help
Sysrepo CPP bindings
-3
View File
@@ -1,3 +0,0 @@
# Locally calculated
sha256 82e3758011ec44c78e98d0777799d6e12aec5b8a64b32ebb20d0fe50e32488bb LICENSE
sha256 d7fbeaba7f2fdaf70c67cc70f438943eee636b21f08854e58967d0d97801bacc sysrepo-cpp-v6.tar.gz
-13
View File
@@ -1,13 +0,0 @@
################################################################################
#
# CPP bindings for sysrepo
#
################################################################################
SYSREPO_CPP_VERSION = v6
SYSREPO_CPP_SITE = $(call github,sysrepo,sysrepo-cpp,$(SYSREPO_CPP_VERSION))
SYSREPO_CPP_LICENSE = BSD-3-Clause
SYSREPO_CPP_LICENSE_FILES = LICENSE
SYSREPO_CPP_DEPENDENCIES = sysrepo libyang-cpp
SYSREPO_CPP_INSTALL_STAGING = YES
$(eval $(cmake-package))