Add support for identityref data type

Change-Id: I369150b83d86a4c22fadf383ee011eee619a4c15
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 55b6ccd..a4a9e08 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -14,6 +14,19 @@
 {
 }
 
+identityRef_::identityRef_() = default;
+
+identityRef_::identityRef_(const std::string& value)
+    : m_value(value)
+{
+}
+
+identityRef_::identityRef_(const std::string& module, const std::string& value)
+    : m_prefix(module_{module})
+    , m_value(value)
+{
+}
+
 binary_::binary_() = default;
 
 binary_::binary_(const std::string& value)
@@ -36,6 +49,11 @@
     return this->m_path == b.m_path && this->m_options == b.m_options;
 }
 
+bool identityRef_::operator==(const identityRef_& b) const
+{
+    return this->m_prefix == b.m_prefix && this->m_value == b.m_value;
+}
+
 bool binary_::operator==(const binary_& b) const
 {
     return this->m_value == b.m_value;
diff --git a/src/ast_commands.hpp b/src/ast_commands.hpp
index 5015787..f4cd0a9 100644
--- a/src/ast_commands.hpp
+++ b/src/ast_commands.hpp
@@ -198,6 +198,7 @@
 BOOST_FUSION_ADAPT_STRUCT(set_, m_path, m_data)
 BOOST_FUSION_ADAPT_STRUCT(enum_, m_value)
 BOOST_FUSION_ADAPT_STRUCT(binary_, m_value)
+BOOST_FUSION_ADAPT_STRUCT(identityRef_, m_prefix, m_value)
 BOOST_FUSION_ADAPT_STRUCT(commit_)
 BOOST_FUSION_ADAPT_STRUCT(help_, m_cmd)
 BOOST_FUSION_ADAPT_STRUCT(discard_)
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index d365641..fede04c 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -360,6 +360,22 @@
     }
 };
 
+// This handler only checks if the module exists
+// It doesn't set any ParserContext flags (except the error message)
+struct data_module_prefix_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        const auto& schema = parserContext.m_schema;
+
+        if (!schema.isModule(parserContext.m_curPath, ast.m_name)) {
+            parserContext.m_errorMsg = "Invalid module name.";
+            _pass(context) = false;
+        }
+    }
+};
+
 struct leaf_data_class {
     template <typename Iterator, typename Exception, typename Context>
     x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const&, Context const& context)
@@ -369,10 +385,10 @@
         if (parserContext.m_errorMsg.empty()) {
             leaf_ leaf = boost::get<leaf_>(parserContext.m_curPath.m_nodes.back().m_suffix);
             schemaPath_ location = pathWithoutLastNode(parserContext.m_curPath);
-            if (location.m_nodes.empty()) {
-                parserContext.m_curModule = parserContext.m_curPath.m_nodes.back().m_prefix->m_name;
-            }
-            parserContext.m_errorMsg = "Expected " + leafDataTypeToString(schema.leafType(location, {parserContext.m_curModule, leaf.m_name})) + " here:";
+            boost::optional<std::string> module;
+            if (parserContext.m_curPath.m_nodes.back().m_prefix)
+                module = parserContext.m_curPath.m_nodes.back().m_prefix.value().m_name;
+            parserContext.m_errorMsg = "Expected " + leafDataTypeToString(schema.leafType(location, {module, leaf.m_name})) + " here:";
             return x3::error_handler_result::fail;
         }
         return x3::error_handler_result::rethrow;
@@ -415,6 +431,9 @@
     void on_success(Iterator const& start, Iterator const& end, T& ast, Context const& context)
     {
         leaf_data_base_class::on_success(start, end, ast, context);
+        // Base class on_success cannot return for us, so we check if it failed the parser.
+        if (_pass(context) == false)
+            return;
         auto& parserContext = x3::get<parser_context_tag>(context);
         auto& schema = parserContext.m_schema;
         boost::optional<std::string> module;
@@ -474,6 +493,43 @@
     }
 };
 
