From 4c00c24dee6bbc694d62e7174b7ac14f8aa95f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 30 Oct 2025 21:16:13 +0100 Subject: [PATCH 4/7] port to libyang v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Organization: Wires Apart from the usual API/ABI changes, the default values of a `leaf` or a `leaf-list` are no longer canonicalized. This used to be missing from libyang v4, but it was added recently. Sill, chances are that the form which was used in the YANG model's source code is actually *the* intended presentation, so let's skip canonicalization altogether. Upstream has removed some flags and defines, so let's sync their list with whatever is currently available in the `devel` branch. Change-Id: I52dcec88eeee003b45dd6cf400f4d6875abbcc05 Signed-off-by: Mattias Walström --- .zuul.yaml | 4 ++-- CMakeLists.txt | 4 ++-- README.md | 2 +- include/libyang-cpp/Context.hpp | 6 +++++- include/libyang-cpp/DataNode.hpp | 5 ++++- include/libyang-cpp/Enum.hpp | 15 +++++++++++++-- src/Context.cpp | 12 ++++++++++-- src/DataNode.cpp | 11 +++++++++-- src/SchemaNode.cpp | 6 +++--- src/Type.cpp | 2 +- src/utils/enum.hpp | 21 +++++++++++++++++++-- tests/context.cpp | 32 +++++++++++++++++--------------- tests/data_node.cpp | 2 +- tests/schema_node.cpp | 4 ++-- 14 files changed, 89 insertions(+), 37 deletions(-) diff --git a/.zuul.yaml b/.zuul.yaml index fd89549..7b92766 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -4,13 +4,13 @@ - f38-gcc-cover: required-projects: - name: github/CESNET/libyang - override-checkout: cesnet/2025-08-07 + override-checkout: devel - name: github/doctest/doctest override-checkout: v2.3.6 - f38-clang-asan-ubsan: required-projects: &projects - name: github/CESNET/libyang - override-checkout: cesnet/2025-08-07 + override-checkout: devel - name: github/doctest/doctest override-checkout: v2.4.11 - f38-clang-tsan: diff --git a/CMakeLists.txt b/CMakeLists.txt index c6db320..90f9aaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,7 +20,7 @@ add_custom_target(libyang-cpp-version-cmake cmake/ProjectGitVersionRunner.cmake ) include(cmake/ProjectGitVersion.cmake) -set(LIBYANG_CPP_PKG_VERSION "4") +set(LIBYANG_CPP_PKG_VERSION "5") prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION}) find_package(Doxygen) @@ -28,7 +28,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON) find_package(PkgConfig REQUIRED) -pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET) +pkg_check_modules(LIBYANG REQUIRED libyang>=4.1.0 IMPORTED_TARGET) # FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency find_package(date) diff --git a/README.md b/README.md index 8291e00..0cef2ff 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Object lifetimes are managed automatically via RAII. ## Dependencies -- [libyang v3](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*) +- [libyang v4](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*) - C++20 compiler (e.g., GCC 10.x+, clang 10+) - CMake 3.19+ - optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework diff --git a/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp index ca89063..e695914 100644 --- a/include/libyang-cpp/Context.hpp +++ b/include/libyang-cpp/Context.hpp @@ -108,7 +108,11 @@ public: std::optional getSubmodule(const std::string& name, const std::optional& revision) const; void registerModuleCallback(std::function callback); - ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const; + ParsedOp parseOp( + const std::string& input, + const DataFormat format, + const OperationType opType, + const std::optional parseOpts = std::nullopt) const; DataNode newPath(const std::string& path, const std::optional& value = std::nullopt, const std::optional options = std::nullopt) const; CreatedNodes newPath2(const std::string& path, const std::optional& value = std::nullopt, const std::optional options = std::nullopt) const; diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp index e202a96..35e5936 100644 --- a/include/libyang-cpp/DataNode.hpp +++ b/include/libyang-cpp/DataNode.hpp @@ -123,7 +123,10 @@ public: Collection siblings() const; Collection immediateChildren() const; - ParsedOp parseOp(const std::string& input, const DataFormat format, const OperationType opType) const; + ParsedOp parseOp(const std::string& input, + const DataFormat format, + const OperationType opType, + const std::optional parseOpts = std::nullopt) const; void parseSubtree( const std::string& data, diff --git a/include/libyang-cpp/Enum.hpp b/include/libyang-cpp/Enum.hpp index 85dcc83..32142f9 100644 --- a/include/libyang-cpp/Enum.hpp +++ b/include/libyang-cpp/Enum.hpp @@ -136,6 +136,7 @@ enum class DuplicationOptions : uint32_t { WithFlags = 0x08, NoExt = 0x10, WithPriv = 0x20, + NoLyds = 0x40, }; enum class NodeType : uint16_t { @@ -167,6 +168,13 @@ enum class ContextOptions : uint16_t { PreferSearchDirs = 0x20, SetPrivParsed = 0x40, ExplicitCompile = 0x80, + EnableImpFeatures = 0x100, + CompileObsolete = 0x200, + LybHashes = 0x400, + LeafrefExtended = 0x800, + LeafrefLinking = 0x1000, + BuiltinPluginsOnly = 0x2000, + StaticPluginsOnly = 0x4000, }; /** @@ -203,7 +211,6 @@ enum class AnydataValueType : uint32_t { String, XML, JSON, - LYB }; /** @@ -252,6 +259,7 @@ enum class ValidationOptions { MultiError = 0x0004, Operational = 0x0008, NoDefaults = 0x0010, + NotFinal = 0x0020, }; /** @@ -262,11 +270,14 @@ enum class ParseOptions { Strict = 0x020000, Opaque = 0x040000, NoState = 0x080000, - LybModUpdate = 0x100000, + LybSkipCtxCheck = 0x100000, Ordered = 0x200000, Subtree = 0x400000, /**< Do not use this one for parsing of data subtrees */ WhenTrue = 0x800000, NoNew = 0x1000000, + StoreOnly = 0x2010000, + JsonNull = 0x4000000, + JsonStringDataTypes = 0x8000000, }; /** diff --git a/src/Context.cpp b/src/Context.cpp index fec2f27..f5bdc71 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -239,7 +239,7 @@ std::optional Context::parseExtData( * Note: to parse a NETCONF RPC reply, you MUST parse the original NETCONF RPC request (that is, you have to use * this method with OperationType::RpcNetconf). */ -ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const +ParsedOp Context::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional parseOpts) const { auto in = wrap_ly_in_new_memory(input); @@ -251,7 +251,15 @@ ParsedOp Context::parseOp(const std::string& input, const DataFormat format, con case OperationType::NotificationYang: { lyd_node* op = nullptr; lyd_node* tree = nullptr; - auto err = lyd_parse_op(m_ctx.get(), nullptr, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, &op); + auto err = lyd_parse_op( + m_ctx.get(), + nullptr, + in.get(), + utils::toLydFormat(format), + utils::toOpType(opType), + parseOpts ? utils::toParseOptions(*parseOpts) : 0, + &tree, + &op); ParsedOp res; res.tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt; diff --git a/src/DataNode.cpp b/src/DataNode.cpp index e28ef30..edabf9b 100644 --- a/src/DataNode.cpp +++ b/src/DataNode.cpp @@ -391,7 +391,7 @@ DataNodeAny DataNode::asAny() const * * Wraps `lyd_parse_op`. */ -ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType) const +ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, const OperationType opType, const std::optional parseOpts) const { auto in = wrap_ly_in_new_memory(input); @@ -401,7 +401,14 @@ ParsedOp DataNode::parseOp(const std::string& input, const DataFormat format, co case OperationType::ReplyRestconf: { lyd_node* op = nullptr; lyd_node* tree = nullptr; - auto err = lyd_parse_op(m_node->schema->module->ctx, m_node, in.get(), utils::toLydFormat(format), utils::toOpType(opType), &tree, nullptr); + auto err = lyd_parse_op(m_node->schema->module->ctx, + m_node, + in.get(), + utils::toLydFormat(format), + utils::toOpType(opType), + parseOpts ? utils::toParseOptions(*parseOpts) : 0, + &tree, + nullptr); ParsedOp res{ .tree = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt, .op = op ? std::optional{libyang::wrapRawNode(op)} : std::nullopt diff --git a/src/SchemaNode.cpp b/src/SchemaNode.cpp index ce24203..2e0351a 100644 --- a/src/SchemaNode.cpp +++ b/src/SchemaNode.cpp @@ -564,7 +564,7 @@ std::vector LeafList::defaultValuesStr() const auto dflts = reinterpret_cast(m_node)->dflts; std::vector res; for (const auto& it : std::span(dflts, LY_ARRAY_COUNT(dflts))) { - res.emplace_back(lyd_value_get_canonical(m_ctx.get(), it)); + res.emplace_back(it.str); } return res; } @@ -660,8 +660,8 @@ bool LeafList::isUserOrdered() const std::optional Leaf::defaultValueStr() const { auto dflt = reinterpret_cast(m_node)->dflt; - if (dflt) { - return lyd_value_get_canonical(m_ctx.get(), dflt); + if (dflt.str) { + return std::string{dflt.str}; } else { return std::nullopt; } diff --git a/src/Type.cpp b/src/Type.cpp index 01b2c4f..4d2db46 100644 --- a/src/Type.cpp +++ b/src/Type.cpp @@ -240,7 +240,7 @@ std::optional Type::description() const */ std::string Type::internalPluginId() const { - return m_type->plugin->id; + return lysc_get_type_plugin(m_type->plugin_ref)->id; } /** diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp index 5e02d24..0b2e2d7 100644 --- a/src/utils/enum.hpp +++ b/src/utils/enum.hpp @@ -91,6 +91,7 @@ static_assert(LYD_DUP_WITH_FLAGS == toDuplicationOptions(DuplicationOptions::Wit static_assert(LYD_DUP_WITH_PARENTS == toDuplicationOptions(DuplicationOptions::WithParents)); static_assert(LYD_DUP_NO_EXT == toDuplicationOptions(DuplicationOptions::NoExt)); static_assert(LYD_DUP_WITH_PRIV == toDuplicationOptions(DuplicationOptions::WithPriv)); +static_assert(LYD_DUP_NO_LYDS == toDuplicationOptions(DuplicationOptions::NoLyds)); static_assert((LYD_DUP_NO_META | LYD_DUP_NO_EXT) == toDuplicationOptions(DuplicationOptions::NoMeta | DuplicationOptions::NoExt)); @@ -131,6 +132,13 @@ static_assert(toContextOptions(ContextOptions::DisableSearchCwd) == LY_CTX_DISAB static_assert(toContextOptions(ContextOptions::PreferSearchDirs) == LY_CTX_PREFER_SEARCHDIRS); static_assert(toContextOptions(ContextOptions::SetPrivParsed) == LY_CTX_SET_PRIV_PARSED); static_assert(toContextOptions(ContextOptions::ExplicitCompile) == LY_CTX_EXPLICIT_COMPILE); +static_assert(toContextOptions(ContextOptions::EnableImpFeatures) == LY_CTX_ENABLE_IMP_FEATURES); +static_assert(toContextOptions(ContextOptions::CompileObsolete) == LY_CTX_COMPILE_OBSOLETE); +static_assert(toContextOptions(ContextOptions::LybHashes) == LY_CTX_LYB_HASHES); +static_assert(toContextOptions(ContextOptions::LeafrefExtended) == LY_CTX_LEAFREF_EXTENDED); +static_assert(toContextOptions(ContextOptions::LeafrefLinking) == LY_CTX_LEAFREF_LINKING); +static_assert(toContextOptions(ContextOptions::BuiltinPluginsOnly) == LY_CTX_BUILTIN_PLUGINS_ONLY); +static_assert(toContextOptions(ContextOptions::StaticPluginsOnly) == LY_CTX_STATIC_PLUGINS_ONLY); constexpr uint16_t toLogOptions(const LogOptions options) { @@ -198,6 +206,10 @@ constexpr uint32_t toValidationOptions(const ValidationOptions opts) static_assert(toValidationOptions(ValidationOptions::NoState) == LYD_VALIDATE_NO_STATE); static_assert(toValidationOptions(ValidationOptions::Present) == LYD_VALIDATE_PRESENT); +static_assert(toValidationOptions(ValidationOptions::MultiError) == LYD_VALIDATE_MULTI_ERROR); +static_assert(toValidationOptions(ValidationOptions::Operational) == LYD_VALIDATE_OPERATIONAL); +static_assert(toValidationOptions(ValidationOptions::NoDefaults) == LYD_VALIDATE_NO_DEFAULTS); +static_assert(toValidationOptions(ValidationOptions::NotFinal) == LYD_VALIDATE_NOT_FINAL); constexpr uint32_t toParseOptions(const ParseOptions opts) { @@ -208,8 +220,14 @@ static_assert(toParseOptions(ParseOptions::ParseOnly) == LYD_PARSE_ONLY); static_assert(toParseOptions(ParseOptions::Strict) == LYD_PARSE_STRICT); static_assert(toParseOptions(ParseOptions::Opaque) == LYD_PARSE_OPAQ); static_assert(toParseOptions(ParseOptions::NoState) == LYD_PARSE_NO_STATE); -static_assert(toParseOptions(ParseOptions::LybModUpdate) == LYD_PARSE_LYB_MOD_UPDATE); +static_assert(toParseOptions(ParseOptions::LybSkipCtxCheck) == LYD_PARSE_LYB_SKIP_CTX_CHECK); static_assert(toParseOptions(ParseOptions::Ordered) == LYD_PARSE_ORDERED); +static_assert(toParseOptions(ParseOptions::Subtree) == LYD_PARSE_SUBTREE); +static_assert(toParseOptions(ParseOptions::WhenTrue) == LYD_PARSE_WHEN_TRUE); +static_assert(toParseOptions(ParseOptions::NoNew) == LYD_PARSE_NO_NEW); +static_assert(toParseOptions(ParseOptions::StoreOnly) == LYD_PARSE_STORE_ONLY); +static_assert(toParseOptions(ParseOptions::JsonNull) == LYD_PARSE_JSON_NULL); +static_assert(toParseOptions(ParseOptions::JsonStringDataTypes) == LYD_PARSE_JSON_STRING_DATATYPES); constexpr lyd_type toOpType(const OperationType type) { @@ -242,7 +260,6 @@ static_assert(toAnydataValueType(AnydataValueType::DataTree) == LYD_ANYDATA_DATA static_assert(toAnydataValueType(AnydataValueType::String) == LYD_ANYDATA_STRING); static_assert(toAnydataValueType(AnydataValueType::XML) == LYD_ANYDATA_XML); static_assert(toAnydataValueType(AnydataValueType::JSON) == LYD_ANYDATA_JSON); -static_assert(toAnydataValueType(AnydataValueType::LYB) == LYD_ANYDATA_LYB); constexpr LYS_OUTFORMAT toLysOutFormat(const SchemaOutputFormat format) { diff --git a/tests/context.cpp b/tests/context.cpp index c0b7e09..eaedebf 100644 --- a/tests/context.cpp +++ b/tests/context.cpp @@ -359,25 +359,27 @@ TEST_CASE("context") ctx->loadModule("mod1", std::nullopt, {}); ctx->parseModule(valid_yang_model, libyang::SchemaFormat::YANG); auto modules = ctx->modules(); - REQUIRE(modules.size() == 8); + REQUIRE(modules.size() == 9); REQUIRE(modules.at(0).name() == "ietf-yang-metadata"); REQUIRE(modules.at(0).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-metadata"); REQUIRE(modules.at(1).name() == "yang"); REQUIRE(modules.at(1).ns() == "urn:ietf:params:xml:ns:yang:1"); - REQUIRE(modules.at(2).name() == "ietf-inet-types"); - REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types"); - REQUIRE(modules.at(3).name() == "ietf-yang-types"); - REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types"); - REQUIRE(modules.at(4).name() == "ietf-yang-schema-mount"); - REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount"); - REQUIRE(modules.at(5).name() == "ietf-yang-structure-ext"); - REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext"); - REQUIRE(modules.at(6).name() == "mod1"); - REQUIRE(modules.at(6).ns() == "http://example.com"); - REQUIRE(*modules.at(6).revision() == "2021-11-15"); - REQUIRE(modules.at(7).name() == "test"); + REQUIRE(modules.at(2).name() == "default"); + REQUIRE(modules.at(2).ns() == "urn:ietf:params:xml:ns:netconf:default:1.0"); + REQUIRE(modules.at(3).name() == "ietf-inet-types"); + REQUIRE(modules.at(3).ns() == "urn:ietf:params:xml:ns:yang:ietf-inet-types"); + REQUIRE(modules.at(4).name() == "ietf-yang-types"); + REQUIRE(modules.at(4).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-types"); + REQUIRE(modules.at(5).name() == "ietf-yang-schema-mount"); + REQUIRE(modules.at(5).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount"); + REQUIRE(modules.at(6).name() == "ietf-yang-structure-ext"); + REQUIRE(modules.at(6).ns() == "urn:ietf:params:xml:ns:yang:ietf-yang-structure-ext"); + REQUIRE(modules.at(7).name() == "mod1"); REQUIRE(modules.at(7).ns() == "http://example.com"); - REQUIRE(modules.at(7).revision() == std::nullopt); + REQUIRE(*modules.at(7).revision() == "2021-11-15"); + REQUIRE(modules.at(8).name() == "test"); + REQUIRE(modules.at(8).ns() == "http://example.com"); + REQUIRE(modules.at(8).revision() == std::nullopt); } DOCTEST_SUBCASE("Module comparison") @@ -704,7 +706,7 @@ TEST_CASE("context") DOCTEST_SUBCASE("schema printing") { - std::optional ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed}; + std::optional ctx_pp{std::in_place, std::nullopt, libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::CompileObsolete}; auto mod = ctx_pp->parseModule(type_module, libyang::SchemaFormat::YANG); REQUIRE(mod.printStr(libyang::SchemaOutputFormat::Tree) == R"(module: type_module diff --git a/tests/data_node.cpp b/tests/data_node.cpp index 9215b12..88ca5b9 100644 --- a/tests/data_node.cpp +++ b/tests/data_node.cpp @@ -2426,7 +2426,7 @@ TEST_CASE("Data Node manipulation") "WTF": "foo bar baz" } } - )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf), + )", libyang::DataFormat::JSON, libyang::OperationType::RpcRestconf, libyang::ParseOptions::Strict), "Can't parse into operation data tree: LY_EVALID", libyang::Error); } } diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp index 65ef462..58152a4 100644 --- a/tests/schema_node.cpp +++ b/tests/schema_node.cpp @@ -19,7 +19,7 @@ using namespace std::string_literals; TEST_CASE("SchemaNode") { std::optional ctx{std::in_place, std::nullopt, - libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd}; + libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd | libyang::ContextOptions::CompileObsolete}; std::optional ctxWithParsed{std::in_place, std::nullopt, libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd}; ctx->parseModule(example_schema, libyang::SchemaFormat::YANG); @@ -841,7 +841,7 @@ TEST_CASE("SchemaNode") DOCTEST_SUBCASE("LeafList::defaultValuesStr") { - REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector{"-1", "512", "1024", "2048"}); + REQUIRE(ctx->findPath("/type_module:leafListWithDefault").asLeafList().defaultValuesStr() == std::vector{"-1", "+512", "0x400", "04000"}); REQUIRE(ctx->findPath("/type_module:leafListBasic").asLeafList().defaultValuesStr().size() == 0); } -- 2.43.0