diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp index 0ac3e068..fadfcfda 100644 --- a/include/libyang-cpp/DataNode.hpp +++ b/include/libyang-cpp/DataNode.hpp @@ -129,6 +129,9 @@ class LIBYANG_CPP_EXPORT DataNode { const std::optional parseOpts = std::nullopt, const std::optional validationOpts = std::nullopt); + bool isEqual(const libyang::DataNode& other, const DataCompare flags=DataCompare::NoOptions) const; + bool siblingsEqual(const libyang::DataNode& other, const DataCompare flags=DataCompare::NoOptions) const; + friend Context; friend DataNodeAny; friend Set; diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp index 7557c02b..3ded4d2c 100644 --- a/include/libyang-cpp/Enum.hpp +++ b/include/libyang-cpp/Enum.hpp @@ -284,6 +284,16 @@ enum class SchemaPrintFlags : uint32_t { Shrink = 0x02, }; +/** + * @brief Wraps LYD_COMPARE_* flags. + */ +enum class DataCompare : uint32_t { + NoOptions = 0x00, /**< Equivalent of a raw C 0 to say "no flags given" in a typesafe manner */ + DistinguishExplicitDefaults = 0x02, /**< LYD_COMPARE_DEFAULTS */ + FullRecursion = 0x01, /**< LYD_COMPARE_FULL_RECURSION*/ + OpaqueAsData = 0x04, /**< LYD_COMPARE_OPAQ */ +}; + template constexpr Enum implEnumBitOr(const Enum a, const Enum b) { @@ -331,6 +341,11 @@ constexpr SchemaPrintFlags operator|(const SchemaPrintFlags a, const SchemaPrint return implEnumBitOr(a, b); } +constexpr DataCompare operator|(const DataCompare a, const DataCompare b) +{ + return implEnumBitOr(a, b); +} + LIBYANG_CPP_EXPORT std::ostream& operator<<(std::ostream& os, const NodeType& type); LIBYANG_CPP_EXPORT std::ostream& operator<<(std::ostream& os, const ErrorCode& err); LIBYANG_CPP_EXPORT std::ostream& operator<<(std::ostream& os, const ValidationErrorCode& err); diff --git a/src/DataNode.cpp b/src/DataNode.cpp index 6696b7fc..7b500399 100644 --- a/src/DataNode.cpp +++ b/src/DataNode.cpp @@ -1223,4 +1223,40 @@ void DataNode::parseSubtree( throwIfError(ret, "DataNode::parseSubtree: lyd_parse_data failed"); } +/** + * @short Compare a single node, possibly recusrively, possibly across contexts. + * + * Wraps `lyd_compare_single()`. + */ +bool DataNode::isEqual(const libyang::DataNode& other, const DataCompare flags) const +{ + auto res = lyd_compare_single(m_node, other.m_node, utils::toDataCompareOptions(flags)); + switch (res) { + case LY_SUCCESS: + return true; + case LY_ENOT: + return false; + default: + throwError(res, "lyd_compare_single"); + } +} + +/** + * @short Compare a node along its siblings, possibly recursively, possibly across contexts. + * + * Wraps `lyd_compare_siblings()`. + */ +bool DataNode::siblingsEqual(const libyang::DataNode& other, const DataCompare flags) const +{ + auto res = lyd_compare_siblings(m_node, other.m_node, utils::toDataCompareOptions(flags)); + switch (res) { + case LY_SUCCESS: + return true; + case LY_ENOT: + return false; + default: + throwError(res, "lyd_compare_single"); + } +} + } diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp index c4ca2375..7674fc6c 100644 --- a/src/utils/enum.hpp +++ b/src/utils/enum.hpp @@ -262,4 +262,14 @@ constexpr uint32_t toSchemaPrintFlags(const SchemaPrintFlags flags) static_assert(toSchemaPrintFlags(SchemaPrintFlags::Shrink) == LYS_PRINT_SHRINK); static_assert(toSchemaPrintFlags(SchemaPrintFlags::NoSubStatements) == LYS_PRINT_NO_SUBSTMT); static_assert(toSchemaPrintFlags(SchemaPrintFlags::Shrink | SchemaPrintFlags::NoSubStatements) == (LYS_PRINT_SHRINK | LYS_PRINT_NO_SUBSTMT)); + +constexpr uint32_t toDataCompareOptions(const DataCompare flags) +{ + return static_cast(flags); +} +static_assert(toDataCompareOptions(DataCompare::DistinguishExplicitDefaults) == LYD_COMPARE_DEFAULTS); +static_assert(toDataCompareOptions(DataCompare::FullRecursion) == LYD_COMPARE_FULL_RECURSION); +static_assert(toDataCompareOptions(DataCompare::OpaqueAsData) == LYD_COMPARE_OPAQ); +static_assert(toDataCompareOptions(DataCompare::FullRecursion | DataCompare::NoOptions) == LYD_COMPARE_FULL_RECURSION); +static_assert(toDataCompareOptions(DataCompare::DistinguishExplicitDefaults | DataCompare::FullRecursion | DataCompare::OpaqueAsData) == (LYD_COMPARE_DEFAULTS | LYD_COMPARE_FULL_RECURSION | LYD_COMPARE_OPAQ)); } diff --git a/tests/data_node.cpp b/tests/data_node.cpp index 3c722942..5ad4fbc1 100644 --- a/tests/data_node.cpp +++ b/tests/data_node.cpp @@ -2116,6 +2116,40 @@ TEST_CASE("Data Node manipulation") } } } + + DOCTEST_SUBCASE("comparing") { + libyang::Context ctxB(std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd); + ctxB.parseModule(example_schema, libyang::SchemaFormat::YANG); + + // Differences from `data2`: + // - `/example-schema:leafInt8` is missing + // - `/example-schema:first/second/third/fourth/fifth` has a different value + const auto fifth666 = R"({ + "example-schema:first": { + "second": { + "third": { + "fourth": { + "fifth": "666" + } + } + } + } +} +)"s; + + auto rootA = ctx.parseData(data2, libyang::DataFormat::JSON); + auto rootB = ctxB.parseData(data2, libyang::DataFormat::JSON); + auto rootC = ctxB.parseData(fifth666, libyang::DataFormat::JSON); + + auto secondA = *rootA->findPath("/example-schema:first/second"); + auto secondB = *rootB->findPath("/example-schema:first/second"); + auto secondC = *rootC->findPath("/example-schema:first/second"); + REQUIRE(secondA.isEqual(secondB)); + REQUIRE(secondA.siblingsEqual(secondB)); + REQUIRE(secondA.isEqual(secondC)); + REQUIRE(!secondA.siblingsEqual(secondC)); + REQUIRE(!secondA.isEqual(secondC, libyang::DataCompare::FullRecursion)); + } } TEST_CASE("union data types")