+struct leaf_data_identityRef_data_class;
+
+struct leaf_data_identityRef_class : leaf_data_base_class {
+    leaf_data_identityRef_class()
+        : leaf_data_base_class(yang::LeafDataTypes::IdentityRef)
+    {
+    }
+
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const& start, Iterator const& end, T& ast, Context const& context)
+    {
+        // FIXME: can I reuse leaf_data_enum_class somehow..?
+        leaf_data_base_class::on_success(start, end, ast, context);
+        // Base class on_success cannot return for us, so we check if it failed the parser.
+        if (_pass(context) == false)
+            return;
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        auto& schema = parserContext.m_schema;
+        boost::optional<std::string> module;
+        if (parserContext.m_curPath.m_nodes.back().m_prefix)
+            module = parserContext.m_curPath.m_nodes.back().m_prefix.value().m_name;
+
+        leaf_ leaf = boost::get<leaf_>(parserContext.m_curPath.m_nodes.back().m_suffix);
+        schemaPath_ location = pathWithoutLastNode(parserContext.m_curPath);
+
+        ModuleValuePair pair;
+        if (ast.m_prefix) {
+            pair.first = ast.m_prefix.get().m_name;
+        }
+        pair.second = ast.m_value;
+
+        if (!schema.leafIdentityIsValid(location, {module, leaf.m_name}, pair)) {
+            _pass(context) = false;
+        }
+    }
+};
+
 struct set_class {
     template <typename Iterator, typename Exception, typename Context>
     x3::error_handler_result on_error(Iterator&, Iterator const&, Exception const& x, Context const& context)
@@ -616,6 +672,32 @@
         leaf_ leaf = boost::get<leaf_>(parserContext.m_curPath.m_nodes.back().m_suffix);
         schemaPath_ location = pathWithoutLastNode(parserContext.m_curPath);
 
-        parserContext.m_suggestions = schema.enumValues(location, {module, leaf.m_name});
+        // Only generate completions if the type is enum so that we don't
+        // overwrite some other completions.
+        if (schema.leafType(location, {module, leaf.m_name}) == yang::LeafDataTypes::Enum)
+            parserContext.m_suggestions = schema.enumValues(location, {module, leaf.m_name});
+    }
+};
+
+// FIXME: can I reuse createEnumSuggestions?
+struct createIdentitySuggestions_class {
+    template <typename T, typename Iterator, typename Context>
+    void on_success(Iterator const& begin, Iterator const&, T&, Context const& context)
+    {
+        auto& parserContext = x3::get<parser_context_tag>(context);
+        parserContext.m_completionIterator = begin;
+        const Schema& schema = parserContext.m_schema;
+
+        boost::optional<std::string> module;
+        if (parserContext.m_curPath.m_nodes.back().m_prefix)
+            module = parserContext.m_curPath.m_nodes.back().m_prefix.value().m_name;
+
+        leaf_ leaf = boost::get<leaf_>(parserContext.m_curPath.m_nodes.back().m_suffix);
+        schemaPath_ location = pathWithoutLastNode(parserContext.m_curPath);
+
+        // Only generate completions if the type is identityref so that we
+        // don't overwrite some other completions.
+        if (schema.leafType(location, {module, leaf.m_name}) == yang::LeafDataTypes::IdentityRef)
+            parserContext.m_suggestions = schema.validIdentities(location, {module, leaf.m_name}, Prefixes::WhenNeeded);
     }
 };
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index 557b01f..dcd13ff 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -18,6 +18,8 @@
 #include <map>
 #include <vector>
 
