Bump sysrepo, netopeer,libyang and libnetconf2

A lot of changes in sysrepo, required to add extra patches (bugfixes) to sysrepo,
sysrepo-cpp and rousette.
This commit is contained in:
Mattias Walström
2025-01-20 09:18:09 +01:00
parent 94cd526772
commit 5cc73ab97c
41 changed files with 5285 additions and 70 deletions
@@ -0,0 +1,82 @@
From d0f6422fee7a46fcb7445c88f499f61b3eb0ead0 Mon Sep 17 00:00:00 2001
From: Adam Piecek <Adam.Piecek@cesnet.cz>
Date: Wed, 23 Oct 2024 14:37:09 +0200
Subject: [PATCH 1/8] added support for RpcYang in Context::parseOp
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
Change-Id: I25182ea2d042be1e6e4246e18aee260cc032e547
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
src/Context.cpp | 6 ++++--
tests/context.cpp | 21 +++++++++++++++++++++
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/Context.cpp b/src/Context.cpp
index b844bd9..287f8c8 100644
--- a/src/Context.cpp
+++ b/src/Context.cpp
@@ -221,7 +221,8 @@ std::optional<DataNode> Context::parseExtData(
* - a NETCONF RPC,
* - a NETCONF notification,
* - a RESTCONF notification,
- * - a YANG notification.
+ * - a YANG notification,
+ * - a YANG RPC.
*
* Parsing any of these requires just the schema (which is available through the Context), and the textual payload.
* All the other information are encoded in the textual payload as per the standard.
@@ -243,6 +244,7 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con
auto in = wrap_ly_in_new_memory(input);
switch (opType) {
+ case OperationType::RpcYang:
case OperationType::RpcNetconf:
case OperationType::NotificationNetconf:
case OperationType::NotificationRestconf:
@@ -254,7 +256,7 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con
ParsedOp res;
res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt;
- if (opType == OperationType::NotificationYang) {
+ if ((opType == OperationType::NotificationYang) || (opType == OperationType::RpcYang)) {
res.op = op && tree ? std::optional{DataNode(op, res.tree->m_refs)} : std::nullopt;
} else {
res.op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt;
diff --git a/tests/context.cpp b/tests/context.cpp
index 71ae873..11019eb 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -464,6 +464,27 @@ TEST_CASE("context")
REQUIRE(data->findPath("/example-schema:leafInt8")->asTerm().valueStr() == "-43");
}
+ DOCTEST_SUBCASE("Context::parseOp")
+ {
+ DOCTEST_SUBCASE("RPC")
+ {
+ ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
+ std::string dataJson = R"({"example-schema:myRpc":{"inputLeaf":"str"}})";
+ auto pop = ctx->parseOp(dataJson, libyang::DataFormat::JSON, libyang::OperationType::RpcYang);
+ REQUIRE(pop.op->schema().name() == "myRpc");
+ REQUIRE(pop.tree->findPath("/example-schema:myRpc/inputLeaf")->asTerm().valueStr() == "str");
+ }
+ DOCTEST_SUBCASE("action")
+ {
+ ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
+ std::string datajson = R"({"example-schema:person":[{"name":"john", "poke":{}}]})";
+ auto pop = ctx->parseOp(datajson, libyang::DataFormat::JSON, libyang::OperationType::RpcYang);
+ REQUIRE(pop.op->schema().name() == "poke");
+ REQUIRE(pop.tree->findPath("/example-schema:person[name='john']/poke")->schema().nodeType() == libyang::NodeType::Action);
+ }
+ }
+
+
DOCTEST_SUBCASE("Context::parseExt")
{
ctx->setSearchDir(TESTS_DIR / "yang");
--
2.43.0
@@ -0,0 +1,53 @@
From 7e015f3486bdbb54f1dcc2e2ce51102b1d623081 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
Date: Wed, 23 Oct 2024 12:52:24 +0200
Subject: [PATCH 2/8] throw when lyd_validate_all returns error
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
Bug: https://github.com/CESNET/libyang-cpp/issues/20
Change-Id: I005a2f1b057978573a4046e7b4cc31d77e36fde3
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
src/DataNode.cpp | 4 +++-
tests/data_node.cpp | 7 +++++++
2 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
index 51c86c8..2ef17f2 100644
--- a/src/DataNode.cpp
+++ b/src/DataNode.cpp
@@ -1170,7 +1170,9 @@ void validateAll(std::optional<libyang::DataNode>& node, const std::optional<Val
}
// TODO: support the `diff` argument
- lyd_validate_all(node ? &node->m_node : nullptr, nullptr, opts ? utils::toValidationOptions(*opts) : 0, nullptr);
+ auto ret = lyd_validate_all(node ? &node->m_node : nullptr, nullptr, opts ? utils::toValidationOptions(*opts) : 0, nullptr);
+ throwIfError(ret, "libyang:validateAll: lyd_validate_all failed");
+
if (!node->m_node) {
node = std::nullopt;
}
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
index a23e4c2..8a2610e 100644
--- a/tests/data_node.cpp
+++ b/tests/data_node.cpp
@@ -489,6 +489,13 @@ TEST_CASE("Data Node manipulation")
REQUIRE_THROWS_WITH_AS(libyang::validateAll(node, libyang::ValidationOptions::NoState), "validateAll: Node is not a unique reference", libyang::Error);
}
+ DOCTEST_SUBCASE("validateAll throws on validation failure")
+ {
+ ctx.parseModule(type_module, libyang::SchemaFormat::YANG);
+ auto node = std::optional{ctx.newPath("/type_module:leafWithConfigFalse", "hi")};
+ REQUIRE_THROWS_WITH_AS(libyang::validateAll(node, libyang::ValidationOptions::NoState), "libyang:validateAll: lyd_validate_all failed: LY_EVALID", libyang::ErrorWithCode);
+ }
+
DOCTEST_SUBCASE("unlink")
{
auto root = ctx.parseData(data2, libyang::DataFormat::JSON);
--
2.43.0
@@ -0,0 +1,38 @@
From 490d8bb242d33213b948485f5b94c55e22cf86a6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Thu, 21 Nov 2024 11:32:44 +0100
Subject: [PATCH 3/8] remove a misleading comment
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
The whole intention within action's input/output handling here was to
put some emphasis on the fact that we aren't tracking the input/output
nodes directly. However, looking at all the other classes this is a bit
redundant, we're using a pattern like this all the time. Just drop the
comment.
Change-Id: Ibd9bf9f1e83c650dda3bc43ef48e61dd6d95da5a
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
src/SchemaNode.cpp | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
index 81e938f..9934cea 100644
--- a/src/SchemaNode.cpp
+++ b/src/SchemaNode.cpp
@@ -640,9 +640,6 @@ bool List::isUserOrdered() const
*/
ActionRpcInput ActionRpc::input() const
{
- // I need a lysc_node* for ActionRpcInput, but m_node->input is a lysp_node_action_inout. lysp_node_action_inout is
- // still just a lysc_node, so I'll just convert to lysc_node.
- // This is not very pretty, but I don't want to introduce another member for ActionRpcInput and ActionRpcOutput.
return ActionRpcInput{reinterpret_cast<const lysc_node*>(&reinterpret_cast<const lysc_node_action*>(m_node)->input), m_ctx};
}
--
2.43.0
@@ -0,0 +1,35 @@
From e1b17386cf61048d2fe27fffb3b763981a225f52 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
Date: Wed, 27 Nov 2024 09:47:47 +0100
Subject: [PATCH 4/8] schema: improve `List::keys()` not to use `std::move`
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
`List::keys()` used `std::move` while iterating over array of leafs.
This was solved without using `std::move`.
Change-Id: I8cbf8780ecd8848e46c1de5d4123a08624536bba
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
src/SchemaNode.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
index 9934cea..20e2aff 100644
--- a/src/SchemaNode.cpp
+++ b/src/SchemaNode.cpp
@@ -593,8 +593,7 @@ std::vector<Leaf> List::keys() const
LY_LIST_FOR(list->child, elem)
{
if (lysc_is_key(elem)) {
- Leaf leaf(elem, m_ctx);
- res.emplace_back(std::move(leaf));
+ res.emplace_back(Leaf(elem, m_ctx));
}
}
--
2.43.0
@@ -0,0 +1,124 @@
From 1102ecdcafbc9206f59b383769687e418557838e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
Date: Mon, 25 Nov 2024 15:54:02 +0100
Subject: [PATCH 5/8] schema: make leaf-list's `default` statement available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
Make leaf-list's `default` statement available so that it can be
accessed if end-user requires reading schema nodes.
`LeafList::defaultValuesStr()` returns array of canonized string default
values.
Change-Id: Idc42cd877f1fd3d717d491d09c46b59492527bff
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/libyang-cpp/SchemaNode.hpp | 1 +
src/SchemaNode.cpp | 17 +++++++++++++++++
tests/context.cpp | 1 +
tests/example_schema.hpp | 8 ++++++++
tests/schema_node.cpp | 7 +++++++
5 files changed, 34 insertions(+)
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
index 83afc06..8ddf9be 100644
--- a/include/libyang-cpp/SchemaNode.hpp
+++ b/include/libyang-cpp/SchemaNode.hpp
@@ -169,6 +169,7 @@ class LIBYANG_CPP_EXPORT LeafList : public SchemaNode {
public:
bool isMandatory() const;
types::Type valueType() const;
+ std::vector<std::string> defaultValuesStr() const;
libyang::types::constraints::ListSize maxElements() const;
libyang::types::constraints::ListSize minElements() const;
std::optional<std::string> units() const;
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
index 9934cea..95bc09b 100644
--- a/src/SchemaNode.cpp
+++ b/src/SchemaNode.cpp
@@ -472,6 +472,23 @@ types::Type LeafList::valueType() const
return types::Type{reinterpret_cast<const lysc_node_leaflist*>(m_node)->type, typeParsed, m_ctx};
}
+/**
+ * @brief Retrieves the default string values for this leaf-list.
+ *
+ * @return The default values, or an empty vector if the leaf-list does not have default values.
+ *
+ * Wraps `lysc_node_leaflist::dflts`.
+ */
+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));
+ }
+ return res;
+}
+
/**
* @brief Retrieves the units for this leaf.
* @return The units, or std::nullopt if no units are available.
diff --git a/tests/context.cpp b/tests/context.cpp
index 11019eb..902faf6 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -733,6 +733,7 @@ TEST_CASE("context")
+--rw iid-valid? instance-identifier
+--rw iid-relaxed? instance-identifier
+--rw leafListBasic* string
+ +--rw leafListWithDefault* int32
+--rw leafListWithMinMaxElements* int32
+--rw leafListWithUnits* int32
+--rw listBasic* [primary-key]
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
index c093f50..2861b1a 100644
--- a/tests/example_schema.hpp
+++ b/tests/example_schema.hpp
@@ -525,6 +525,14 @@ module type_module {
ordered-by user;
}
+ leaf-list leafListWithDefault {
+ type int32;
+ default -1;
+ default +512;
+ default 0x400;
+ default 04000;
+ }
+
leaf-list leafListWithMinMaxElements {
type int32;
min-elements 1;
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
index a86900d..80c7407 100644
--- a/tests/schema_node.cpp
+++ b/tests/schema_node.cpp
@@ -200,6 +200,7 @@ TEST_CASE("SchemaNode")
"/type_module:iid-valid",
"/type_module:iid-relaxed",
"/type_module:leafListBasic",
+ "/type_module:leafListWithDefault",
"/type_module:leafListWithMinMaxElements",
"/type_module:leafListWithUnits",
"/type_module:listBasic",
@@ -606,6 +607,12 @@ TEST_CASE("SchemaNode")
REQUIRE(!ctx->findPath("/type_module:leafListBasic").asLeafList().isMandatory());
}
+ 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:leafListBasic").asLeafList().defaultValuesStr().size() == 0);
+ }
+
DOCTEST_SUBCASE("LeafList::maxElements")
{
REQUIRE(ctx->findPath("/type_module:leafListWithMinMaxElements").asLeafList().maxElements() == 5);
--
2.43.0
@@ -0,0 +1,434 @@
From 01f2633cef60495d5cafc4b4b1f25273b03ab3cd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
Date: Tue, 22 Oct 2024 15:11:30 +0200
Subject: [PATCH 6/8] schema: Make choice and case statements available
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
Make choice and case statements available so that they can be accessed
if end-user requires reading schema nodes.
By design, choice and case statements do not exist in data tree directly.
Only children of one case can be present in the data tree at one time.
That means that choice and case children are not instantiable, thus
`SchemaNode::immediateChildren` must be used (instead of
`SchemaNode::childInstantibles`) if end-user wants to access choice
and case substatements.
Change-Id: Ib089672ad21dda8a0344895835d92d3432fcccb8
Co-authored-by: Jan Kundrát <jan.kundrat@cesnet.cz>
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/libyang-cpp/SchemaNode.hpp | 34 +++++++++++++
src/SchemaNode.cpp | 68 ++++++++++++++++++++++++++
tests/context.cpp | 77 +++++++++++++++++++-----------
tests/example_schema.hpp | 60 +++++++++++++++++++++++
tests/schema_node.cpp | 72 ++++++++++++++++++++++++++++
5 files changed, 284 insertions(+), 27 deletions(-)
diff --git a/include/libyang-cpp/SchemaNode.hpp b/include/libyang-cpp/SchemaNode.hpp
index 8ddf9be..0f1a4c4 100644
--- a/include/libyang-cpp/SchemaNode.hpp
+++ b/include/libyang-cpp/SchemaNode.hpp
@@ -22,6 +22,8 @@ class AnyDataAnyXML;
class ActionRpc;
class ActionRpcInput;
class ActionRpcOutput;
+class Case;
+class Choice;
class Container;
class Leaf;
class LeafList;
@@ -62,6 +64,8 @@ public:
// drectly by the user.
// TODO: turn these into a templated `as<>` method.
AnyDataAnyXML asAnyDataAnyXML() const;
+ Case asCase() const;
+ Choice asChoice() const;
Container asContainer() const;
Leaf asLeaf() const;
LeafList asLeafList() const;
@@ -129,6 +133,36 @@ private:
using SchemaNode::SchemaNode;
};
+/**
+ * @brief Class representing a schema definition of a `case` node.
+ *
+ * Wraps `lysc_node_case`.
+ */
+class LIBYANG_CPP_EXPORT Case : public SchemaNode {
+public:
+ friend SchemaNode;
+ friend Choice;
+
+private:
+ using SchemaNode::SchemaNode;
+};
+
+/**
+ * @brief Class representing a schema definition of a `choice` node.
+ *
+ * Wraps `lysc_node_choice`.
+ */
+class LIBYANG_CPP_EXPORT Choice : public SchemaNode {
+public:
+ bool isMandatory() const;
+ std::vector<Case> cases() const;
+ std::optional<Case> defaultCase() const;
+ friend SchemaNode;
+
+private:
+ using SchemaNode::SchemaNode;
+};
+
/**
* @brief Class representing a schema definition of a `container` node.
*/
diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp
index bd20402..26b5099 100644
--- a/src/SchemaNode.cpp
+++ b/src/SchemaNode.cpp
@@ -191,6 +191,32 @@ NodeType SchemaNode::nodeType() const
return utils::toNodeType(m_node->nodetype);
}
+/**
+ * @brief Try to cast this SchemaNode to a Case node.
+ * @throws Error If this node is not a case.
+ */
+Case SchemaNode::asCase() const
+{
+ if (nodeType() != NodeType::Case) {
+ throw Error("Schema node is not a case: " + path());
+ }
+
+ return Case{m_node, m_ctx};
+}
+
+/**
+ * @brief Try to cast this SchemaNode to a Choice node.
+ * @throws Error If this node is not a choice.
+ */
+Choice SchemaNode::asChoice() const
+{
+ if (nodeType() != NodeType::Choice) {
+ throw Error("Schema node is not a choice: " + path());
+ }
+
+ return Choice{m_node, m_ctx};
+}
+
/**
* @brief Try to cast this SchemaNode to a Container node.
* @throws Error If this node is not a container.
@@ -401,6 +427,48 @@ bool AnyDataAnyXML::isMandatory() const
return m_node->flags & LYS_MAND_TRUE;
}
+/**
+ * @brief Checks whether this choice is mandatory.
+ *
+ * Wraps flag `LYS_MAND_TRUE`.
+ */
+bool Choice::isMandatory() const
+{
+ return m_node->flags & LYS_MAND_TRUE;
+}
+
+/**
+ * @brief Retrieves the list of cases for this choice.
+ *
+ * Wraps `lysc_node_choice::cases`.
+ */
+std::vector<Case> Choice::cases() const
+{
+ auto choice = reinterpret_cast<const lysc_node_choice*>(m_node);
+ auto cases = reinterpret_cast<lysc_node*>(choice->cases);
+ std::vector<Case> res;
+ lysc_node* elem;
+ LY_LIST_FOR(cases, elem)
+ {
+ res.emplace_back(Case(elem, m_ctx));
+ }
+ return res;
+}
+
+/**
+ * @brief Retrieves the default case for this choice.
+ *
+ * Wraps `lysc_node_choice::dflt`.
+ */
+std::optional<Case> Choice::defaultCase() const
+{
+ auto choice = reinterpret_cast<const lysc_node_choice*>(m_node);
+ if (!choice->dflt) {
+ return std::nullopt;
+ }
+ return Case{reinterpret_cast<lysc_node*>(choice->dflt), m_ctx};
+}
+
/**
* @brief Checks whether this container is mandatory.
*
diff --git a/tests/context.cpp b/tests/context.cpp
index 902faf6..5929b75 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -709,33 +709,56 @@ TEST_CASE("context")
auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG);
REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module
- +--rw anydataBasic? anydata
- +--rw anydataWithMandatoryChild anydata
- +--rw anyxmlBasic? anyxml
- +--rw anyxmlWithMandatoryChild anyxml
- +--rw leafBinary? binary
- +--rw leafBits? bits
- +--rw leafEnum? enumeration
- +--rw leafEnum2? enumeration
- +--rw leafNumber? int32
- +--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol
- +--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol
- +--rw leafString? string
- +--rw leafUnion? union
- +--rw meal? identityref
- +--ro leafWithConfigFalse? string
- +--rw leafWithDefaultValue? string
- +--rw leafWithDescription? string
- +--rw leafWithMandatoryTrue string
- x--rw leafWithStatusDeprecated? string
- o--rw leafWithStatusObsolete? string
- +--rw leafWithUnits? int32
- +--rw iid-valid? instance-identifier
- +--rw iid-relaxed? instance-identifier
- +--rw leafListBasic* string
- +--rw leafListWithDefault* int32
- +--rw leafListWithMinMaxElements* int32
- +--rw leafListWithUnits* int32
+ +--rw anydataBasic? anydata
+ +--rw anydataWithMandatoryChild anydata
+ +--rw anyxmlBasic? anyxml
+ +--rw anyxmlWithMandatoryChild anyxml
+ +--rw choiceBasicContainer
+ | +--rw (choiceBasic)?
+ | +--:(case1)
+ | | +--rw l? string
+ | | +--rw ll* string
+ | +--:(case2)
+ | +--rw l2? string
+ +--rw choiceWithMandatoryContainer
+ | +--rw (choiceWithMandatory)
+ | +--:(case3)
+ | | +--rw l3? string
+ | +--:(case4)
+ | +--rw l4? string
+ +--rw choiceWithDefaultContainer
+ | +--rw (choiceWithDefault)?
+ | +--:(case5)
+ | | +--rw l5? string
+ | +--:(case6)
+ | +--rw l6? string
+ +--rw implicitCaseContainer
+ | +--rw (implicitCase)?
+ | +--:(implicitLeaf)
+ | +--rw implicitLeaf? string
+ +--rw leafBinary? binary
+ +--rw leafBits? bits
+ +--rw leafEnum? enumeration
+ +--rw leafEnum2? enumeration
+ +--rw leafNumber? int32
+ +--rw leafRef? -> /custom-prefix:listAdvancedWithOneKey/lol
+ +--rw leafRefRelaxed? -> /custom-prefix:listAdvancedWithOneKey/lol
+ +--rw leafString? string
+ +--rw leafUnion? union
+ +--rw meal? identityref
+ +--ro leafWithConfigFalse? string
+ +--rw leafWithDefaultValue? string
+ +--rw leafWithDescription? string
+ +--rw leafWithMandatoryTrue string
+ x--rw leafWithStatusDeprecated? string
+ o--rw leafWithStatusObsolete? string
+ +--rw leafWithUnits? int32
+ +--rw iid-valid? instance-identifier
+ +--rw iid-relaxed? instance-identifier
+ +--rw leafListBasic* string
+ +--rw leafListWithDefault* int32
+ +--rw leafListWithMinMaxElements* int32
+ +--rw leafListWithUnits* int32
+--rw listBasic* [primary-key]
| +--rw primary-key string
+--rw listAdvancedWithOneKey* [lol]
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
index 2861b1a..ae3b4de 100644
--- a/tests/example_schema.hpp
+++ b/tests/example_schema.hpp
@@ -390,6 +390,66 @@ module type_module {
mandatory true;
}
+ container choiceBasicContainer {
+ choice choiceBasic {
+ case case1 {
+ leaf l {
+ type string;
+ }
+ leaf-list ll {
+ type string;
+ ordered-by user;
+ }
+ }
+ case case2 {
+ leaf l2 {
+ type string;
+ }
+ }
+ }
+ }
+
+ container choiceWithMandatoryContainer {
+ choice choiceWithMandatory {
+ mandatory true;
+ case case3 {
+ leaf l3 {
+ type string;
+ }
+ }
+ case case4 {
+ leaf l4 {
+ type string;
+ }
+ }
+ }
+ }
+
+ container choiceWithDefaultContainer {
+ choice choiceWithDefault {
+ default case5;
+ case case5 {
+ leaf l5 {
+ type string;
+ }
+ }
+ case case6 {
+ leaf l6 {
+ type string;
+ }
+ }
+ }
+ }
+
+ container implicitCaseContainer {
+ choice implicitCase {
+ leaf implicitLeaf {
+ type string;
+ }
+ }
+ }
+
+
leaf leafBinary {
type binary;
}
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
index 80c7407..8d74bd2 100644
--- a/tests/schema_node.cpp
+++ b/tests/schema_node.cpp
@@ -58,6 +58,9 @@ TEST_CASE("SchemaNode")
],
"type_module:anydataWithMandatoryChild": {"content": "test-string"},
"type_module:anyxmlWithMandatoryChild": {"content": "test-string"},
+ "type_module:choiceWithMandatoryContainer": {
+ "l4": "test-string"
+ },
"type_module:containerWithMandatoryChild": {
"leafWithMandatoryTrue": "test-string"
},
@@ -180,6 +183,10 @@ TEST_CASE("SchemaNode")
"/type_module:anydataWithMandatoryChild",
"/type_module:anyxmlBasic",
"/type_module:anyxmlWithMandatoryChild",
+ "/type_module:choiceBasicContainer",
+ "/type_module:choiceWithMandatoryContainer",
+ "/type_module:choiceWithDefaultContainer",
+ "/type_module:implicitCaseContainer",
"/type_module:leafBinary",
"/type_module:leafBits",
"/type_module:leafEnum",
@@ -417,6 +424,71 @@ TEST_CASE("SchemaNode")
REQUIRE(!ctx->findPath("/type_module:anyxmlBasic").asAnyDataAnyXML().isMandatory());
}
+ DOCTEST_SUBCASE("Choice and Case")
+ {
+ std::string xpath;
+ bool isMandatory = false;
+ std::optional<std::string> defaultCase;
+ std::vector<std::string> caseNames;
+ std::optional<libyang::SchemaNode> root;
+
+ DOCTEST_SUBCASE("two cases with nothing fancy")
+ {
+ root = ctx->findPath("/type_module:choiceBasicContainer");
+ caseNames = {"case1", "case2"};
+ }
+
+ DOCTEST_SUBCASE("mandatory choice") {
+ root = ctx->findPath("/type_module:choiceWithMandatoryContainer");
+ isMandatory = true;
+ caseNames = {"case3", "case4"};
+ }
+
+ DOCTEST_SUBCASE("default choice") {
+ root = ctx->findPath("/type_module:choiceWithDefaultContainer");
+ defaultCase = "case5";
+ caseNames = {"case5", "case6"};
+ }
+
+ DOCTEST_SUBCASE("implicit case") {
+ root = ctx->findPath("/type_module:implicitCaseContainer");
+ caseNames = {"implicitLeaf"};
+ }
+
+ // For testing purposes, we have each choice in its own container. As choice and case are not directly instantiable,
+ // we wrap them in a container to simplify the testing process. It allows us to simply address the choice by its
+ // container and then get the choice from it. It also prevents polluting the test schema with unnecessary nodes
+ // and isolates the choice from other nodes.
+ auto container = root->asContainer();
+ auto choice = container.immediateChildren().begin()->asChoice();
+ REQUIRE(choice.isMandatory() == isMandatory);
+ REQUIRE(!!choice.defaultCase() == !!defaultCase);
+ if (defaultCase) {
+ REQUIRE(choice.defaultCase()->name() == *defaultCase);
+ }
+ std::vector<std::string> actualCaseNames;
+ for (const auto& case_ : choice.cases()) {
+ actualCaseNames.push_back(case_.name());
+ }
+ REQUIRE(actualCaseNames == caseNames);
+
+ // Also test child node access for one arbitrary choice/case combination
+ if (root->path() == "/type_module:choiceBasicContainer") {
+ REQUIRE(choice.cases().size() == 2);
+ auto case1 = choice.cases()[0];
+ auto children = case1.immediateChildren();
+ auto it = children.begin();
+ REQUIRE(it->asLeaf().name() == "l");
+ ++it;
+ REQUIRE(it->asLeafList().name() == "ll");
+
+ auto case2 = choice.cases()[1];
+ children = case2.immediateChildren();
+ it = children.begin();
+ REQUIRE(it->asLeaf().name() == "l2");
+ }
+ }
+
DOCTEST_SUBCASE("Container::isMandatory")
{
REQUIRE(ctx->findPath("/type_module:containerWithMandatoryChild").asContainer().isMandatory());
--
2.43.0
@@ -0,0 +1,129 @@
From a1acdc794facf8cbf113f73274ecebd5898c81a1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
Date: Tue, 17 Dec 2024 15:08:43 +0100
Subject: [PATCH 7/8] Wrap lyd_change_term for changing the value for a
terminal node
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
Previously, the code would require a newPath(...,
libyang::CreationOptions::Update), which is quite a mouthful.
Change-Id: I8a908c0fdd3e48dda830819758522a511adedd3b
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
include/libyang-cpp/DataNode.hpp | 8 ++++++
src/DataNode.cpp | 21 ++++++++++++++++
tests/data_node.cpp | 42 ++++++++++++++++++++++++++------
3 files changed, 63 insertions(+), 8 deletions(-)
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
index 2211415..851681b 100644
--- a/include/libyang-cpp/DataNode.hpp
+++ b/include/libyang-cpp/DataNode.hpp
@@ -212,6 +212,14 @@ public:
Value value() const;
types::Type valueType() const;
+ /** @brief Was the value changed? */
+ enum class ValueChange {
+ Changed, /**< Yes, this is an actual change of the stored value */
+ ExplicitNonDefault, /**< It still holds the default value, but it's been set explicitly now */
+ EqualValueNotChanged, /**< No change, the previous value is the same as the new one, and it isn't an implicit default */
+ };
+ ValueChange changeValue(const std::string value);
+
private:
using DataNode::DataNode;
};
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
index 2ef17f2..84591e5 100644
--- a/src/DataNode.cpp
+++ b/src/DataNode.cpp
@@ -903,6 +903,27 @@ types::Type DataNodeTerm::valueType() const
return impl(reinterpret_cast<const lyd_node_term*>(m_node)->value);
}
+/** @short Change the term's value
+ *
+ * Wraps `lyd_change_term`.
+ * */
+DataNodeTerm::ValueChange DataNodeTerm::changeValue(const std::string value)
+{
+ auto ret = lyd_change_term(m_node, value.c_str());
+
+ switch (ret) {
+ case LY_SUCCESS:
+ return ValueChange::Changed;
+ case LY_EEXIST:
+ return ValueChange::ExplicitNonDefault;
+ case LY_ENOT:
+ return ValueChange::EqualValueNotChanged;
+ default:
+ throwIfError(ret, "DataNodeTerm::changeValue failed");
+ __builtin_unreachable();
+ }
+}
+
/**
* @brief Returns a collection for iterating depth-first over the subtree this instance points to.
*
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
index 8a2610e..45fd6c1 100644
--- a/tests/data_node.cpp
+++ b/tests/data_node.cpp
@@ -456,15 +456,41 @@ TEST_CASE("Data Node manipulation")
REQUIRE(node.hasDefaultValue());
REQUIRE(node.isImplicitDefault());
- data->newPath("/example-schema3:leafWithDefault", "not-default-value", libyang::CreationOptions::Update);
- node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
- REQUIRE(!node.hasDefaultValue());
- REQUIRE(!node.isImplicitDefault());
+ DOCTEST_SUBCASE("newPath")
+ {
+ data->newPath("/example-schema3:leafWithDefault", "not-default-value", libyang::CreationOptions::Update);
+ node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
+ REQUIRE(!node.hasDefaultValue());
+ REQUIRE(!node.isImplicitDefault());
- data->newPath("/example-schema3:leafWithDefault", "AHOJ", libyang::CreationOptions::Update);
- node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
- REQUIRE(node.hasDefaultValue());
- REQUIRE(!node.isImplicitDefault());
+ data->newPath("/example-schema3:leafWithDefault", "AHOJ", libyang::CreationOptions::Update);
+ node = data->findPath("/example-schema3:leafWithDefault")->asTerm();
+ REQUIRE(node.hasDefaultValue());
+ REQUIRE(!node.isImplicitDefault());
+ }
+
+ DOCTEST_SUBCASE("changing values")
+ {
+ auto node = data->findPath("/example-schema3:leafWithDefault");
+ REQUIRE(!!node);
+ auto term = node->asTerm();
+
+ DOCTEST_SUBCASE("to an arbitrary value") {
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed);
+ }
+
+ DOCTEST_SUBCASE("from an implicit default to an explicit default") {
+ REQUIRE(term.changeValue("AHOJ") == libyang::DataNodeTerm::ValueChange::ExplicitNonDefault);
+ REQUIRE(term.changeValue("AHOJ") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged);
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed);
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged);
+ }
+
+ DOCTEST_SUBCASE("from an implicit default to something else") {
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::Changed);
+ REQUIRE(term.changeValue("cau") == libyang::DataNodeTerm::ValueChange::EqualValueNotChanged);
+ }
+ }
}
DOCTEST_SUBCASE("isTerm")
--
2.43.0
@@ -0,0 +1,46 @@
From 39c7530caa510144c17521278b721ba1e6d8ff40 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
Date: Thu, 9 Jan 2025 15:31:37 +0100
Subject: [PATCH 8/8] upstream stopped reporting schema-mounts node
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Organization: Addiva Elektronik
Change-Id: I940769d38d56fcfda3e1408c92331fdb00c161e9
Signed-off-by: Mattias Walström <lazzer@gmail.com>
---
CMakeLists.txt | 2 +-
tests/context.cpp | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d86809..732f52b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -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.4.2 IMPORTED_TARGET)
+pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET)
set(LIBYANG_CPP_PKG_VERSION "3")
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
diff --git a/tests/context.cpp b/tests/context.cpp
index 5929b75..9d38fea 100644
--- a/tests/context.cpp
+++ b/tests/context.cpp
@@ -509,8 +509,7 @@ TEST_CASE("context")
"error-message": "hi"
}
]
- },
- "ietf-yang-schema-mount:schema-mounts": {}
+ }
}
)");
}
--
2.43.0