Fix leafDataFromValue

Leafrefs were not working correctly, when pointing to a data node not in
the tree. Because of this I had to rework the thing a little bit (I
basically copied the algorithm from cla-sysrepo, where I had the same
issue). I also changed the name. And also added test, both versions of
leafref included.

Funnily enough, it seems that I was already fixing this before, but for
some reason the bug appeared again while using netconf-cli, oh well, now
the tests should cover that hopefully.

Change-Id: I8a102c54bf58a4221610c8a83d26979fb7b4461d
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 448b094..e7570c6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -114,11 +114,15 @@
 
 target_link_libraries(yangaccess PUBLIC datastoreaccess yangschema PRIVATE PkgConfig::LIBYANG)
 
-add_library(yangschema STATIC
-    src/yang_schema.cpp
+add_library(libyangutils
     src/libyang_utils.cpp
     )
-target_link_libraries(yangschema PUBLIC schemas utils PRIVATE PkgConfig::LIBYANG)
+target_link_libraries(libyangutils PUBLIC PkgConfig::LIBYANG)
+
+add_library(yangschema STATIC
+    src/yang_schema.cpp
+    )
+target_link_libraries(yangschema PUBLIC schemas utils libyangutils PRIVATE PkgConfig::LIBYANG)
 
 add_library(parser STATIC
     src/parser.cpp
@@ -287,6 +291,7 @@
     cli_test(yang)
     target_link_libraries(test_yang yangschema)
     cli_test(utils)
+    target_link_libraries(test_utils libyangutils)
     cli_test(path_completion)
     cli_test(command_completion)
     cli_test(set_value_completion)
diff --git a/src/libyang_utils.cpp b/src/libyang_utils.cpp
index 274fec1..f1af868 100644
--- a/src/libyang_utils.cpp
+++ b/src/libyang_utils.cpp
@@ -4,60 +4,65 @@
 #include "libyang_utils.hpp"
 #include "utils.hpp"
 
-leaf_data_ leafValueFromValue(const libyang::S_Value& value, LY_DATA_TYPE type)
+leaf_data_ leafValueFromNode(libyang::S_Data_Node_Leaf_List node)
 {
-    using namespace std::string_literals;
-    switch (type) {
-    case LY_TYPE_INT8:
-        return value->int8();
-    case LY_TYPE_INT16:
-        return value->int16();
-    case LY_TYPE_INT32:
-        return value->int32();
-    case LY_TYPE_INT64:
-        return value->int64();
-    case LY_TYPE_UINT8:
-        return value->uint8();
-    case LY_TYPE_UINT16:
-        return value->uint16();
-    case LY_TYPE_UINT32:
-        return value->uint32();
-    case LY_TYPE_UINT64:
-        return value->uint64();
-    case LY_TYPE_BOOL:
-        return value->bln();
-    case LY_TYPE_STRING:
-        return std::string(value->string());
-    case LY_TYPE_ENUM:
-        return enum_{std::string(value->enm()->name())};
-    case LY_TYPE_IDENT:
-        return identityRef_{value->ident()->module()->name(), value->ident()->name()};
-    case LY_TYPE_BINARY:
-        return binary_{value->binary()};
-    case LY_TYPE_EMPTY:
-        return empty_{};
-    case LY_TYPE_BITS:
-    {
-        auto bits = value->bit();
-        std::vector<libyang::S_Type_Bit> filterNull;
-        std::copy_if(bits.begin(), bits.end(), std::back_inserter(filterNull), [] (auto bit) { return bit; });
-        bits_ res;
-        std::transform(filterNull.begin(), filterNull.end(), std::inserter(res.m_bits, res.m_bits.end()), [] (const auto& bit) { return bit->name(); });
-        return bits_{res};
+    std::function<leaf_data_(libyang::S_Data_Node_Leaf_List)> impl = [&impl] (libyang::S_Data_Node_Leaf_List node) -> leaf_data_ {
+        // value_type() is what's ACTUALLY stored inside `node`
+        // Leafrefs sometimes don't hold a reference to another, but they have the actual pointed-to value.
+        switch (node->value_type()) {
+        case LY_TYPE_ENUM:
+            return enum_{node->value()->enm()->name()};
+        case LY_TYPE_UINT8:
+            return node->value()->uint8();
+        case LY_TYPE_UINT16:
+            return node->value()->uint16();
+        case LY_TYPE_UINT32:
+            return node->value()->uint32();
+        case LY_TYPE_UINT64:
+            return node->value()->uint64();
+        case LY_TYPE_INT8:
+            return node->value()->int8();
+        case LY_TYPE_INT16:
+            return node->value()->int16();
+        case LY_TYPE_INT32:
+            return node->value()->int32();
+        case LY_TYPE_INT64:
+            return node->value()->int64();
+        case LY_TYPE_DEC64:
+        {
+            auto v = node->value()->dec64();
+            return v.value * std::pow(10, -v.digits);
+        }
+        case LY_TYPE_BOOL:
+            return node->value()->bln();
+        case LY_TYPE_STRING:
+            return std::string{node->value()->string()};
+        case LY_TYPE_BINARY:
+            return binary_{node->value()->binary()};
+        case LY_TYPE_IDENT:
+            return identityRef_{node->value()->ident()->module()->name(), node->value()->ident()->name()};
+        case LY_TYPE_EMPTY:
+            return empty_{};
+        case LY_TYPE_LEAFREF:
+        {
+            auto refsTo = node->value()->leafref();
+            assert(refsTo);
+            return impl(std::make_shared<libyang::Data_Node_Leaf_List>(node->value()->leafref()));
+        }
+        case LY_TYPE_BITS:
+        {
+            auto bits = node->value()->bit();
+            std::vector<libyang::S_Type_Bit> filterNull;
+            std::copy_if(bits.begin(), bits.end(), std::back_inserter(filterNull), [] (auto bit) { return bit; });
+            bits_ res;
+            std::transform(filterNull.begin(), filterNull.end(), std::inserter(res.m_bits, res.m_bits.end()), [] (const auto& bit) { return bit->name(); });
+            return bits_{res};
+        }
+        default:
+            return std::string{"(can't print)"};
     }
-    case LY_TYPE_DEC64:
-    {
-        auto v = value->dec64();
-        return v.value * std::pow(10, -v.digits);
-    }
-    case LY_TYPE_LEAFREF:
-    {
-        libyang::Data_Node_Leaf_List toPrint{value->leafref()};
-        return leafValueFromValue(toPrint.value(), toPrint.value_type());
-    }
-    default: // TODO: implement all types
-        return "(can't print)"s;
-    }
+    };
+    return impl(node);
 }
 
 namespace {
@@ -80,8 +85,8 @@
             res.emplace_back(stripXPathPrefix(it->path()), special_{SpecialValue::List});
         }
         if (it->schema()->nodetype() == LYS_LEAF || it->schema()->nodetype() == LYS_LEAFLIST) {
-            libyang::Data_Node_Leaf_List leaf(it);
-            auto value = leafValueFromValue(leaf.value(), leaf.value_type());
+            auto leaf = std::make_shared<libyang::Data_Node_Leaf_List>(it);
+            auto value = leafValueFromNode(leaf);
             res.emplace_back(stripXPathPrefix(it->path()), value);
         }
     }
diff --git a/src/libyang_utils.hpp b/src/libyang_utils.hpp
index d8bf4ad..a33e70b 100644
--- a/src/libyang_utils.hpp
+++ b/src/libyang_utils.hpp
@@ -9,5 +9,5 @@
 #include "ast_values.hpp"
 #include "datastore_access.hpp"
 
-leaf_data_ leafValueFromValue(const libyang::S_Value& value, LY_DATA_TYPE type);
+leaf_data_ leafValueFromNode(libyang::S_Data_Node_Leaf_List node);
 void lyNodesToTree(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items, std::optional<std::string> ignoredXPathPrefix = std::nullopt);
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 307ccf1..2c64cd4 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -232,9 +232,8 @@
 
         // I take the first child here, because the first element (the parent of the child()) will be the list
         for (const auto& keyLeaf : instance->child()->tree_for()) {
-            auto leafData = libyang::Data_Node_Leaf_List{keyLeaf};
-            auto leafSchema = libyang::Schema_Node_Leaf{leafData.schema()};
-            instanceRes.insert({ leafSchema.name(), leafValueFromValue(leafData.value(), leafSchema.type()->base())});
+            auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(keyLeaf);
+            instanceRes.insert({ leafData->schema()->name(), leafValueFromNode(leafData)});
         }
         res.emplace_back(instanceRes);
     }
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 6176f67..cdaff49 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -419,8 +419,8 @@
         ListInstance instanceRes;
         for (const auto& key : keys) {
             auto vec = wantedList->find_path(key->name())->data();
-            auto leaf = libyang::Data_Node_Leaf_List{*(vec.begin())};
-            instanceRes.emplace(key->name(), leafValueFromValue(leaf.value(), leaf.leaf_type()->base()));
+            auto leaf = std::make_shared<libyang::Data_Node_Leaf_List>(*(vec.begin()));
+            instanceRes.emplace(key->name(), leafValueFromNode(leaf));
         }
         res.emplace_back(instanceRes);
     }
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
index 8171c89..a460150 100644
--- a/src/yang_access.cpp
+++ b/src/yang_access.cpp
@@ -284,8 +284,8 @@
             if (child->schema()->nodetype() == LYS_LEAF) {
                 libyang::Schema_Node_Leaf leafSchema(child->schema());
                 if (leafSchema.is_key()) {
-                    libyang::Data_Node_Leaf_List leafData(child);
-                    instance.insert({leafSchema.name(), leafValueFromValue(leafData.value(), leafSchema.type()->base())});
+                    auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(child);
+                    instance.insert({leafSchema.name(), leafValueFromNode(leafData)});
                 }
             }
         }