+#include "ast_values.hpp"
+
 struct nodeup_ {
     bool operator==(const nodeup_&) const
     {
@@ -62,11 +64,6 @@
     std::string m_name;
 };
 
-struct module_ {
-    bool operator==(const module_& b) const;
-    std::string m_name;
-};
-
 struct schemaNode_ {
     boost::optional<module_> m_prefix;
     boost::variant<container_, list_, nodeup_, leaf_> m_suffix;
diff --git a/src/ast_values.hpp b/src/ast_values.hpp
index b9053c0..350fc8d 100644
--- a/src/ast_values.hpp
+++ b/src/ast_values.hpp
@@ -7,6 +7,7 @@
 */
 #pragma once
 
+#include <boost/optional.hpp>
 #include <boost/variant.hpp>
 
 struct enum_ {
@@ -23,8 +24,23 @@
     std::string m_value;
 };
 
+struct module_ {
+    bool operator==(const module_& b) const;
+    std::string m_name;
+};
+
+struct identityRef_ {
+    identityRef_();
+    identityRef_(const std::string& module, const std::string& value);
+    identityRef_(const std::string& value);
+    bool operator==(const identityRef_& b) const;
+    boost::optional<module_> m_prefix;
+    std::string m_value;
+};
+
 using leaf_data_ = boost::variant<enum_,
                                   binary_,
+                                  identityRef_,
                                   double,
                                   bool,
                                   int32_t,
diff --git a/src/grammars.hpp b/src/grammars.hpp
index a2e68fc..9ba81c6 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -46,6 +46,8 @@
 x3::rule<leaf_data_string_class, std::string> const leaf_data_string = "leaf_data_string";
 x3::rule<leaf_data_binary_data_class, std::string> const leaf_data_binary_data = "leaf_data_binary_data";
 x3::rule<leaf_data_binary_class, binary_> const leaf_data_binary = "leaf_data_binary";
+x3::rule<leaf_data_identityRef_data_class, identityRef_> const leaf_data_identityRef_data = "leaf_data_identityRef_data";
+x3::rule<leaf_data_identityRef_class, identityRef_> const leaf_data_identityRef = "leaf_data_identityRef";
 
 x3::rule<discard_class, discard_> const discard = "discard";
 x3::rule<ls_class, ls_> const ls = "ls";
@@ -65,6 +67,7 @@
 x3::rule<createCommandSuggestions_class, x3::unused_type> const createCommandSuggestions = "createCommandSuggestions";
 x3::rule<completing_class, x3::unused_type> const completing = "completing";
 x3::rule<createEnumSuggestions_class, x3::unused_type> const createEnumSuggestions = "createEnumSuggestions";
+x3::rule<createIdentitySuggestions_class, x3::unused_type> const createIdentitySuggestions = "createIdentitySuggestions";
 
 #if __clang__
 #pragma GCC diagnostic push
@@ -219,6 +222,15 @@
 auto const leaf_data_binary_def =
     leaf_data_binary_data;
 
+auto const leaf_data_identityRef_data_def =
+    -module  >> node_identifier;
+
+auto const createIdentitySuggestions_def =
+    x3::eps;
+
+auto const leaf_data_identityRef_def =
+    createIdentitySuggestions >> leaf_data_identityRef_data;
+
 auto const leaf_data_def =
 x3::expect[
     leaf_data_enum |
@@ -227,6 +239,7 @@
     leaf_data_int |
     leaf_data_uint |
     leaf_data_binary |
+    leaf_data_identityRef |
     leaf_data_string];
 
 struct ls_options_table : x3::symbols<LsOption> {
@@ -320,6 +333,8 @@
 BOOST_SPIRIT_DEFINE(leaf_data_string)
 BOOST_SPIRIT_DEFINE(leaf_data_binary_data)
 BOOST_SPIRIT_DEFINE(leaf_data_binary)
+BOOST_SPIRIT_DEFINE(leaf_data_identityRef_data)
+BOOST_SPIRIT_DEFINE(leaf_data_identityRef)
 BOOST_SPIRIT_DEFINE(initializePath)
 BOOST_SPIRIT_DEFINE(set)
 BOOST_SPIRIT_DEFINE(commit)
@@ -337,3 +352,4 @@
 BOOST_SPIRIT_DEFINE(createCommandSuggestions)
 BOOST_SPIRIT_DEFINE(completing)
 BOOST_SPIRIT_DEFINE(createEnumSuggestions)
+BOOST_SPIRIT_DEFINE(createIdentitySuggestions)
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index 44177b6..e08eab2 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -21,6 +21,11 @@
         return data.m_value;
     }
 
+    std::string operator()(const identityRef_& data) const
+    {
+        return data.m_value;
+    }
+
     template <typename T>
     std::string operator()(const T& data) const
     {
@@ -42,7 +47,17 @@
 
 void Interpreter::operator()(const set_& set) const
 {
-    m_datastore.setLeaf(absolutePathFromCommand(set), set.m_data);
+    auto data = set.m_data;
+
+    // If the user didn't supply a module prefix for identityref, we need to add it ourselves
+    if (data.type() == typeid(identityRef_)) {
+        auto identityRef = boost::get<identityRef_>(data);
+        if (!identityRef.m_prefix) {
+            identityRef.m_prefix = set.m_path.m_nodes.front().m_prefix.value();
+            data = identityRef;
+        }
+    }
+    m_datastore.setLeaf(absolutePathFromCommand(set), data);
 }
 
 void Interpreter::operator()(const get_& get) const
diff --git a/src/schema.hpp b/src/schema.hpp
index aaeb846..6a2240d 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -15,6 +15,8 @@
 #include <unordered_map>
 #include "ast_path.hpp"
 
+using ModuleValuePair = std::pair<boost::optional<std::string>, std::string>;
+
 namespace yang {
 enum class ContainerTraits {
     Presence,
@@ -29,6 +31,7 @@
     Uint,
     Enum,
     Binary,
+    IdentityRef,
 };
 
 struct container {
@@ -37,9 +40,11 @@
 struct list {
     std::set<std::string> m_keys;
 };
+
 struct leaf {
     yang::LeafDataTypes m_type;
     std::set<std::string> m_enumValues;
+    ModuleValuePair m_identBase;
 };
 
 struct module {
@@ -51,6 +56,11 @@
     Recursive
 };
 
+enum class Prefixes {
+    Always,
+    WhenNeeded
+};
+
 using NodeType = boost::variant<yang::container, yang::list, yang::leaf, yang::module>;
 
 
@@ -76,10 +86,12 @@
     virtual bool isList(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual bool leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const = 0;
+    virtual bool leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const = 0;
     virtual bool listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const = 0;
     virtual bool nodeExists(const std::string& location, const std::string& node) const = 0;
     virtual const std::set<std::string> listKeys(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual yang::LeafDataTypes leafType(const schemaPath_& location, const ModuleNodePair& node) const = 0;
+    virtual const std::set<std::string> validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const = 0;
     virtual const std::set<std::string> enumValues(const schemaPath_& location, const ModuleNodePair& node) const = 0;
     virtual std::set<std::string> childNodes(const schemaPath_& path, const Recursion recursion) const = 0;
 
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index d80ac7b..a3649ae 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -16,7 +16,6 @@
     m_nodes.emplace("", std::unordered_map<std::string, NodeType>());
 }
 
-
 const std::unordered_map<std::string, NodeType>& StaticSchema::children(const std::string& name) const
 {
     return m_nodes.at(name);
@@ -105,12 +104,24 @@
 
 void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataTypes& type)
 {
-    m_nodes.at(location).emplace(name, yang::leaf{type, {}});
+    m_nodes.at(location).emplace(name, yang::leaf{type, {}, {}});
 }
 
 void StaticSchema::addLeafEnum(const std::string& location, const std::string& name, std::set<std::string> enumValues)
 {
-    m_nodes.at(location).emplace(name, yang::leaf{yang::LeafDataTypes::Enum, enumValues});
+    yang::leaf toAdd;
+    toAdd.m_type = yang::LeafDataTypes::Enum;
+    toAdd.m_enumValues = enumValues;
+    m_nodes.at(location).emplace(name, toAdd);
+}
+
+void StaticSchema::addLeafIdentityRef(const std::string& location, const std::string& name, const ModuleValuePair& base)
+{
+    assert(base.first); // base identity cannot have an empty module
+    yang::leaf toAdd;
+    toAdd.m_type = yang::LeafDataTypes::IdentityRef;
+    toAdd.m_identBase = base;
+    m_nodes.at(location).emplace(name, toAdd);
 }
 
 void StaticSchema::addModule(const std::string& name)
@@ -118,6 +129,13 @@
     m_modules.emplace(name);
 }
 
+void StaticSchema::addIdentity(const std::optional<ModuleValuePair>& base, const ModuleValuePair& name)
+{
+    if (base)
+        m_identities.at(base.value()).emplace(name);
+
+    m_identities.emplace(name, std::set<ModuleValuePair>());
+}
 
 bool StaticSchema::leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const
 {
@@ -125,6 +143,50 @@
     return enums.find(value) != enums.end();
 }
 
+void StaticSchema::getIdentSet(const ModuleValuePair& ident, std::set<ModuleValuePair>& res) const
+{
+    res.insert(ident);
+    auto derivedIdentities = m_identities.at(ident);
+    for (auto it : derivedIdentities) {
+        getIdentSet(it, res);
+    }
+}
+
+const std::set<std::string> StaticSchema::validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const
+{
+    std::string locationString = pathToAbsoluteSchemaString(location);
+    assert(isLeaf(location, node));
+
+    const auto& child = children(locationString).at(fullNodeName(location, node));
+    const auto& leaf = boost::get<yang::leaf>(child);
+
+    std::set<ModuleValuePair> identSet;
+    getIdentSet(leaf.m_identBase, identSet);
+
+    std::set<std::string> res;
+    std::transform(identSet.begin(), identSet.end(), std::inserter(res, res.end()), [location, node, prefixes](const auto& it) {
+        auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
+        std::string stringIdent;
+        if (prefixes == Prefixes::Always || (it.first && it.first.value() != topLevelModule)) {
+            stringIdent += it.first ? it.first.value() : topLevelModule;
+            stringIdent += ":";
+        }
+        stringIdent += it.second;
+        return stringIdent;
+    });
+
+    return res;
+}
+
+bool StaticSchema::leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const
+{
+    auto identities = validIdentities(location, node, Prefixes::Always);
+
+    auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
+    auto identModule = value.first ? value.first.value() : topLevelModule;
+    return std::any_of(identities.begin(), identities.end(), [identModule, value](const auto& x) { return x == identModule + ":" + value.second; });
+}
+
 bool StaticSchema::isLeaf(const schemaPath_& location, const ModuleNodePair& node) const
 {
     std::string locationString = pathToAbsoluteSchemaString(location);
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index d07852e..3bf60f0 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -29,22 +29,28 @@
     bool isList(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const override;
+    bool leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const override;
     bool listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const override;
     bool nodeExists(const std::string& location, const std::string& node) const override;
     const std::set<std::string> listKeys(const schemaPath_& location, const ModuleNodePair& node) const override;
     yang::LeafDataTypes leafType(const schemaPath_& location, const ModuleNodePair& node) const override;
     const std::set<std::string> enumValues(const schemaPath_& location, const ModuleNodePair& node) const override;
+    const std::set<std::string> validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const override;
     std::set<std::string> childNodes(const schemaPath_& path, const Recursion) const override;
 
     void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
     void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataTypes& type);
     void addLeafEnum(const std::string& location, const std::string& name, std::set<std::string> enumValues);
+    void addLeafIdentityRef(const std::string& location, const std::string& name, const ModuleValuePair& base);
     void addList(const std::string& location, const std::string& name, const std::set<std::string>& keys);
     void addModule(const std::string& name);
+    void addIdentity(const std::optional<ModuleValuePair>& base, const ModuleValuePair& name);
 
 private:
     const std::unordered_map<std::string, NodeType>& children(const std::string& name) const;
+    void getIdentSet(const ModuleValuePair& ident, std::set<ModuleValuePair>& res) const;
 
     std::unordered_map<std::string, std::unordered_map<std::string, NodeType>> m_nodes;
     std::set<std::string> m_modules;
+    std::map<ModuleValuePair, std::set<ModuleValuePair>> m_identities;
 };
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index d514203..4e1ab4b 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -48,6 +48,12 @@
         return std::make_shared<sysrepo::Val>(value.m_value.c_str(), SR_BINARY_T);
     }
 
+    sysrepo::S_Val operator()(const identityRef_& value) const
+    {
+        auto res = value.m_prefix.value().m_name + ":" + value.m_value;
+        return std::make_shared<sysrepo::Val>(res.c_str(), SR_IDENTITYREF_T);
+    }
+
     sysrepo::S_Val operator()(const std::string& value) const
     {
         return std::make_shared<sysrepo::Val>(value.c_str());
diff --git a/src/utils.cpp b/src/utils.cpp
index 595dc7a..421fc3e 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -48,8 +48,10 @@
         return "an unsigned integer";
     case yang::LeafDataTypes::Enum:
         return "an enum";
+    case yang::LeafDataTypes::IdentityRef:
+        return "an identity";
     default:
-        return "";
+        throw std::runtime_error("leafDataTypeToString: unsupported leaf data type");
     }
 }
 
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index f6e0eea..757c9f4 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -138,6 +138,46 @@
     return enumSet;
 }
 
+const std::set<std::string> YangSchema::validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const
+{
+    if (!isLeaf(location, node) || leafType(location, node) != yang::LeafDataTypes::IdentityRef)
+        return {};
+
+    std::set<std::string> identSet;
+
+    auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
+    auto insertToSet = [&identSet, prefixes, topLevelModule](auto module, auto name) {
+        std::string stringIdent;
+        if (prefixes == Prefixes::Always || topLevelModule != module) {
+            stringIdent += module;
+            stringIdent += ":";
+        }
+        stringIdent += name;
+        identSet.emplace(stringIdent);
+    };
+
+    auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(getSchemaNode(location, node));
+    auto info = leaf->type()->info();
+    for (auto base : info->ident()->ref()) { // Iterate over all bases
+        insertToSet(base->module()->name(), base->name());
+        // Iterate over derived identities (this is recursive!)
+        for (auto derived : base->der()->schema()) {
+            insertToSet(derived->module()->name(), derived->name());
+        }
+    }
+
+    return identSet;
+}
+
+bool YangSchema::leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const
+{
+    auto identities = validIdentities(location, node, Prefixes::Always);
+
+    auto topLevelModule = location.m_nodes.empty() ? node.first.get() : location.m_nodes.front().m_prefix.get().m_name;
+    auto identModule = value.first ? value.first.value() : topLevelModule;
+    return std::any_of(identities.begin(), identities.end(), [identModule, value](const auto& x) { return x == identModule + ":" + value.second; });
+}
+
 bool YangSchema::listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const
 {
     if (!isList(location, node))
@@ -206,6 +246,8 @@
         return yang::LeafDataTypes::Enum;
     case LY_TYPE_BINARY:
         return yang::LeafDataTypes::Binary;
+    case LY_TYPE_IDENT:
+        return yang::LeafDataTypes::IdentityRef;
     default:
         throw UnsupportedYangTypeException("the type of "s + fullNodeName(location, node) + " is not supported");
     }
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index e56c853..64e3ac5 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -35,10 +35,12 @@
     bool isList(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const override;
     bool leafEnumHasValue(const schemaPath_& location, const ModuleNodePair& node, const std::string& value) const override;
+    bool leafIdentityIsValid(const schemaPath_& location, const ModuleNodePair& node, const ModuleValuePair& value) const override;
     bool listHasKey(const schemaPath_& location, const ModuleNodePair& node, const std::string& key) const override;
     bool nodeExists(const std::string& location, const std::string& node) const override;
     const std::set<std::string> listKeys(const schemaPath_& location, const ModuleNodePair& node) const override;
     yang::LeafDataTypes leafType(const schemaPath_& location, const ModuleNodePair& node) const override;
+    const std::set<std::string> validIdentities(const schemaPath_& location, const ModuleNodePair& node, const Prefixes prefixes) const override;
     const std::set<std::string> enumValues(const schemaPath_& location, const ModuleNodePair& node) const override;
     std::set<std::string> childNodes(const schemaPath_& path, const Recursion recursion) const override;
 
diff --git a/tests/leaf_editing.cpp b/tests/leaf_editing.cpp
index f18af1c..925a238 100644
--- a/tests/leaf_editing.cpp
+++ b/tests/leaf_editing.cpp
@@ -15,6 +15,7 @@
 {
     auto schema = std::make_shared<StaticSchema>();
     schema->addModule("mod");
+    schema->addModule("pizza-module");
     schema->addContainer("", "mod:contA");
     schema->addLeaf("", "mod:leafString", yang::LeafDataTypes::String);
     schema->addLeaf("", "mod:leafDecimal", yang::LeafDataTypes::Decimal);
@@ -22,6 +23,14 @@
     schema->addLeaf("", "mod:leafInt", yang::LeafDataTypes::Int);
     schema->addLeaf("", "mod:leafUint", yang::LeafDataTypes::Uint);
     schema->addLeaf("", "mod:leafBinary", yang::LeafDataTypes::Binary);
+    schema->addIdentity(std::nullopt, ModuleValuePair{"mod", "food"});
+    schema->addIdentity(std::nullopt, ModuleValuePair{"mod", "vehicle"});
+    schema->addIdentity(ModuleValuePair{"mod", "food"}, ModuleValuePair{"mod", "pizza"});
+    schema->addIdentity(ModuleValuePair{"mod", "food"}, ModuleValuePair{"mod", "spaghetti"});
+    schema->addIdentity(ModuleValuePair{"mod", "pizza"}, ModuleValuePair{"pizza-module", "hawaii"});
+    schema->addLeafIdentityRef("", "mod:foodIdentRef", ModuleValuePair{"mod", "food"});
+    schema->addLeafIdentityRef("", "mod:pizzaIdentRef", ModuleValuePair{"mod", "pizza"});
+    schema->addLeafIdentityRef("mod:contA", "mod:identInCont", ModuleValuePair{"mod", "pizza"});
     schema->addLeafEnum("", "mod:leafEnum", {"lol", "data", "coze"});
     schema->addLeaf("mod:contA", "mod:leafInCont", yang::LeafDataTypes::String);
     schema->addList("", "mod:list", {"number"});
@@ -117,6 +126,82 @@
                 }
                 expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("leafBinary")});
             }
