mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 04:33:00 +02:00
463 lines
16 KiB
Diff
463 lines
16 KiB
Diff
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
|
|
|