diff --git a/tests/pretty_printers.hpp b/tests/pretty_printers.hpp
index b459a11..d3d67d3 100644
--- a/tests/pretty_printers.hpp
+++ b/tests/pretty_printers.hpp
@@ -10,6 +10,11 @@
 #include "parser.hpp"
 #include "utils.hpp"
 namespace std {
+std::ostream& operator<<(std::ostream& s, const leaf_data_& value)
+{
+    s << "leaf_data_<"<< boost::core::demangle(value.type().name()) << ">{" << leafDataToString(value) << "}" << std::endl;
+    return s;
+}
 std::ostream& operator<<(std::ostream& s, const Completions& completion)
 {
     s << std::endl << "Completions {" << std::endl << "    m_completions: ";
diff --git a/tests/utils.cpp b/tests/utils.cpp
index 509c2fe..e5740a7 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -9,6 +9,8 @@
 #include "trompeloeil_doctest.hpp"
 #include "completion.hpp"
 #include "leaf_data_helpers.hpp"
+#include "libyang_utils.hpp"
+#include "pretty_printers.hpp"
 #include "utils.hpp"
 
 TEST_CASE("utils")
@@ -102,3 +104,212 @@
     }
 
 }
+
+const auto schema = R"(
+module test-schema {
+    namespace "http://example.com/ayyyy";
+    prefix AHOJ;
+
+    leaf int8 {
+        type int8;
+    }
+    leaf int16 {
+        type int16;
+    }
+    leaf int32 {
+        type int32;
+    }
+    leaf int64 {
+        type int64;
+    }
+    leaf uint8 {
+        type uint8;
+    }
+    leaf uint16 {
+        type uint16;
+    }
+    leaf uint32 {
+        type uint32;
+    }
+    leaf uint64 {
+        type uint64;
+    }
+    leaf boolean {
+        type boolean;
+    }
+    leaf string {
+        type string;
+    }
+    leaf enum {
+        type enumeration {
+            enum A;
+            enum B;
+            enum C;
+        }
+    }
+    identity food;
+    identity apple {
+        base "food";
+    }
+    leaf identityRef {
+        type identityref {
+            base "food";
+        }
+    }
+    leaf binary {
+        type binary;
+    }
+    leaf empty {
+        type empty;
+    }
+    leaf bits {
+        type bits {
+            bit a;
+            bit b;
+            bit AHOJ;
+        }
+    }
+    leaf dec64 {
+        type decimal64 {
+            fraction-digits 5;
+        }
+    }
+
+    list stuff {
+        key "name";
+        leaf name {
+            type string;
+        }
+    }
+
+    leaf leafRefPresent {
+        type leafref {
+            path ../stuff/name;
+        }
+    }
+
+    leaf leafRefNonPresent {
+        type leafref {
+            path ../stuff/name;
+        }
+    }
+}
+)";
+
+const auto data = R"(
+{
+    "test-schema:int8": 8,
+    "test-schema:int16": 300,
+    "test-schema:int32": -300,
+    "test-schema:int64": -999999999999999,
+    "test-schema:uint8": 8,
+    "test-schema:uint16": 300,
+    "test-schema:uint32": 300,
+    "test-schema:uint64": 999999999999999,
+    "test-schema:boolean": true,
+    "test-schema:string": "AHOJ",
+    "test-schema:enum": "A",
+    "test-schema:identityRef": "apple",
+    "test-schema:binary": "QUhPSgo=",
+    "test-schema:empty": "",
+    "test-schema:bits": "a AHOJ",
+    "test-schema:dec64": "43242.43260",
+    "test-schema:stuff": [
+        {
+            "name": "Xaver"
+        }
+    ],
+    "test-schema:leafRefPresent": "Xaver",
+    "test-schema:leafRefNonPresent": "Lucas"
+}
+)";
+
+
+TEST_CASE("libyang_utils")
+{
+    auto ctx = std::make_shared<libyang::Context>();
+    ctx->parse_module_mem(schema, LYS_IN_YANG);
+    auto dataNode = ctx->parse_data_mem(data, LYD_JSON, LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_NOEXTDEPS);
+
+    std::string path;
+    leaf_data_ expectedLeafData;
+
+
+    SECTION("test-schema:int8") {
+        path = "test-schema:int8";
+        expectedLeafData = int8_t{8};
+    }
+    SECTION("test-schema:int16") {
+        path = "test-schema:int16";
+        expectedLeafData = int16_t{300};
+    }
+    SECTION("test-schema:int32") {
+        path = "test-schema:int32";
+        expectedLeafData = int32_t{-300};
+    }
+    SECTION("test-schema:int64") {
+        path = "test-schema:int64";
+        expectedLeafData = int64_t{-999999999999999};
+    }
+    SECTION("test-schema:uint8") {
+        path = "test-schema:uint8";
+        expectedLeafData = uint8_t{8};
+    }
+    SECTION("test-schema:uint16") {
+        path = "test-schema:uint16";
+        expectedLeafData = uint16_t{300};
+    }
+    SECTION("test-schema:uint32") {
+        path = "test-schema:uint32";
+        expectedLeafData = uint32_t{300};
+    }
+    SECTION("test-schema:uint64") {
+        path = "test-schema:uint64";
+        expectedLeafData = uint64_t{999999999999999};
+    }
+    SECTION("test-schema:boolean") {
+        path = "test-schema:boolean";
+        expectedLeafData = true;
+    }
+    SECTION("test-schema:string") {
+        path = "test-schema:string";
+        expectedLeafData = std::string{"AHOJ"};
+    }
+    SECTION("test-schema:enum") {
+        path = "test-schema:enum";
+        expectedLeafData = enum_{"A"};
+    }
+    SECTION("test-schema:identityRef") {
+        path = "test-schema:identityRef";
+        expectedLeafData = identityRef_{"test-schema", "apple"};
+    }
+    SECTION("test-schema:binary") {
+        path = "test-schema:binary";
+        expectedLeafData = binary_{"QUhPSgo="};
+    }
+    SECTION("test-schema:empty") {
+        path = "test-schema:empty";
+        expectedLeafData = empty_{};
+    }
+    SECTION("test-schema:bits") {
+        path = "test-schema:bits";
+        expectedLeafData = bits_{{"a", "AHOJ"}};
+    }
+    SECTION("test-schema:dec64") {
+        path = "test-schema:dec64";
+        expectedLeafData = 43242.43260;
+    }
+    SECTION("test-schema:leafRefPresent") {
+        path = "test-schema:leafRefPresent";
+        expectedLeafData = std::string{"Xaver"};
+    }
+    SECTION("test-schema:leafRefNonPresent") {
+        path = "test-schema:leafRefNonPresent";
+        expectedLeafData = std::string{"Lucas"};
+    }
+
+    auto leaf = dataNode->find_path(("/" + path).c_str());
+    REQUIRE(leaf->number() == 1);
+    auto firstLeaf = std::make_shared<libyang::Data_Node_Leaf_List>(leaf->data().front());
+    REQUIRE(leafValueFromNode(firstLeaf) == expectedLeafData);
+}