+
+            SECTION("identityRef")
+            {
+                SECTION("foodIdentRef")
+                {
+                    input = "set mod:foodIdentRef ";
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("foodIdentRef")});
+
+                    SECTION("food")
+                    {
+                        input += "food";
+                        expected.m_data = identityRef_("food");
+                    }
+                    SECTION("mod:food")
+                    {
+                        input += "mod:food";
+                        expected.m_data = identityRef_("mod", "food");
+                    }
+                    SECTION("pizza")
+                    {
+                        input += "pizza";
+                        expected.m_data = identityRef_("pizza");
+                    }
+                    SECTION("mod:pizza")
+                    {
+                        input += "mod:pizza";
+                        expected.m_data = identityRef_("mod", "pizza");
+                    }
+                    SECTION("pizza-module:hawaii")
+                    {
+                        input += "pizza-module:hawaii";
+                        expected.m_data = identityRef_("pizza-module", "hawaii");
+                    }
+                }
+                SECTION("pizzaIdentRef")
+                {
+                    input = "set mod:pizzaIdentRef ";
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("pizzaIdentRef")});
+                    SECTION("pizza")
+                    {
+                        input += "pizza";
+                        expected.m_data = identityRef_("pizza");
+                    }
+                    SECTION("mod:pizza")
+                    {
+                        input += "mod:pizza";
+                        expected.m_data = identityRef_("mod", "pizza");
+                    }
+                    SECTION("pizza-module:hawaii")
+                    {
+                        input += "pizza-module:hawaii";
+                        expected.m_data = identityRef_("pizza-module", "hawaii");
+                    }
+                }
+                SECTION("mod:contA/identInCont")
+                {
+                    input = "set mod:contA/identInCont ";
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, container_("contA")});
+                    expected.m_path.m_nodes.push_back(dataNode_(leaf_("identInCont")));
+                    SECTION("pizza")
+                    {
+                        input += "pizza";
+                        expected.m_data = identityRef_("pizza");
+                    }
+                    SECTION("mod:pizza")
+                    {
+                        input += "mod:pizza";
+                        expected.m_data = identityRef_("mod", "pizza");
+                    }
+                    SECTION("pizza-module:hawaii")
+                    {
+                        input += "pizza-module:hawaii";
+                        expected.m_data = identityRef_("pizza-module", "hawaii");
+                    }
+                }
+            }
         }
 
         command_ command = parser.parseCommand(input, errorStream);
