Add support for yang type descriptions

Change-Id: I1fd070fb975aa82b2d4c1aa4165c5ab0153ff49f
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index f1682bf..aaa0595 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -127,6 +127,7 @@
 std::string Interpreter::buildTypeInfo(const std::string& path) const
 {
     std::ostringstream ss;
+    std::string typeDescription;
     switch (m_datastore.schema()->nodeType(path)) {
     case yang::NodeTypes::Container:
         ss << "container";
@@ -156,6 +157,10 @@
             ss << " [" + *leafType.m_units + "]";
         }
 
+        if (leafType.m_description) {
+            typeDescription = "\nType description: " + *leafType.m_description;
+        }
+
         if (m_datastore.schema()->leafIsKey(path)) {
             ss << " (key)";
         }
@@ -184,24 +189,29 @@
     }
 
     if (!m_datastore.schema()->isConfig(path)) {
-        ss << " (ro)";
+        ss << " (ro)\n";
     }
 
-    return ss.str();
-}
-
-void Interpreter::operator()(const describe_& describe) const
-{
-    auto path = pathToString(toCanonicalPath(describe.m_path));
     auto status = m_datastore.schema()->status(path);
     auto statusStr = status == yang::Status::Deprecated ? " (deprecated)" :
         status == yang::Status::Obsolete ? " (obsolete)" :
         "";
 
-    std::cout << path << ": " << buildTypeInfo(path) << statusStr << std::endl;
+    ss << statusStr;
+
     if (auto description = m_datastore.schema()->description(path)) {
-        std::cout << std::endl << *description << std::endl;
+        ss << std::endl << *description << std::endl;
     }
+
+    ss << typeDescription;
+    return ss.str();
+}
+
+void Interpreter::operator()(const describe_& describe) const
+{
+    auto fullPath = pathToString(toCanonicalPath(describe.m_path));
+
+    std::cout << pathToString(describe.m_path) << ": " << buildTypeInfo(fullPath) << std::endl;
 }
 
 void Interpreter::operator()(const move_& move) const
diff --git a/src/leaf_data_type.cpp b/src/leaf_data_type.cpp
index fe6fe04..f97e382 100644
--- a/src/leaf_data_type.cpp
+++ b/src/leaf_data_type.cpp
@@ -10,11 +10,12 @@
 namespace yang {
 bool TypeInfo::operator==(const TypeInfo& other) const
 {
-    return this->m_type == other.m_type && this->m_units == other.m_units;
+    return std::tie(this->m_type, this->m_units, this->m_description) == std::tie(other.m_type, other.m_units, other.m_description);
 }
-TypeInfo::TypeInfo(const yang::LeafDataType& type, const std::optional<std::string> units)
+TypeInfo::TypeInfo(const yang::LeafDataType& type, const std::optional<std::string> units, const std::optional<std::string> description)
     : m_type(type)
     , m_units(units)
+    , m_description(description)
 {
 }
 Enum::Enum(std::set<enum_>&& values)
diff --git a/src/leaf_data_type.hpp b/src/leaf_data_type.hpp
index 98cb12e..35a2961 100644
--- a/src/leaf_data_type.hpp
+++ b/src/leaf_data_type.hpp
@@ -105,9 +105,12 @@
     std::vector<TypeInfo> m_unionTypes;
 };
 struct TypeInfo {
-    TypeInfo(const yang::LeafDataType& type, const std::optional<std::string> units = std::nullopt);
+    TypeInfo(const yang::LeafDataType& type,
+            const std::optional<std::string> units = std::nullopt,
+            const std::optional<std::string> description = std::nullopt);
     bool operator==(const TypeInfo& other) const;
     yang::LeafDataType m_type;
     std::optional<std::string> m_units;
+    std::optional<std::string> m_description;
 };
 }
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 7f27711..7e5bb9f 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -284,7 +284,19 @@
             }
         }
 
-        return yang::TypeInfo(resType, resUnits);
+        std::optional<std::string> resDescription;
+
+        // checking for parentTypedef->type()->der() means I'm going to enter inside base types like "string". These
+        // also have a description, but it isn't too helpful ("human-readable string")
+        for (auto parentTypedef = type->der(); parentTypedef && parentTypedef->type()->der(); parentTypedef = parentTypedef->type()->der()) {
+            auto dsc = parentTypedef->dsc();
+            if (dsc) {
+                resDescription = dsc;
+                break;
+            }
+        }
+
+        return yang::TypeInfo(resType, resUnits, resDescription);
     };
     return resolveType(leaf->type());
 }
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index 6c546d3..0db4c4f 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -88,6 +88,16 @@
         }
     }
 
+    typedef myType {
+        type int32;
+        description "My type.";
+    }
+
+    leaf typedefedLeaf {
+        type myType;
+        description "This is a typedefed leaf.";
+    }
+
     grouping upAndDown {
         leaf up {
             type boolean;
diff --git a/tests/pretty_printers.hpp b/tests/pretty_printers.hpp
index 1c7154b..7f19a8e 100644
--- a/tests/pretty_printers.hpp
+++ b/tests/pretty_printers.hpp
@@ -96,7 +96,9 @@
 
 std::ostream& operator<<(std::ostream& s, const yang::TypeInfo& type)
 {
-    s << type.m_type << (type.m_units ? " units: " + *type.m_units : "");
+    s << type.m_type;
+    s << " units: " << (type.m_units ? *type.m_units : "std::nullopt");
+    s << " description: " << (type.m_description ? *type.m_description : "std::nullopt");
     return s;
 }
 
diff --git a/tests/yang.cpp b/tests/yang.cpp
index 1ec6507..699a05c 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -171,6 +171,7 @@
             enum lol;
             enum data;
         }
+        description "This is a restricted enum typedef.";
     }
 
     leaf leafEnumTypedef {
@@ -518,6 +519,7 @@
         SECTION("leafType")
         {
             yang::LeafDataType type;
+            std::optional<std::string> expectedDescription;
 
             SECTION("leafString")
             {
@@ -622,6 +624,7 @@
                 node.first = "example-schema";
                 node.second = "leafEnumTypedefRestricted2";
                 type = createEnum({"lol", "data"});
+                expectedDescription = "This is a restricted enum typedef.";
             }
 
             SECTION("pizzaSize")
@@ -736,7 +739,7 @@
             }
 
 
-            REQUIRE(ys.leafType(path, node) == type);
+            REQUIRE(ys.leafType(path, node) == yang::TypeInfo(type, std::nullopt, expectedDescription));
         }
         SECTION("availableNodes")
         {
@@ -1072,6 +1075,16 @@
             REQUIRE(ys.leafType(pathToSchemaString(path, Prefixes::WhenNeeded)) == yang::TypeInfo{expectedType, expectedUnits});
         }
 
+        SECTION("type description")
+        {
+            yang::LeafDataType expectedType = createEnum({"lol", "data"});
+            std::optional<std::string> expectedDescription;
+
+            path.m_nodes.emplace_back(module_{"example-schema"}, leaf_("leafEnumTypedefRestricted2"));
+            expectedDescription = "This is a restricted enum typedef.";
+            REQUIRE(ys.leafType(pathToSchemaString(path, Prefixes::WhenNeeded)) == yang::TypeInfo{expectedType, std::nullopt, expectedDescription});
+        }
+
         SECTION("nodeType")
         {
             yang::NodeTypes expected;