mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Bump sysrepo, netopeer,libyang and libnetconf2, libyang-cpp, sysrepo-cpp, rousette
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
From 22b41e7ed8268b94ccc139138e2037c390a3a616 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Thu, 10 Jul 2025 15:00:45 +0200
|
||||
Subject: [PATCH] Implement `SchemaNode::actionRpcs()`
|
||||
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,82 +0,0 @@
|
||||
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 01/18] added support for RpcYang in Context::parseOp
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
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
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
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 02/18] throw when lyd_validate_all returns error
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
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
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
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 03/18] remove a misleading comment
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
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
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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 04/18] 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: Wires
|
||||
|
||||
`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
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
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 05/18] schema: make leaf-list's `default` statement available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
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
|
||||
|
||||
@@ -1,434 +0,0 @@
|
||||
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 06/18] schema: Make choice and case statements available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
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
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
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 07/18] 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: Wires
|
||||
|
||||
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
|
||||
|
||||
@@ -1,652 +0,0 @@
|
||||
From 32b200ed06e9adb44a8d4ce6771f18812a54d06e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Wed, 20 Nov 2024 10:20:19 +0100
|
||||
Subject: [PATCH 08/18] Add `Module::child()`, `Module::childrenDfs()` and
|
||||
`Module::immediateChildren()`
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Those functions are implemented in the same manner as in `SchemaNode`
|
||||
and allows to walk through modules children. This is counterpart to
|
||||
already implemented `Module::childInstantiables()` that returns
|
||||
instantiables schema nodes. These return all nodes, including
|
||||
the schema-only nodes such as choice and case if end-user needs
|
||||
to read its schema.
|
||||
|
||||
While the implementation is inspired by functions in `SchemaNode`,
|
||||
imlementation of `Module::parent()` and `Module::siblings()` was omitted
|
||||
as those do no make sense on `Module`.
|
||||
|
||||
Change-Id: I38c8374304f859d65343d04d08302e07deb05f27
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/Collection.hpp | 1 +
|
||||
include/libyang-cpp/Module.hpp | 5 +
|
||||
src/Module.cpp | 40 +++
|
||||
tests/context.cpp | 5 +
|
||||
tests/example_schema.hpp | 21 ++
|
||||
tests/schema_node.cpp | 409 +++++++++++++++++++----------
|
||||
6 files changed, 346 insertions(+), 135 deletions(-)
|
||||
|
||||
diff --git a/include/libyang-cpp/Collection.hpp b/include/libyang-cpp/Collection.hpp
|
||||
index 557a0a2..4324791 100644
|
||||
--- a/include/libyang-cpp/Collection.hpp
|
||||
+++ b/include/libyang-cpp/Collection.hpp
|
||||
@@ -98,6 +98,7 @@ class LIBYANG_CPP_EXPORT Collection {
|
||||
public:
|
||||
friend DataNode;
|
||||
friend Iterator<NodeType, ITER_TYPE>;
|
||||
+ friend Module;
|
||||
friend SchemaNode;
|
||||
~Collection();
|
||||
Collection(const Collection<NodeType, ITER_TYPE>&);
|
||||
diff --git a/include/libyang-cpp/Module.hpp b/include/libyang-cpp/Module.hpp
|
||||
index f10c36f..ab20d36 100644
|
||||
--- a/include/libyang-cpp/Module.hpp
|
||||
+++ b/include/libyang-cpp/Module.hpp
|
||||
@@ -34,6 +34,8 @@ class ChildInstanstiables;
|
||||
class Identity;
|
||||
class SchemaNode;
|
||||
class SubmoduleParsed;
|
||||
+template <typename NodeType, IterationType ITER_TYPE>
|
||||
+class Collection;
|
||||
|
||||
namespace types {
|
||||
class IdentityRef;
|
||||
@@ -86,7 +88,10 @@ public:
|
||||
|
||||
std::vector<Identity> identities() const;
|
||||
|
||||
+ std::optional<SchemaNode> child() const;
|
||||
ChildInstanstiables childInstantiables() const;
|
||||
+ libyang::Collection<SchemaNode, IterationType::Dfs> childrenDfs() const;
|
||||
+ Collection<SchemaNode, IterationType::Sibling> immediateChildren() const;
|
||||
std::vector<SchemaNode> actionRpcs() const;
|
||||
|
||||
std::string printStr(const SchemaOutputFormat format, const std::optional<SchemaPrintFlags> flags = std::nullopt, std::optional<size_t> lineLength = std::nullopt) const;
|
||||
diff --git a/src/Module.cpp b/src/Module.cpp
|
||||
index 4dc9e3b..d6d4023 100644
|
||||
--- a/src/Module.cpp
|
||||
+++ b/src/Module.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <libyang-cpp/ChildInstantiables.hpp>
|
||||
+#include <libyang-cpp/Collection.hpp>
|
||||
#include <libyang-cpp/Module.hpp>
|
||||
#include <libyang-cpp/Utils.hpp>
|
||||
#include <libyang/libyang.h>
|
||||
@@ -178,6 +179,23 @@ std::vector<Identity> Module::identities() const
|
||||
return res;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Returns the first child node of this module.
|
||||
+ * @return The child, or std::nullopt if there are no children.
|
||||
+ */
|
||||
+std::optional<SchemaNode> Module::child() const
|
||||
+{
|
||||
+ if (!m_module->implemented) {
|
||||
+ throw Error{"Module::child: module is not implemented"};
|
||||
+ }
|
||||
+
|
||||
+ if (!m_module->compiled->data) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+
|
||||
+ return SchemaNode{m_module->compiled->data, m_ctx};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Returns a collection of data instantiable top-level nodes of this module.
|
||||
*
|
||||
@@ -191,6 +209,28 @@ ChildInstanstiables Module::childInstantiables() const
|
||||
return ChildInstanstiables{nullptr, m_module->compiled, m_ctx};
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Returns a collection for iterating depth-first over the subtree this module points to.
|
||||
+ */
|
||||
+Collection<SchemaNode, IterationType::Dfs> Module::childrenDfs() const
|
||||
+{
|
||||
+ if (!m_module->implemented) {
|
||||
+ throw Error{"Module::childrenDfs: module is not implemented"};
|
||||
+ }
|
||||
+ return Collection<SchemaNode, IterationType::Dfs>{m_module->compiled->data, m_ctx};
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Returns a collection for iterating over the immediate children of where this module points to.
|
||||
+ *
|
||||
+ * This is a convenience function for iterating over this->child().siblings() which does not throw even when module has no children.
|
||||
+ */
|
||||
+Collection<SchemaNode, IterationType::Sibling> Module::immediateChildren() const
|
||||
+{
|
||||
+ auto c = child();
|
||||
+ return c ? c->siblings() : Collection<SchemaNode, IterationType::Sibling>{nullptr, nullptr};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Returns a collection of RPC nodes (not action nodes) as SchemaNode
|
||||
*
|
||||
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||
index 5929b75..25343db 100644
|
||||
--- a/tests/context.cpp
|
||||
+++ b/tests/context.cpp
|
||||
@@ -713,6 +713,11 @@ TEST_CASE("context")
|
||||
+--rw anydataWithMandatoryChild anydata
|
||||
+--rw anyxmlBasic? anyxml
|
||||
+--rw anyxmlWithMandatoryChild anyxml
|
||||
+ +--rw (choiceOnModule)?
|
||||
+ | +--:(case1)
|
||||
+ | | +--rw choiceOnModuleLeaf1? string
|
||||
+ | +--:(case2)
|
||||
+ | +--rw choiceOnModuleLeaf2? string
|
||||
+--rw choiceBasicContainer
|
||||
| +--rw (choiceBasic)?
|
||||
| +--:(case1)
|
||||
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
|
||||
index ae3b4de..0d8acb9 100644
|
||||
--- a/tests/example_schema.hpp
|
||||
+++ b/tests/example_schema.hpp
|
||||
@@ -390,6 +390,19 @@ module type_module {
|
||||
mandatory true;
|
||||
}
|
||||
|
||||
+ choice choiceOnModule {
|
||||
+ case case1 {
|
||||
+ leaf choiceOnModuleLeaf1 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ case case2 {
|
||||
+ leaf choiceOnModuleLeaf2 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
container choiceBasicContainer {
|
||||
choice choiceBasic {
|
||||
case case1 {
|
||||
@@ -787,6 +800,14 @@ module type_module {
|
||||
}
|
||||
)"s;
|
||||
|
||||
+const auto empty_module = R"(
|
||||
+module empty_module {
|
||||
+ yang-version 1.1;
|
||||
+ namespace "e";
|
||||
+ prefix "e";
|
||||
+}
|
||||
+)"s;
|
||||
+
|
||||
const auto with_inet_types_module = R"(
|
||||
module with-inet-types {
|
||||
yang-version 1.1;
|
||||
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||
index 8d74bd2..0001377 100644
|
||||
--- a/tests/schema_node.cpp
|
||||
+++ b/tests/schema_node.cpp
|
||||
@@ -24,8 +24,10 @@ TEST_CASE("SchemaNode")
|
||||
libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd};
|
||||
ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
ctx->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
+ ctx->parseModule(empty_module, libyang::SchemaFormat::YANG);
|
||||
ctxWithParsed->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
ctxWithParsed->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
+ ctxWithParsed->parseModule(empty_module, libyang::SchemaFormat::YANG);
|
||||
|
||||
DOCTEST_SUBCASE("context lifetime")
|
||||
{
|
||||
@@ -74,10 +76,34 @@ TEST_CASE("SchemaNode")
|
||||
REQUIRE(node->schema().path() == "/example-schema:person");
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::child")
|
||||
+ DOCTEST_SUBCASE("child")
|
||||
{
|
||||
- REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first");
|
||||
- REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value());
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("SchemaNode::child")
|
||||
+ {
|
||||
+ REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first");
|
||||
+ REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value());
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Module::child")
|
||||
+ {
|
||||
+ REQUIRE(ctx->getModule("type_module", std::nullopt)->child()->name() == "anydataBasic");
|
||||
+ REQUIRE(!ctx->getModule("empty_module", std::nullopt)->child());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::child")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->child(), "Module::child: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("SchemaNode::config")
|
||||
@@ -160,162 +186,275 @@ TEST_CASE("SchemaNode")
|
||||
|
||||
DOCTEST_SUBCASE("childInstantiables")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
- std::optional<libyang::ChildInstanstiables> children;
|
||||
-
|
||||
- DOCTEST_SUBCASE("SchemaNode::childInstantiables")
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithOneKey/lol",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey1",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey2",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey3",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey4",
|
||||
- };
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::ChildInstanstiables> children;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("SchemaNode::childInstantiables")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithOneKey/lol",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey1",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey2",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey3",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey4",
|
||||
+ };
|
||||
+
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables();
|
||||
+ }
|
||||
|
||||
- children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables();
|
||||
- }
|
||||
+ DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ "/type_module:anydataWithMandatoryChild",
|
||||
+ "/type_module:anyxmlBasic",
|
||||
+ "/type_module:anyxmlWithMandatoryChild",
|
||||
+ "/type_module:choiceOnModuleLeaf1",
|
||||
+ "/type_module:choiceOnModuleLeaf2",
|
||||
+ "/type_module:choiceBasicContainer",
|
||||
+ "/type_module:choiceWithMandatoryContainer",
|
||||
+ "/type_module:choiceWithDefaultContainer",
|
||||
+ "/type_module:implicitCaseContainer",
|
||||
+ "/type_module:leafBinary",
|
||||
+ "/type_module:leafBits",
|
||||
+ "/type_module:leafEnum",
|
||||
+ "/type_module:leafEnum2",
|
||||
+ "/type_module:leafNumber",
|
||||
+ "/type_module:leafRef",
|
||||
+ "/type_module:leafRefRelaxed",
|
||||
+ "/type_module:leafString",
|
||||
+ "/type_module:leafUnion",
|
||||
+ "/type_module:meal",
|
||||
+ "/type_module:leafWithConfigFalse",
|
||||
+ "/type_module:leafWithDefaultValue",
|
||||
+ "/type_module:leafWithDescription",
|
||||
+ "/type_module:leafWithMandatoryTrue",
|
||||
+ "/type_module:leafWithStatusDeprecated",
|
||||
+ "/type_module:leafWithStatusObsolete",
|
||||
+ "/type_module:leafWithUnits",
|
||||
+ "/type_module:iid-valid",
|
||||
+ "/type_module:iid-relaxed",
|
||||
+ "/type_module:leafListBasic",
|
||||
+ "/type_module:leafListWithDefault",
|
||||
+ "/type_module:leafListWithMinMaxElements",
|
||||
+ "/type_module:leafListWithUnits",
|
||||
+ "/type_module:listBasic",
|
||||
+ "/type_module:listAdvancedWithOneKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listWithMinMaxElements",
|
||||
+ "/type_module:numeric",
|
||||
+ "/type_module:container",
|
||||
+ "/type_module:containerWithMandatoryChild",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->childInstantiables();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:anydataBasic",
|
||||
- "/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",
|
||||
- "/type_module:leafEnum2",
|
||||
- "/type_module:leafNumber",
|
||||
- "/type_module:leafRef",
|
||||
- "/type_module:leafRefRelaxed",
|
||||
- "/type_module:leafString",
|
||||
- "/type_module:leafUnion",
|
||||
- "/type_module:meal",
|
||||
- "/type_module:leafWithConfigFalse",
|
||||
- "/type_module:leafWithDefaultValue",
|
||||
- "/type_module:leafWithDescription",
|
||||
- "/type_module:leafWithMandatoryTrue",
|
||||
- "/type_module:leafWithStatusDeprecated",
|
||||
- "/type_module:leafWithStatusObsolete",
|
||||
- "/type_module:leafWithUnits",
|
||||
- "/type_module:iid-valid",
|
||||
- "/type_module:iid-relaxed",
|
||||
- "/type_module:leafListBasic",
|
||||
- "/type_module:leafListWithDefault",
|
||||
- "/type_module:leafListWithMinMaxElements",
|
||||
- "/type_module:leafListWithUnits",
|
||||
- "/type_module:listBasic",
|
||||
- "/type_module:listAdvancedWithOneKey",
|
||||
- "/type_module:listAdvancedWithTwoKey",
|
||||
- "/type_module:listWithMinMaxElements",
|
||||
- "/type_module:numeric",
|
||||
- "/type_module:container",
|
||||
- "/type_module:containerWithMandatoryChild",
|
||||
- };
|
||||
- children = ctx->getModule("type_module", std::nullopt)->childInstantiables();
|
||||
- }
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& child : *children) {
|
||||
+ actualPaths.emplace_back(child.path());
|
||||
+ }
|
||||
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& child : *children) {
|
||||
- actualPaths.emplace_back(child.path());
|
||||
+ REQUIRE(expectedPaths == actualPaths);
|
||||
}
|
||||
|
||||
- REQUIRE(expectedPaths == actualPaths);
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->childInstantiables(), "Module::childInstantiables: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::childrenDfs")
|
||||
+ DOCTEST_SUBCASE("childrenDfs")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
+ {
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::Collection<libyang::SchemaNode, libyang::IterationType::Dfs>> children;
|
||||
|
||||
- const char* path;
|
||||
+ DOCTEST_SUBCASE("SchemaNode::childrenDfs")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey/first",
|
||||
+ "/type_module:listAdvancedWithTwoKey/second",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").childrenDfs();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithTwoKey",
|
||||
- "/type_module:listAdvancedWithTwoKey/first",
|
||||
- "/type_module:listAdvancedWithTwoKey/second",
|
||||
- };
|
||||
+ DOCTEST_SUBCASE("DFS on a leaf")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:leafString",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:leafString").childrenDfs();
|
||||
+ }
|
||||
+ }
|
||||
|
||||
- path = "/type_module:listAdvancedWithTwoKey";
|
||||
- }
|
||||
+ DOCTEST_SUBCASE("Module::childrenDfs")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->childrenDfs();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("DFS on a leaf")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:leafString",
|
||||
- };
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& it : *children) {
|
||||
+ actualPaths.emplace_back(it.path());
|
||||
+ }
|
||||
|
||||
- path = "/type_module:leafString";
|
||||
+ REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& it : ctx->findPath(path).childrenDfs()) {
|
||||
- actualPaths.emplace_back(it.path());
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::childrenDfs")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->childrenDfs(), "Module::childrenDfs: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
}
|
||||
-
|
||||
- REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::immediateChildren")
|
||||
+ DOCTEST_SUBCASE("immediateChildren")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
- const char* path;
|
||||
- DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithTwoKey/first",
|
||||
- "/type_module:listAdvancedWithTwoKey/second",
|
||||
- };
|
||||
- path = "/type_module:listAdvancedWithTwoKey";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("leaf")
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- };
|
||||
- path = "/type_module:leafString";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("no recursion")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/x",
|
||||
- "/type_module:container/y",
|
||||
- "/type_module:container/z",
|
||||
- };
|
||||
- path = "/type_module:container";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("empty container")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- };
|
||||
- path = "/type_module:container/y";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("one item")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/z/z1",
|
||||
- };
|
||||
- path = "/type_module:container/z";
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::Collection<libyang::SchemaNode, libyang::IterationType::Sibling>> children;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("SchemaNode::immediateChildren")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithTwoKey/first",
|
||||
+ "/type_module:listAdvancedWithTwoKey/second",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("leaf")
|
||||
+ {
|
||||
+ expectedPaths = {};
|
||||
+ children = ctx->findPath("/type_module:leafString").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("no recursion")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/x",
|
||||
+ "/type_module:container/y",
|
||||
+ "/type_module:container/z",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("empty container")
|
||||
+ {
|
||||
+ expectedPaths = {};
|
||||
+ children = ctx->findPath("/type_module:container/y").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("one item")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/z/z1",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container/z").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("two items")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/x/x1",
|
||||
+ "/type_module:container/x/x2",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container/x").immediateChildren();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Module::immediateChildren")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ "/type_module:anydataWithMandatoryChild",
|
||||
+ "/type_module:anyxmlBasic",
|
||||
+ "/type_module:anyxmlWithMandatoryChild",
|
||||
+ // choiceOnModule is a choice, so it doesn't have path "/type_module:choiceOnModule".
|
||||
+ // This node is tested at the end of the test subcase.
|
||||
+ "/",
|
||||
+ "/type_module:choiceBasicContainer",
|
||||
+ "/type_module:choiceWithMandatoryContainer",
|
||||
+ "/type_module:choiceWithDefaultContainer",
|
||||
+ "/type_module:implicitCaseContainer",
|
||||
+ "/type_module:leafBinary",
|
||||
+ "/type_module:leafBits",
|
||||
+ "/type_module:leafEnum",
|
||||
+ "/type_module:leafEnum2",
|
||||
+ "/type_module:leafNumber",
|
||||
+ "/type_module:leafRef",
|
||||
+ "/type_module:leafRefRelaxed",
|
||||
+ "/type_module:leafString",
|
||||
+ "/type_module:leafUnion",
|
||||
+ "/type_module:meal",
|
||||
+ "/type_module:leafWithConfigFalse",
|
||||
+ "/type_module:leafWithDefaultValue",
|
||||
+ "/type_module:leafWithDescription",
|
||||
+ "/type_module:leafWithMandatoryTrue",
|
||||
+ "/type_module:leafWithStatusDeprecated",
|
||||
+ "/type_module:leafWithStatusObsolete",
|
||||
+ "/type_module:leafWithUnits",
|
||||
+ "/type_module:iid-valid",
|
||||
+ "/type_module:iid-relaxed",
|
||||
+ "/type_module:leafListBasic",
|
||||
+ "/type_module:leafListWithDefault",
|
||||
+ "/type_module:leafListWithMinMaxElements",
|
||||
+ "/type_module:leafListWithUnits",
|
||||
+ "/type_module:listBasic",
|
||||
+ "/type_module:listAdvancedWithOneKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listWithMinMaxElements",
|
||||
+ "/type_module:numeric",
|
||||
+ "/type_module:container",
|
||||
+ "/type_module:containerWithMandatoryChild",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->immediateChildren();
|
||||
+
|
||||
+ std::vector<std::string> actualNames;
|
||||
+ for (auto it : children.value()) {
|
||||
+ actualNames.emplace_back(it.name());
|
||||
+ }
|
||||
+ // choiceOnModule is a choice, so it doesn't have path, just name.
|
||||
+ REQUIRE(actualNames[4] == "choiceOnModule");
|
||||
+ }
|
||||
+
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& it : *children) {
|
||||
+ actualPaths.emplace_back(it.path());
|
||||
+ }
|
||||
+ REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
- DOCTEST_SUBCASE("two items")
|
||||
+
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/x/x1",
|
||||
- "/type_module:container/x/x2",
|
||||
- };
|
||||
- path = "/type_module:container/x";
|
||||
- }
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& it : ctx->findPath(path).immediateChildren()) {
|
||||
- actualPaths.emplace_back(it.path());
|
||||
+ DOCTEST_SUBCASE("Module::immediateChildren")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->immediateChildren(), "Module::child: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
}
|
||||
- REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("SchemaNode::siblings")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
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 09/18] upstream stopped reporting schema-mounts node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
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
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
From 82c963766ad4e4a802db7be656acbedb640745e9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 30 Jan 2025 16:34:12 +0100
|
||||
Subject: [PATCH 10/18] CI: temporarily pin version due to anyxml behavior
|
||||
changes upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Id977f4d045098c1b93656c0efb871c9a1b650e2d
|
||||
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 b41c490..19f0def 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-01-29
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-29
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
From 50a216e35a555961f94a32a71bb2d45ac611d0aa Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 29 Jan 2025 22:49:08 +0100
|
||||
Subject: [PATCH 11/18] build: a single place to define package version
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I2cd7397895ed4852f852e99b97543dde76eaff8f
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 732f52b..c518ca8 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -21,7 +21,8 @@ add_custom_target(libyang-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-prepare_git_version(LIBYANG_CPP_VERSION "3")
|
||||
+set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
+prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${DOXYGEN_FOUND})
|
||||
@@ -29,7 +30,6 @@ option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a st
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
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
|
||||
find_package(date)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
From 1533458346b4f395b1187c646b61bbcb1fddc615 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 4 Nov 2024 14:52:12 +0100
|
||||
Subject: [PATCH 12/18] YANG-flavored regular expressions
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I93b2756d0f470585280c076308df3f384bd7765d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 3 +++
|
||||
include/libyang-cpp/Regex.hpp | 28 ++++++++++++++++++++++
|
||||
src/Regex.cpp | 45 +++++++++++++++++++++++++++++++++++
|
||||
tests/regex.cpp | 30 +++++++++++++++++++++++
|
||||
4 files changed, 106 insertions(+)
|
||||
create mode 100644 include/libyang-cpp/Regex.hpp
|
||||
create mode 100644 src/Regex.cpp
|
||||
create mode 100644 tests/regex.cpp
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c518ca8..512af8c 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -54,6 +54,7 @@ add_library(yang-cpp
|
||||
src/Enum.cpp
|
||||
src/Collection.cpp
|
||||
src/Module.cpp
|
||||
+ src/Regex.cpp
|
||||
src/SchemaNode.cpp
|
||||
src/Set.cpp
|
||||
src/Type.cpp
|
||||
@@ -83,6 +84,7 @@ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.24")
|
||||
libyang-cpp/Enum.hpp
|
||||
libyang-cpp/ChildInstantiables.hpp
|
||||
libyang-cpp/Module.hpp
|
||||
+ libyang-cpp/Regex.hpp
|
||||
libyang-cpp/Set.hpp
|
||||
libyang-cpp/SchemaNode.hpp
|
||||
libyang-cpp/Time.hpp
|
||||
@@ -119,6 +121,7 @@ if(BUILD_TESTING)
|
||||
libyang_cpp_test(schema_node)
|
||||
libyang_cpp_test(unsafe)
|
||||
target_link_libraries(test_unsafe PkgConfig::LIBYANG)
|
||||
+ libyang_cpp_test(regex)
|
||||
|
||||
if(date_FOUND)
|
||||
add_executable(test_time-stl-hhdate tests/time.cpp)
|
||||
diff --git a/include/libyang-cpp/Regex.hpp b/include/libyang-cpp/Regex.hpp
|
||||
new file mode 100644
|
||||
index 0000000..31935f2
|
||||
--- /dev/null
|
||||
+++ b/include/libyang-cpp/Regex.hpp
|
||||
@@ -0,0 +1,28 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+#pragma once
|
||||
+#include <libyang-cpp/export.h>
|
||||
+#include <string>
|
||||
+
|
||||
+namespace libyang {
|
||||
+class Context;
|
||||
+
|
||||
+/**
|
||||
+ * @brief A regular expression pattern which uses the YANG-flavored regex engine
|
||||
+ */
|
||||
+class LIBYANG_CPP_EXPORT Regex {
|
||||
+public:
|
||||
+ Regex(const std::string& pattern);
|
||||
+ ~Regex();
|
||||
+ bool matches(const std::string& input);
|
||||
+
|
||||
+private:
|
||||
+ void* code;
|
||||
+};
|
||||
+
|
||||
+}
|
||||
diff --git a/src/Regex.cpp b/src/Regex.cpp
|
||||
new file mode 100644
|
||||
index 0000000..a34fcd5
|
||||
--- /dev/null
|
||||
+++ b/src/Regex.cpp
|
||||
@@ -0,0 +1,45 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+
|
||||
+// The following header MUST be included before anything else which "might" use PCRE2
|
||||
+// because that library uses the preprocessor to prepare the "correct" versions of symbols.
|
||||
+#include <libyang/tree_data.h>
|
||||
+
|
||||
+#include <libyang-cpp/Regex.hpp>
|
||||
+#include <pcre2.h>
|
||||
+#include "utils/exception.hpp"
|
||||
+
|
||||
+#define THE_PCRE2_CODE_P reinterpret_cast<pcre2_code*>(this->code)
|
||||
+#define THE_PCRE2_CODE_P_P reinterpret_cast<pcre2_code**>(&this->code)
|
||||
+
|
||||
+namespace libyang {
|
||||
+
|
||||
+Regex::Regex(const std::string& pattern)
|
||||
+ : code(nullptr)
|
||||
+{
|
||||
+ auto res = ly_pattern_compile(nullptr, pattern.c_str(), THE_PCRE2_CODE_P_P);
|
||||
+ throwIfError(res, ly_last_logmsg());
|
||||
+}
|
||||
+
|
||||
+Regex::~Regex()
|
||||
+{
|
||||
+ pcre2_code_free(THE_PCRE2_CODE_P);
|
||||
+}
|
||||
+
|
||||
+bool Regex::matches(const std::string& input)
|
||||
+{
|
||||
+ auto res = ly_pattern_match(nullptr, nullptr /* we have a precompiled pattern */, input.c_str(), input.size(), THE_PCRE2_CODE_P_P);
|
||||
+ if (res == LY_SUCCESS) {
|
||||
+ return true;
|
||||
+ } else if (res == LY_ENOT) {
|
||||
+ return false;
|
||||
+ } else {
|
||||
+ throwError(res, ly_last_logmsg());
|
||||
+ }
|
||||
+}
|
||||
+}
|
||||
diff --git a/tests/regex.cpp b/tests/regex.cpp
|
||||
new file mode 100644
|
||||
index 0000000..7594f43
|
||||
--- /dev/null
|
||||
+++ b/tests/regex.cpp
|
||||
@@ -0,0 +1,30 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+
|
||||
+#include <doctest/doctest.h>
|
||||
+#include <libyang-cpp/Regex.hpp>
|
||||
+#include <libyang-cpp/Utils.hpp>
|
||||
+
|
||||
+TEST_CASE("regex")
|
||||
+{
|
||||
+ using libyang::Regex;
|
||||
+ using namespace std::string_literals;
|
||||
+
|
||||
+ REQUIRE_THROWS_WITH_AS(Regex{"\\"}, R"(Regular expression "\" is not valid ("": \ at end of pattern).: LY_EVALID)", libyang::ErrorWithCode);
|
||||
+
|
||||
+ Regex re{"ahoj"};
|
||||
+ REQUIRE(re.matches("ahoj"));
|
||||
+ REQUIRE(!re.matches("cau"));
|
||||
+ REQUIRE(re.matches("ahoj")); // test repeated calls as well
|
||||
+ REQUIRE(!re.matches("oj"));
|
||||
+ REQUIRE(!re.matches("aho"));
|
||||
+
|
||||
+ // Testing runtime errors during pattern *matching* is tricky. There's a limit on backtracking,
|
||||
+ // so testing a pattern like x+x+y on an obscenely long string of "x" characters *will* do the trick, eventually,
|
||||
+ // but the PCRE2 library has a default limit of 10M attempts. That's a VERY big number to hit during a test :(.
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
From 8d406728a53c2e77a4fe7393b7e30d42b8f9b9bb Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 30 Jan 2025 15:40:22 +0100
|
||||
Subject: [PATCH 13/18] Adapt to upstream changes in anyxml JSON printing
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5f6de28cebc95a446549017c2768b450f4fd6526
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
CMakeLists.txt | 3 ++-
|
||||
tests/data_node.cpp | 2 +-
|
||||
3 files changed, 5 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index 19f0def..b41c490 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -4,13 +4,13 @@
|
||||
- f38-gcc-cover:
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-01-29
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-01-29
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 512af8c..a40fd52 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -29,7 +29,8 @@ 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.7.8 IMPORTED_TARGET)
|
||||
+# FIXME: it's actually 3.7.12, but that hasn't been released yet
|
||||
+pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 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/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index 45fd6c1..14470dd 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1568,7 +1568,7 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||
== R"|({"example-schema:ax":[1,2,3]})|"s);
|
||||
REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||
- == R"|(<ax xmlns="http://example.com/coze"/>)|"s);
|
||||
+ == R"|(<ax xmlns="http://example.com/coze">)|"s + origJSON + "</ax>");
|
||||
}
|
||||
|
||||
REQUIRE(!!val);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
From f050e7e4a17ef2e221ca000a544042c33c9541fc Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 19:21:26 +0100
|
||||
Subject: [PATCH 14/18] fix DataNode::insertSibling() return value
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
When such an insert happens, the C library returns the node which is now
|
||||
the first sibling among all of the siblings of the node which was used
|
||||
as a reference during the insert. Our API was also documented this way,
|
||||
but we were not doing that.
|
||||
|
||||
Reported-by: Irfan <irfan.haslanded@gmail.com>
|
||||
Bug: https://github.com/CESNET/libyang-cpp/issues/29
|
||||
Change-Id: Id7f84a31e50212d6e2cbce9ab03a351a3721f767
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/DataNode.cpp | 2 +-
|
||||
tests/data_node.cpp | 7 +++++++
|
||||
2 files changed, 8 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 84591e5..b899b18 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -711,7 +711,7 @@ DataNode DataNode::insertSibling(DataNode toInsert)
|
||||
lyd_insert_sibling(this->m_node, toInsert.m_node, &firstSibling);
|
||||
}, toInsert.parent() ? OperationScope::JustThisNode : OperationScope::AffectsFollowingSiblings, m_refs);
|
||||
|
||||
- return DataNode{m_node, m_refs};
|
||||
+ return DataNode{firstSibling, m_refs};
|
||||
}
|
||||
|
||||
/**
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index 14470dd..b6ee455 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -973,6 +973,13 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE(getNumberOrder() == expected);
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("DataNode::insertSibling")
|
||||
+ {
|
||||
+ auto node = ctx.newPath("/example-schema:leafUInt8", "10");
|
||||
+ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:leafUInt16", "10")).path() == "/example-schema:leafUInt8");
|
||||
+ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:dummy", "10")).path() == "/example-schema:dummy");
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("DataNode::duplicate")
|
||||
{
|
||||
auto root = ctx.parseData(data2, libyang::DataFormat::JSON);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
From f958af42bf5d9fbd901ed59ebc1359ac0ddcc00f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 11:36:50 +0100
|
||||
Subject: [PATCH 15/18] API/ABI change: opaque node naming
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Our C++ API would ignore the "module name or XML prefix", which turns
|
||||
out to be *the* relevant part when it comes to opaque node naming. The
|
||||
prefix is, instead, just that string that might have been inherited from
|
||||
the parent node when parsing the serialized data; it's an optional
|
||||
thingy which, if not set explicitly, is implicitly inherited.
|
||||
|
||||
Adapt the API for this, and since this *will* break the build, let's
|
||||
bump the package version.
|
||||
|
||||
Change-Id: I199afe5fa7a571034b744531c63b93b9c656563a
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 5 ++---
|
||||
include/libyang-cpp/Context.hpp | 4 ++--
|
||||
include/libyang-cpp/DataNode.hpp | 11 ++++++++--
|
||||
src/Context.cpp | 32 ++++++++++++++++++++--------
|
||||
src/DataNode.cpp | 19 ++++++++++++++---
|
||||
tests/data_node.cpp | 36 ++++++++++++++++++++++++++------
|
||||
6 files changed, 82 insertions(+), 25 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index a40fd52..c5fec45 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -21,7 +21,7 @@ add_custom_target(libyang-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
+set(LIBYANG_CPP_PKG_VERSION "4")
|
||||
prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -29,8 +29,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)
|
||||
-# FIXME: it's actually 3.7.12, but that hasn't been released yet
|
||||
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 IMPORTED_TARGET)
|
||||
+pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.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/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp
|
||||
index baa47b3..ca89063 100644
|
||||
--- a/include/libyang-cpp/Context.hpp
|
||||
+++ b/include/libyang-cpp/Context.hpp
|
||||
@@ -115,8 +115,8 @@ public:
|
||||
CreatedNodes newPath2(const std::string& path, libyang::JSON json, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
CreatedNodes newPath2(const std::string& path, libyang::XML xml, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
std::optional<DataNode> newExtPath(const ExtensionInstance& ext, const std::string& path, const std::optional<std::string>& value, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
- std::optional<DataNode> newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional<libyang::JSON>& value) const;
|
||||
- std::optional<DataNode> newOpaqueXML(const std::string& moduleName, const std::string& name, const std::optional<libyang::XML>& value) const;
|
||||
+ std::optional<DataNode> newOpaqueJSON(const OpaqueName& name, const std::optional<libyang::JSON>& value) const;
|
||||
+ std::optional<DataNode> newOpaqueXML(const OpaqueName& name, const std::optional<libyang::XML>& value) const;
|
||||
SchemaNode findPath(const std::string& dataPath, const InputOutputNodes inputOutputNodes = InputOutputNodes::Input) const;
|
||||
Set<SchemaNode> findXPath(const std::string& path) const;
|
||||
|
||||
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||
index 851681b..310b5e3 100644
|
||||
--- a/include/libyang-cpp/DataNode.hpp
|
||||
+++ b/include/libyang-cpp/DataNode.hpp
|
||||
@@ -225,15 +225,22 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
- * @brief Contains a (possibly module-qualified) name of an opaque node.
|
||||
+ * @brief Contains a name of an opaque node.
|
||||
*
|
||||
- * This is generic container of a prefix/module string and a name string.
|
||||
+ * An opaque node always has a name, and a module (or XML namespace) to which this node belongs.
|
||||
+ * Sometimes, it also has a prefix.
|
||||
+ *
|
||||
+ * If the prefix is set *and* the underlying node is an opaque JSON one, then the prefix must be the same as the "module or namespace" name.
|
||||
+ * If the underlying node is an opaque XML one, then the XML prefix might be something completely different, and in that case the real fun begins.
|
||||
+ * Review the libayng C manual, this is something that the C++ wrapper doesn't really have under control.
|
||||
*
|
||||
* Wraps `ly_opaq_name`.
|
||||
*/
|
||||
struct LIBYANG_CPP_EXPORT OpaqueName {
|
||||
+ std::string moduleOrNamespace;
|
||||
std::optional<std::string> prefix;
|
||||
std::string name;
|
||||
+ std::string pretty() const;
|
||||
};
|
||||
|
||||
/**
|
||||
diff --git a/src/Context.cpp b/src/Context.cpp
|
||||
index 287f8c8..fec2f27 100644
|
||||
--- a/src/Context.cpp
|
||||
+++ b/src/Context.cpp
|
||||
@@ -378,17 +378,25 @@ std::optional<DataNode> Context::newExtPath(const ExtensionInstance& ext, const
|
||||
*
|
||||
* Wraps `lyd_new_opaq`.
|
||||
*
|
||||
- * @param moduleName Node module name, used as a prefix as well
|
||||
* @param name Name of the created node
|
||||
* @param value JSON data blob, if any
|
||||
* @return Returns the newly created node (if created)
|
||||
*/
|
||||
-std::optional<DataNode> Context::newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional<libyang::JSON>& value) const
|
||||
+std::optional<DataNode> Context::newOpaqueJSON(const OpaqueName& name, const std::optional<libyang::JSON>& value) const
|
||||
{
|
||||
+ if (name.prefix && *name.prefix != name.moduleOrNamespace) {
|
||||
+ throw Error{"invalid opaque JSON node: prefix \"" + *name.prefix + "\" doesn't match module name \"" + name.moduleOrNamespace + "\""};
|
||||
+ }
|
||||
lyd_node* out;
|
||||
- auto err = lyd_new_opaq(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, moduleName.c_str(), &out);
|
||||
+ auto err = lyd_new_opaq(nullptr,
|
||||
+ m_ctx.get(),
|
||||
+ name.name.c_str(),
|
||||
+ value ? value->content.c_str() : nullptr,
|
||||
+ name.prefix ? name.prefix->c_str() : nullptr,
|
||||
+ name.moduleOrNamespace.c_str(),
|
||||
+ &out);
|
||||
|
||||
- throwIfError(err, "Couldn't create an opaque JSON node '"s + moduleName + ':' + name + "'");
|
||||
+ throwIfError(err, "Couldn't create an opaque JSON node " + name.pretty());
|
||||
|
||||
if (out) {
|
||||
return DataNode{out, std::make_shared<internal_refcount>(m_ctx)};
|
||||
@@ -403,17 +411,23 @@ std::optional<DataNode> Context::newOpaqueJSON(const std::string& moduleName, co
|
||||
*
|
||||
* Wraps `lyd_new_opaq2`.
|
||||
*
|
||||
- * @param xmlNamespace Node module namespace
|
||||
* @param name Name of the created node
|
||||
* @param value XML data blob, if any
|
||||
* @return Returns the newly created node (if created)
|
||||
*/
|
||||
-std::optional<DataNode> Context::newOpaqueXML(const std::string& xmlNamespace, const std::string& name, const std::optional<libyang::XML>& value) const
|
||||
+std::optional<DataNode> Context::newOpaqueXML(const OpaqueName& name, const std::optional<libyang::XML>& value) const
|
||||
{
|
||||
+ // the XML node naming is "complex", we cannot really check the XML namespace for sanity here
|
||||
lyd_node* out;
|
||||
- auto err = lyd_new_opaq2(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, xmlNamespace.c_str(), &out);
|
||||
-
|
||||
- throwIfError(err, "Couldn't create an opaque XML node '"s + name +"' from namespace '" + xmlNamespace + "'");
|
||||
+ auto err = lyd_new_opaq2(nullptr,
|
||||
+ m_ctx.get(),
|
||||
+ name.name.c_str(),
|
||||
+ value ? value->content.c_str() : nullptr,
|
||||
+ name.prefix ? name.prefix->c_str() : nullptr,
|
||||
+ name.moduleOrNamespace.c_str(),
|
||||
+ &out);
|
||||
+
|
||||
+ throwIfError(err, "Couldn't create an opaque XML node " + name.pretty());
|
||||
|
||||
if (out) {
|
||||
return DataNode{out, std::make_shared<internal_refcount>(m_ctx)};
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index b899b18..344f1b6 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -1112,9 +1112,9 @@ OpaqueName DataNodeOpaque::name() const
|
||||
{
|
||||
auto opaq = reinterpret_cast<lyd_node_opaq*>(m_node);
|
||||
return OpaqueName{
|
||||
- .prefix = opaq->name.prefix ? std::optional(opaq->name.prefix) : std::nullopt,
|
||||
- .name = opaq->name.name
|
||||
- };
|
||||
+ .moduleOrNamespace = opaq->name.module_name,
|
||||
+ .prefix = opaq->name.prefix ? std::optional{opaq->name.prefix} : std::nullopt,
|
||||
+ .name = opaq->name.name};
|
||||
}
|
||||
|
||||
std::string DataNodeOpaque::value() const
|
||||
@@ -1122,6 +1122,19 @@ std::string DataNodeOpaque::value() const
|
||||
return reinterpret_cast<lyd_node_opaq*>(m_node)->value;
|
||||
}
|
||||
|
||||
+std::string OpaqueName::pretty() const
|
||||
+{
|
||||
+ if (prefix) {
|
||||
+ if (*prefix == moduleOrNamespace) {
|
||||
+ return *prefix + ':' + name;
|
||||
+ } else {
|
||||
+ return "{" + moduleOrNamespace + "}, " + *prefix + ':' + name;
|
||||
+ }
|
||||
+ } else {
|
||||
+ return "{" + moduleOrNamespace + "}, " + name;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Wraps a raw non-null lyd_node pointer.
|
||||
* @param node The pointer to be wrapped. Must not be null.
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index b6ee455..a1096a6 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1969,9 +1969,11 @@ TEST_CASE("Data Node manipulation")
|
||||
|
||||
DOCTEST_SUBCASE("opaque nodes for sysrepo ops data discard")
|
||||
{
|
||||
- auto discard1 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:a"});
|
||||
+ // the "short" form with no prefix
|
||||
+ auto discard1 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", std::nullopt, "discard-items"}, libyang::JSON{"/example-schema:a"});
|
||||
REQUIRE(!!discard1);
|
||||
- auto discard2 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:b"});
|
||||
+ // let's use a prefix form here
|
||||
+ 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)
|
||||
@@ -2001,16 +2003,38 @@ TEST_CASE("Data Node manipulation")
|
||||
auto data = ctx.newPath2("/example-schema:myRpc/outputLeaf", "AHOJ", libyang::CreationOptions::Output).createdNode;
|
||||
REQUIRE(data);
|
||||
data->newPath("/example-schema:myRpc/another", "yay", libyang::CreationOptions::Output);
|
||||
+ std::string prettyName;
|
||||
|
||||
- DOCTEST_SUBCASE("JSON") {
|
||||
- out = ctx.newOpaqueJSON(data->schema().module().name(), "output", std::nullopt);
|
||||
+ DOCTEST_SUBCASE("JSON no prefix") {
|
||||
+ out = ctx.newOpaqueJSON({data->schema().module().name(), std::nullopt, "output"}, std::nullopt);
|
||||
+ prettyName = "{example-schema}, output";
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("XML") {
|
||||
- out = ctx.newOpaqueXML(data->schema().module().ns(), "output", std::nullopt);
|
||||
+ DOCTEST_SUBCASE("JSON with prefix") {
|
||||
+ out = ctx.newOpaqueJSON({data->schema().module().name(), data->schema().module().name(), "output"}, std::nullopt);
|
||||
+ prettyName = "example-schema:output";
|
||||
+
|
||||
+ // wrong prefix is detected
|
||||
+ REQUIRE_THROWS_WITH_AS(ctx.newOpaqueJSON({data->schema().module().name(), "xxx", "output"}, std::nullopt),
|
||||
+ R"(invalid opaque JSON node: prefix "xxx" doesn't match module name "example-schema")",
|
||||
+ libyang::Error);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XML no prefix") {
|
||||
+ out = ctx.newOpaqueXML({data->schema().module().ns(), std::nullopt, "output"}, std::nullopt);
|
||||
+ prettyName = "{http://example.com/coze}, output";
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XML with prefix") {
|
||||
+ out = ctx.newOpaqueXML({data->schema().module().ns(),
|
||||
+ data->schema().module().name() /* prefix is a module name, not the XML NS*/,
|
||||
+ "output"},
|
||||
+ std::nullopt);
|
||||
+ prettyName = "{http://example.com/coze}, example-schema:output";
|
||||
}
|
||||
|
||||
REQUIRE(out);
|
||||
+ REQUIRE(prettyName == out->asOpaque().name().pretty());
|
||||
data->unlinkWithSiblings();
|
||||
out->insertChild(*data);
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,462 +0,0 @@
|
||||
From 8c59ecc5c687f8ee2ce62825835a378b422185f2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 13:41:01 +0100
|
||||
Subject: [PATCH 16/18] Add a helper for finding opaque nodes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is needed for sysrepo where we want to search through the
|
||||
sysrepo:discard-items top-level opaque nodes.
|
||||
|
||||
At first I wanted to simply wrap lyd_find_sibling_opaq_next(), but its
|
||||
semantics is quite different to what is needed in the code which uses
|
||||
sysrepo, so here's my attempt at a nice-ish API.
|
||||
|
||||
Change-Id: I2571961e42f6d7a121e27c881cacdcfec0e87762
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/DataNode.hpp | 2 +
|
||||
src/DataNode.cpp | 49 ++++++
|
||||
tests/data_node.cpp | 74 +++++++++
|
||||
tests/yang/sysrepo@2024-10-25.yang | 257 +++++++++++++++++++++++++++++
|
||||
4 files changed, 382 insertions(+)
|
||||
create mode 100644 tests/yang/sysrepo@2024-10-25.yang
|
||||
|
||||
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||
index 310b5e3..50d6c0e 100644
|
||||
--- a/include/libyang-cpp/DataNode.hpp
|
||||
+++ b/include/libyang-cpp/DataNode.hpp
|
||||
@@ -102,6 +102,7 @@ public:
|
||||
|
||||
bool isOpaque() const;
|
||||
DataNodeOpaque asOpaque() const;
|
||||
+ std::optional<DataNodeOpaque> firstOpaqueSibling() const;
|
||||
|
||||
// TODO: allow setting the `parent` argument
|
||||
DataNode duplicate(const std::optional<DuplicationOptions> opts = std::nullopt) const;
|
||||
@@ -241,6 +242,7 @@ struct LIBYANG_CPP_EXPORT OpaqueName {
|
||||
std::optional<std::string> prefix;
|
||||
std::string name;
|
||||
std::string pretty() const;
|
||||
+ bool matches(const std::string& prefixIsh, const std::string& name) const;
|
||||
};
|
||||
|
||||
/**
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 344f1b6..7e87917 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -1135,6 +1135,20 @@ std::string OpaqueName::pretty() const
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short Fuzzy-match a real-world name against a combination of "something like a prefix" and "unqualified name"
|
||||
+ *
|
||||
+ * Because libyang doesn't propagate inherited prefixes, and because opaque nodes are magic, we seem to require
|
||||
+ * this "fuzzy matching". It won't properly report a match on opaque nodes with a prefix that's inherited when
|
||||
+ * using XML namespaces, though.
|
||||
+ * */
|
||||
+bool OpaqueName::matches(const std::string& prefixIsh, const std::string& name) const
|
||||
+{
|
||||
+ return name == this->name
|
||||
+ && (prefixIsh == moduleOrNamespace
|
||||
+ || (!!prefix && prefixIsh == *prefix));
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Wraps a raw non-null lyd_node pointer.
|
||||
* @param node The pointer to be wrapped. Must not be null.
|
||||
@@ -1305,4 +1319,39 @@ bool DataNode::siblingsEqual(const libyang::DataNode& other, const DataCompare f
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short Find the first opaque node among the siblings
|
||||
+ *
|
||||
+ * This function was inspired by `lyd_find_sibling_opaq_next()`.
|
||||
+ * */
|
||||
+std::optional<DataNodeOpaque> DataNode::firstOpaqueSibling() const
|
||||
+{
|
||||
+ struct lyd_node *candidate = m_node;
|
||||
+
|
||||
+ // Skip all non-opaque nodes; libyang guarantees to have them first, followed by a (possibly empty) set
|
||||
+ // of opaque nodes. This is not documented anywhere, but it was explicitly confirmed by the maintainer:
|
||||
+ //
|
||||
+ // JK: can I rely on all non-opaque nodes being listed first among the siblings, and then all opaque nodes
|
||||
+ // in one continuous sequence (but with an unspecified order among the opaque nodes themselves)?
|
||||
+ //
|
||||
+ // MV: yep
|
||||
+ while (candidate && candidate->schema) {
|
||||
+ candidate = candidate->next;
|
||||
+ }
|
||||
+
|
||||
+ // walk back through the opaque nodes; however, libyang lists are not your regular linked lists
|
||||
+ while (candidate
|
||||
+ && !candidate->prev->schema // don't go from the first opaque node through the non-opaque ones
|
||||
+ && candidate->prev->next // don't wrap from the first node to the last one in case all of them are opaque
|
||||
+ ) {
|
||||
+ candidate = candidate->prev;
|
||||
+ }
|
||||
+
|
||||
+ if (candidate) {
|
||||
+ return DataNode{candidate, m_refs}.asOpaque();
|
||||
+ }
|
||||
+
|
||||
+ return std::nullopt;
|
||||
+}
|
||||
+
|
||||
}
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index a1096a6..db5a28e 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1982,6 +1982,80 @@ TEST_CASE("Data Node manipulation")
|
||||
"sysrepo:discard-items": "/example-schema:b"
|
||||
}
|
||||
)");
|
||||
+
|
||||
+ // check that a list which only consists of opaque nodes doesn't break our iteration
|
||||
+ REQUIRE(!!discard1->firstOpaqueSibling());
|
||||
+ REQUIRE(*discard1->firstOpaqueSibling() == *discard1);
|
||||
+ REQUIRE(!!discard2->firstOpaqueSibling());
|
||||
+ REQUIRE(*discard2->firstOpaqueSibling() == *discard1);
|
||||
+
|
||||
+ auto leafInt16 = ctx.newPath("/example-schema:leafInt16", "666");
|
||||
+ leafInt16.insertSibling(*discard1);
|
||||
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
+ == R"({
|
||||
+ "example-schema:leafInt16": 666,
|
||||
+ "sysrepo:discard-items": "/example-schema:a",
|
||||
+ "sysrepo:discard-items": "/example-schema:b"
|
||||
+}
|
||||
+)");
|
||||
+ REQUIRE(leafInt16.firstSibling().path() == "/example-schema:leafInt16");
|
||||
+ REQUIRE(discard1->firstSibling().path() == "/example-schema:leafInt16");
|
||||
+ REQUIRE(discard2->firstSibling().path() == "/example-schema:leafInt16");
|
||||
+
|
||||
+ auto dummy = ctx.newPath("/example-schema:dummy", "blah");
|
||||
+ auto opaqueLeaf = ctx.newPath("/example-schema:leafInt32", std::nullopt, libyang::CreationOptions::Opaque);
|
||||
+ opaqueLeaf.newAttrOpaqueJSON("ietf-netconf", "operation", "delete");
|
||||
+ dummy.insertSibling(opaqueLeaf);
|
||||
+
|
||||
+ // FIXME reword this: this one might not be handled by sysrepo, but we want it for our fuzzy matcher testing anyway
|
||||
+ auto discard3 = ctx.newOpaqueXML(libyang::OpaqueName{"http://www.sysrepo.org/yang/sysrepo", "sysrepo", "discard-items"}, libyang::XML{"/example-schema:c"});
|
||||
+ REQUIRE(!!discard3);
|
||||
+ // notice that it's printed without a proper prefix at first...
|
||||
+ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink)
|
||||
+ == R"({"discard-items":"/example-schema:c"})");
|
||||
+ // ...but after loading the module, the proper module is added back
|
||||
+ ctx.parseModule(TESTS_DIR / "yang" / "sysrepo@2024-10-25.yang", libyang::SchemaFormat::YANG);
|
||||
+ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink)
|
||||
+ == R"({"sysrepo:discard-items":"/example-schema:c"})");
|
||||
+
|
||||
+ dummy.insertSibling(*discard3);
|
||||
+ leafInt16.insertSibling(dummy);
|
||||
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
+ == R"({
|
||||
+ "example-schema:dummy": "blah",
|
||||
+ "example-schema:leafInt16": 666,
|
||||
+ "sysrepo:discard-items": "/example-schema:a",
|
||||
+ "sysrepo:discard-items": "/example-schema:b",
|
||||
+ "example-schema:leafInt32": "",
|
||||
+ "@example-schema:leafInt32": {
|
||||
+ "ietf-netconf:operation": "delete"
|
||||
+ },
|
||||
+ "sysrepo:discard-items": "/example-schema:c"
|
||||
+}
|
||||
+)");
|
||||
+
|
||||
+ REQUIRE(dummy.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(dummy.firstOpaqueSibling() != discard2);
|
||||
+ REQUIRE(leafInt16.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(opaqueLeaf.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard1->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard2->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard3->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard1->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(!discard1->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard2->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(!discard2->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard3->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard3->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(!opaqueLeaf.asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling() == discard2);
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling() == opaqueLeaf);
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling() == discard3);
|
||||
+ REQUIRE(!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling()->nextSibling());
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("RESTCONF RPC output")
|
||||
diff --git a/tests/yang/sysrepo@2024-10-25.yang b/tests/yang/sysrepo@2024-10-25.yang
|
||||
new file mode 100644
|
||||
index 0000000..f5bc32c
|
||||
--- /dev/null
|
||||
+++ b/tests/yang/sysrepo@2024-10-25.yang
|
||||
@@ -0,0 +1,257 @@
|
||||
+module sysrepo {
|
||||
+ namespace "http://www.sysrepo.org/yang/sysrepo";
|
||||
+ prefix sr;
|
||||
+
|
||||
+ yang-version 1.1;
|
||||
+
|
||||
+ import ietf-yang-types {
|
||||
+ prefix yang;
|
||||
+ }
|
||||
+
|
||||
+ import ietf-datastores {
|
||||
+ prefix ds;
|
||||
+ }
|
||||
+
|
||||
+ import ietf-yang-metadata {
|
||||
+ prefix md;
|
||||
+ revision-date 2016-08-05;
|
||||
+ }
|
||||
+
|
||||
+ organization
|
||||
+ "CESNET";
|
||||
+
|
||||
+ contact
|
||||
+ "Author: Michal Vasko
|
||||
+ <mvasko@cesnet.cz>";
|
||||
+
|
||||
+ description
|
||||
+ "Sysrepo YANG datastore internal attributes and information.";
|
||||
+
|
||||
+ revision "2024-10-25" {
|
||||
+ description
|
||||
+ "Removed redundant metadata used for push operational data.";
|
||||
+ }
|
||||
+
|
||||
+ revision "2019-07-10" {
|
||||
+ description
|
||||
+ "Initial revision.";
|
||||
+ }
|
||||
+
|
||||
+ typedef module-ref {
|
||||
+ description
|
||||
+ "Reference to a module.";
|
||||
+ type leafref {
|
||||
+ path "/sysrepo-modules/module/name";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ md:annotation operation {
|
||||
+ type enumeration {
|
||||
+ enum none {
|
||||
+ description
|
||||
+ "Node with this operation must exist but does not affect the datastore in any way.";
|
||||
+ reference
|
||||
+ "RFC 6241 section 7.2.: default-operation";
|
||||
+ }
|
||||
+ enum ether {
|
||||
+ description
|
||||
+ "Node with this operation does not have to exist and does not affect the datastore in any way.";
|
||||
+ }
|
||||
+ enum purge {
|
||||
+ description
|
||||
+ "Node with this operation represents an arbitrary generic node instance and all
|
||||
+ the instances will be deleted.";
|
||||
+ }
|
||||
+ }
|
||||
+ description
|
||||
+ "Additional proprietary <edit-config> operations used internally.";
|
||||
+ reference
|
||||
+ "RFC 6241 section 7.2.";
|
||||
+ }
|
||||
+
|
||||
+ identity notification {
|
||||
+ base ds:datastore;
|
||||
+ description
|
||||
+ "Special datastore for storing notifications for replay.";
|
||||
+ }
|
||||
+
|
||||
+ grouping module-info-grp {
|
||||
+ leaf name {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "Module name.";
|
||||
+ }
|
||||
+
|
||||
+ leaf revision {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "Module revision.";
|
||||
+ }
|
||||
+
|
||||
+ leaf-list enabled-feature {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "List of all the enabled features.";
|
||||
+ }
|
||||
+
|
||||
+ list plugin {
|
||||
+ key "datastore";
|
||||
+ description
|
||||
+ "Module datastore plugin handling specific datastore data.";
|
||||
+
|
||||
+ leaf datastore {
|
||||
+ type identityref {
|
||||
+ base ds:datastore;
|
||||
+ }
|
||||
+ description
|
||||
+ "Datastore of this plugin.";
|
||||
+ }
|
||||
+
|
||||
+ leaf name {
|
||||
+ type string;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Specific plugin name as present in the plugin structures.";
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ grouping deps-grp {
|
||||
+ list lref {
|
||||
+ description
|
||||
+ "Dependency of a leafref node.";
|
||||
+
|
||||
+ leaf target-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Path identifying the leafref target node.";
|
||||
+ }
|
||||
+
|
||||
+ leaf target-module {
|
||||
+ type module-ref;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Foreign target module of the leafref.";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list inst-id {
|
||||
+ description
|
||||
+ "Dependency of an instance-identifier node.";
|
||||
+
|
||||
+ leaf source-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Path identifying the instance-identifier node.";
|
||||
+ }
|
||||
+
|
||||
+ leaf default-target-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Default instance-identifier value.";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list xpath {
|
||||
+ description
|
||||
+ "Dependency of an XPath expression - must or when statement.";
|
||||
+
|
||||
+ leaf expression {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "XPath expression of the dependency - must or when statement argument.";
|
||||
+ }
|
||||
+
|
||||
+ leaf-list target-module {
|
||||
+ type module-ref;
|
||||
+ description
|
||||
+ "Foreign modules with the data needed for evaluation of the XPath.";
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ container sysrepo-modules {
|
||||
+ config false;
|
||||
+ description
|
||||
+ "All installed Sysrepo modules.";
|
||||
+
|
||||
+ leaf content-id {
|
||||
+ type uint32;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Sysrepo module-set content-id to be used for its generated yang-library data.";
|
||||
+ }
|
||||
+
|
||||
+ list module {
|
||||
+ key "name";
|
||||
+ description
|
||||
+ "Sysrepo module.";
|
||||
+
|
||||
+ uses module-info-grp;
|
||||
+
|
||||
+ leaf replay-support {
|
||||
+ type yang:date-and-time;
|
||||
+ description
|
||||
+ "Present only if the module supports replay. Means the earliest stored notification if any present.
|
||||
+ Otherwise the time the replay support was switched on.";
|
||||
+ }
|
||||
+
|
||||
+ container deps {
|
||||
+ description
|
||||
+ "Module data dependencies on other modules.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+
|
||||
+ leaf-list inverse-deps {
|
||||
+ type module-ref;
|
||||
+ description
|
||||
+ "List of modules that depend on this module.";
|
||||
+ }
|
||||
+
|
||||
+ list rpc {
|
||||
+ key "path";
|
||||
+ description
|
||||
+ "Module RPC/actions.";
|
||||
+
|
||||
+ leaf path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Path identifying the operation.";
|
||||
+ }
|
||||
+
|
||||
+ container in {
|
||||
+ description
|
||||
+ "Operation input dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+
|
||||
+ container out {
|
||||
+ description
|
||||
+ "Operation output dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list notification {
|
||||
+ key "path";
|
||||
+ description
|
||||
+ "Module notifications.";
|
||||
+
|
||||
+ leaf path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Path identifying the notification.";
|
||||
+ }
|
||||
+
|
||||
+ container deps {
|
||||
+ description
|
||||
+ "Notification dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
From dab8c9aac96063ccb8541d5d1795ee89b75faeee Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 2 Apr 2025 11:50:24 -0700
|
||||
Subject: [PATCH 17/18] build: link to libpcre2 explicitly
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is apparently a problem on Mac OS X builds where the transitive
|
||||
dependency via libyang doesn't take effect:
|
||||
|
||||
Undefined symbols for architecture arm64:
|
||||
"_pcre2_code_free_8", referenced from:
|
||||
libyang::Regex::~Regex() in Regex.cpp.o
|
||||
libyang::Regex::~Regex() in Regex.cpp.o
|
||||
ld: symbol(s) not found for architecture arm64
|
||||
|
||||
Let's fix this by linking with libpcre2-8 directly.
|
||||
|
||||
We're using a different approach than libyang; they have their own CMake
|
||||
wrapper. I find it easier to work with pkg-config modules, so let's try
|
||||
it that way.
|
||||
|
||||
Change-Id: I1a64407d852161595b7dca654433190002cc3600
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c5fec45..4e009aa 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -34,6 +34,9 @@ pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.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)
|
||||
|
||||
+# libyang::Regex::~Regex() bypasses libyang and calls out to libpcre directly
|
||||
+pkg_check_modules(LIBPCRE2 REQUIRED libpcre2-8 IMPORTED_TARGET)
|
||||
+
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
include(CheckIncludeFileCXX)
|
||||
@@ -64,7 +67,7 @@ add_library(yang-cpp
|
||||
src/utils/newPath.cpp
|
||||
)
|
||||
|
||||
-target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG)
|
||||
+target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG PkgConfig::LIBPCRE2)
|
||||
# We do not offer any long-term API/ABI guarantees. To make stuff easier for downstream consumers,
|
||||
# we will be bumping both API and ABI versions very deliberately.
|
||||
# There will be no attempts at semver tracking, for example.
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
From 7519fcd35216072a6b1eebe2a79e19789be345a9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 15:35:37 +0200
|
||||
Subject: [PATCH 18/18] CI: renamed project upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5447f243297fbfde7c364eb3919b00db239bd069
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
README.md | 2 +-
|
||||
ci/build.sh | 2 +-
|
||||
3 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index b41c490..7b92766 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -5,13 +5,13 @@
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
required-projects: *projects
|
||||
diff --git a/README.md b/README.md
|
||||
index d76975b..8291e00 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -11,7 +11,7 @@ Object lifetimes are managed automatically via RAII.
|
||||
- [libyang v3](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/onqtam/doctest/) as a C++ unit test framework
|
||||
+- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||
- optionally for the docs, Doxygen
|
||||
|
||||
## Building
|
||||
diff --git a/ci/build.sh b/ci/build.sh
|
||||
index d06b646..0867f07 100755
|
||||
--- a/ci/build.sh
|
||||
+++ b/ci/build.sh
|
||||
@@ -73,7 +73,7 @@ build_n_test() {
|
||||
}
|
||||
|
||||
build_n_test github/CESNET/libyang -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF
|
||||
-build_n_test github/onqtam/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
+build_n_test github/doctest/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
build_n_test ${ZUUL_PROJECT_NAME} -DBUILD_TESTING=ON
|
||||
|
||||
pushd ${BUILD_DIR}/${ZUUL_PROJECT_NAME}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Locally calculated
|
||||
sha256 82e3758011ec44c78e98d0777799d6e12aec5b8a64b32ebb20d0fe50e32488bb LICENSE
|
||||
sha256 43c84317ba13470f421a90bca74c062cf63a70e488d2e90c432b662c4638a14e libyang-cpp-v3.tar.gz
|
||||
sha256 70fd0df940026fb930d5407abe679e064caf4701bff35d79465b9ad2c0915808 libyang-cpp-v4.tar.gz
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# CPP bindings for libyang
|
||||
#
|
||||
################################################################################
|
||||
LIBYANG_CPP_VERSION = v3
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user