@@ -183,6 +268,31 @@
                 input = "set leafBinary db=ahj";
         }
 
+        SECTION("non-existing identity")
+        {
+            input = "set mod:foodIdentRef identityBLABLA";
+        }
+
+        SECTION("setting identities with wrong bases")
+        {
+            SECTION("set mod:foodIdentRef mod:vehicle")
+            {
+                input = "set mod:foodIdentRef mod:vehicle";
+            }
+            SECTION("set mod:pizzaIdentRef mod:food")
+            {
+                input = "set mod:pizzaIdentRef mod:food";
+            }
+        }
+        SECTION("setting different module identities without prefix")
+        {
+            input = "set mod:pizzaIdentRef hawaii";
+        }
+        SECTION("identity prefix without name")
+        {
+            input = "set mod:contA/identInCont pizza-module:";
+        }
+
         REQUIRE_THROWS_AS(parser.parseCommand(input, errorStream), InvalidCommandException);
     }
 }
diff --git a/tests/yang.cpp b/tests/yang.cpp
index 5846a23..e19f991 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -18,6 +18,10 @@
         prefix "example";
     }
 
+    identity pineapple {
+        base "example:fruit";
+    }
+
     augment /example:a {
         container augmentedContainer {
         }
@@ -36,6 +40,28 @@
     namespace "http://example.com/example-sports";
     prefix coze;
 
+    identity drink {
+    }
+
+    identity voda {
+        base "drink";
+    }
+
+    identity food {
+    }
+
+    identity fruit {
+        base "food";
+    }
+
+    identity pizza {
+        base "food";
+    }
+
+    identity hawaii {
+        base "pizza";
+    }
+
     container a {
         container a2 {
             container a3 {
@@ -116,6 +142,25 @@
         type enumTypedefRestricted;
     }
 
+    leaf foodIdentLeaf {
+        type identityref {
+            base "food";
+        }
+    }
+
+    leaf pizzaIdentLeaf {
+        type identityref {
+            base "pizza";
+        }
+    }
+
+    leaf foodDrinkIdentLeaf {
+        type identityref {
+            base "food";
+            base "drink";
+        }
+    }
+
     list _list {
         key number;
 
@@ -279,6 +324,110 @@
 
             REQUIRE(ys.leafEnumHasValue(path, node, value));
         }
+        SECTION("leafIdentityIsValid")
+        {
+            ModuleValuePair value;
+
+            SECTION("foodIdentLeaf")
+            {
+                node.first = "example-schema";
+                node.second = "foodIdentLeaf";
+
+                SECTION("food")
+                {
+                    value.second = "food";
+                }
+                SECTION("example-schema:food")
+                {
+                    value.first = "example-schema";
+                    value.second = "food";
+                }
+                SECTION("pizza")
+                {
+                    value.second = "pizza";
+                }
+                SECTION("example-schema:pizza")
+                {
+                    value.first = "example-schema";
+                    value.second = "pizza";
+                }
+                SECTION("hawaii")
+                {
+                    value.second = "hawaii";
+                }
+                SECTION("example-schema:hawaii")
+                {
+                    value.first = "example-schema";
+                    value.second = "hawaii";
+                }
+                SECTION("fruit")
+                {
+                    value.second = "fruit";
+                }
+                SECTION("example-schema:fruit")
+                {
+                    value.first = "example-schema";
+                    value.second = "fruit";
+                }
+                SECTION("second-schema:pineapple")
+                {
+                    value.first = "second-schema";
+                    value.second = "pineapple";
+                }
+            }
+
+            SECTION("pizzaIdentLeaf")
+            {
+                node.first = "example-schema";
+                node.second = "pizzaIdentLeaf";
+
+                SECTION("pizza")
+                {
+                    value.second = "pizza";
+                }
+                SECTION("example-schema:pizza")
+                {
+                    value.first = "example-schema";
+                    value.second = "pizza";
+                }
+                SECTION("hawaii")
+                {
+                    value.second = "hawaii";
+                }
+                SECTION("example-schema:hawaii")
+                {
+                    value.first = "example-schema";
+                    value.second = "hawaii";
+                }
+            }
+
+            SECTION("foodDrinkIdentLeaf")
+            {
+                node.first = "example-schema";
+                node.second = "foodDrinkIdentLeaf";
+
+                SECTION("food")
+                {
+                    value.second = "food";
+                }
+                SECTION("example-schema:food")
+                {
+                    value.first = "example-schema";
+                    value.second = "food";
+                }
+                SECTION("drink")
+                {
+                    value.second = "drink";
+                }
+                SECTION("example-schema:drink")
+                {
+                    value.first = "example-schema";
+                    value.second = "drink";
+                }
+            }
+            REQUIRE(ys.leafIdentityIsValid(path, node, value));
+        }
+
         SECTION("listHasKey")
         {
             std::string key;
@@ -381,6 +530,7 @@
                        "example-schema:leafDecimal", "example-schema:leafBool", "example-schema:leafInt",
                        "example-schema:leafUint", "example-schema:leafEnum", "example-schema:leafEnumTypedef",
                        "example-schema:leafEnumTypedefRestricted", "example-schema:leafEnumTypedefRestricted2",
+                       "example-schema:foodIdentLeaf", "example-schema:pizzaIdentLeaf", "example-schema:foodDrinkIdentLeaf",
                        "example-schema:_list", "example-schema:twoKeyList", "second-schema:bla"};
             }
 
@@ -460,5 +610,43 @@
         {
             REQUIRE(!ys.isModule(path, "notAModule"));
         }
+
+        SECTION("leafIdentityIsValid")
+        {
+            ModuleValuePair value;
+            SECTION("pizzaIdentLeaf")
+            {
+                node.first = "example-schema";
+                node.second = "pizzaIdentLeaf";
+
+                SECTION("wrong base ident")
+                {
+                    SECTION("food")
+                    {
+                        value.second = "food";
+                    }
+                    SECTION("fruit")
+                    {
+                        value.second = "fruit";
+                    }
+                }
+                SECTION("non-existent identity")
+                {
+                    value.second = "nonexistent";
+                }
+                SECTION("weird module")
+                {
+                    value.first = "ahahaha";
+                    value.second = "pizza";
+                }
+            }
+            SECTION("different module identity, but withotu prefix")
+            {
+                node.first = "example-schema";
+                node.second = "foodIdentLeaf";
+                value.second = "pineapple";
+            }
+            REQUIRE_FALSE(ys.leafIdentityIsValid(path, node, value));
+        }
     }
 }