Merge "Rework path parsing"
diff --git a/src/common_parsers.hpp b/src/common_parsers.hpp
index eac8e61..8da90a6 100644
--- a/src/common_parsers.hpp
+++ b/src/common_parsers.hpp
@@ -34,6 +34,22 @@
 auto const space_separator_def =
     x3::omit[x3::no_skip[x3::space]];
 
+template <typename CoerceTo>
+struct as_type {
+    template <typename...> struct Tag{};
+
+    template <typename ParserType>
+    auto operator[](ParserType p) const {
+        return x3::rule<Tag<CoerceTo, ParserType>, CoerceTo> {"as"} = x3::as_parser(p);
+    }
+};
+
+// The `as` parser creates an ad-hoc x3::rule with the attribute specified with `CoerceTo`.
+// Example usage: as<std::string>[someParser]
+// someParser will have its attribute coerced to std::string
+// https://github.com/boostorg/spirit/issues/530#issuecomment-584836532
+template <typename CoerceTo> const as_type<CoerceTo> as{};
+
 BOOST_SPIRIT_DEFINE(node_identifier)
 BOOST_SPIRIT_DEFINE(module)
 BOOST_SPIRIT_DEFINE(module_identifier)
diff --git a/src/leaf_data.hpp b/src/leaf_data.hpp
index 68705d0..5a181e5 100644
--- a/src/leaf_data.hpp
+++ b/src/leaf_data.hpp
@@ -22,8 +22,6 @@
 x3::rule<struct leaf_data_class<yang::Binary>, binary_> const leaf_data_binary = "leaf_data_binary";
 x3::rule<struct leaf_data_class<yang::Decimal>, double> const leaf_data_decimal = "leaf_data_decimal";
 x3::rule<struct leaf_data_class<yang::String>, std::string> const leaf_data_string = "leaf_data_string";
-x3::rule<struct leaf_data_class_binary, std::string> const leaf_data_binary_data = "leaf_data_binary_data";
-x3::rule<struct leaf_data_identityRef_data_class, identityRef_> const leaf_data_identityRef_data = "leaf_data_identityRef_data";
 
 using x3::char_;
 
@@ -43,22 +41,11 @@
     '\'' >> *(char_-'\'') >> '\'' |
     '\"' >> *(char_-'\"') >> '\"';
 
-// This intermediate rule is neccessary for coercing to std::string.
-// TODO: check if I can do the coercing right in the grammar with `as{}` from
-// https://github.com/boostorg/spirit/issues/530#issuecomment-584836532
-// This would shave off some more lines.
-auto const leaf_data_binary_data_def =
-    +(x3::alnum | char_('+') | char_('/')) >> -char_('=') >> -char_('=');
-
 auto const leaf_data_binary_def =
-    leaf_data_binary_data;
+    as<std::string>[+(x3::alnum | char_('+') | char_('/')) >> -char_('=') >> -char_('=')];
 
-auto const leaf_data_identityRef_data_def =
-    -module  >> node_identifier;
-
-// TODO: get rid of this and use leaf_data_identityRef_data directly
 auto const leaf_data_identityRef_def =
-    leaf_data_identityRef_data;
+    -module >> node_identifier;
 
 template <typename It, typename Ctx, typename RCtx, typename Attr>
 struct impl_LeafData {
@@ -130,44 +117,29 @@
     bool operator()(const yang::Enum& type) const
     {
         createSetSuggestions(type);
-        // leaf_data_enum will advance the iterator if it succeeds, so I have
-        // to save the iterator here, to roll it back in case the enum is
-        // invalid.
-        auto saveIter = first;
-        auto pass = leaf_data_enum.parse(first, last, ctx, rctx, attr);
-        if (!pass) {
-            return false;
-        }
-        auto isValidEnum = type.m_allowedValues.count(boost::get<enum_>(attr)) != 0;
-        if (!isValidEnum) {
-            first = saveIter;
-            parserContext.m_errorMsg = "leaf data type mismatch: Expected an enum here. Allowed values:";
-            for (const auto& it : type.m_allowedValues) {
-                parserContext.m_errorMsg += " " + it.m_value;
+        auto checkValidEnum = [this, type] (auto& ctx) {
+            if (type.m_allowedValues.count(boost::get<enum_>(attr)) == 0) {
+                _pass(ctx) = false;
+                parserContext.m_errorMsg = "leaf data type mismatch: Expected an enum here. Allowed values:";
+                for (const auto& it : type.m_allowedValues) {
+                    parserContext.m_errorMsg += " " + it.m_value;
+                }
             }
-        }
-        return isValidEnum;
+        };
+        return leaf_data_enum[checkValidEnum].parse(first, last, ctx, rctx, attr);
     }
     bool operator()(const yang::IdentityRef& type) const
     {
         createSetSuggestions(type);
-        // leaf_data_identityRef will advance the iterator if it succeeds, so I have
-        // to save the iterator here, to roll it back in case the enum is
-        // invalid.
-        auto saveIter = first;
-        auto pass = leaf_data_identityRef.parse(first, last, ctx, rctx, attr);
-        if (!pass) {
-            return false;
-        }
-        identityRef_ pair{boost::get<identityRef_>(attr)};
-        if (!pair.m_prefix) {
-            pair.m_prefix = module_{parserContext.currentSchemaPath().m_nodes.front().m_prefix.get().m_name};
-        }
-        auto isValidIdentity  = type.m_allowedValues.count(pair) != 0;
-        if (!isValidIdentity) {
-            first = saveIter;
-        }
-        return isValidIdentity;
+        auto checkValidIdentity = [this, type] (auto& ctx) {
+            identityRef_ pair{boost::get<identityRef_>(_attr(ctx))};
+            if (!pair.m_prefix) {
+                pair.m_prefix = module_{parserContext.currentSchemaPath().m_nodes.front().m_prefix.get().m_name};
+            }
+            _pass(ctx) = type.m_allowedValues.count(pair) != 0;
+        };
+
+        return leaf_data_identityRef[checkValidIdentity].parse(first, last, ctx, rctx, attr);
     }
     bool operator()(const yang::LeafRef& leafRef) const
     {
@@ -207,7 +179,5 @@
 
 BOOST_SPIRIT_DEFINE(leaf_data_enum)
 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)
diff --git a/src/libyang_utils.cpp b/src/libyang_utils.cpp
index 9051e16..ddb3630 100644
--- a/src/libyang_utils.cpp
+++ b/src/libyang_utils.cpp
@@ -27,8 +27,10 @@
         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 std::string{value->binary()};
+        return binary_{value->binary()};
     case LY_TYPE_DEC64:
     {
         auto v = value->dec64();
diff --git a/src/schema.hpp b/src/schema.hpp
index f11265f..0a9b2ff 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -15,8 +15,6 @@
 #include "ast_path.hpp"
 #include "leaf_data_type.hpp"
 
-using ModuleValuePair = std::pair<boost::optional<std::string>, std::string>;
-
 namespace yang {
 enum class NodeTypes {
     Container,
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index d554cb9..aecad5a 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -73,15 +73,10 @@
 
 std::set<identityRef_> StaticSchema::validIdentities(std::string_view module, std::string_view value)
 {
-    std::set<ModuleValuePair> identities;
-    getIdentSet(ModuleNodePair{boost::optional<std::string>{module}, value}, identities);
-    std::set<identityRef_> res;
+    std::set<identityRef_> identities;
+    getIdentSet(identityRef_{std::string{module}, std::string{value}}, identities);
 
-    std::transform(identities.begin(), identities.end(), std::inserter(res, res.end()), [](const auto& identity) {
-        return identityRef_{*identity.first, identity.second};
-    });
-
-    return res;
+    return identities;
 }
 
 void StaticSchema::addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type)
@@ -96,15 +91,15 @@
     m_modules.emplace(name);
 }
 
-void StaticSchema::addIdentity(const std::optional<ModuleValuePair>& base, const ModuleValuePair& name)
+void StaticSchema::addIdentity(const std::optional<identityRef_>& base, const identityRef_& name)
 {
     if (base)
         m_identities.at(base.value()).emplace(name);
 
-    m_identities.emplace(name, std::set<ModuleValuePair>());
+    m_identities.emplace(name, std::set<identityRef_>());
 }
 
-void StaticSchema::getIdentSet(const ModuleValuePair& ident, std::set<ModuleValuePair>& res) const
+void StaticSchema::getIdentSet(const identityRef_& ident, std::set<identityRef_>& res) const
 {
     res.insert(ident);
     auto derivedIdentities = m_identities.at(ident);
@@ -135,15 +130,6 @@
     return boost::get<yang::leaf>(children(locationString).at(node)).m_type;
 }
 
-ModuleNodePair splitModuleNode(const std::string& input)
-{
-    auto colonLocation = input.find_first_of(':');
-    if (colonLocation != std::string::npos) {
-        return ModuleNodePair{input.substr(0, colonLocation), input.substr(colonLocation + 1)};
-    }
-    throw std::logic_error("Tried to split a string without a colon (StaticSchema node names should always be stored with prefixes)");
-}
-
 std::set<ModuleNodePair> StaticSchema::availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const
 {
     if (recursion == Recursion::Recursive) {
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index 67bd7b9..5184ae6 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -69,16 +69,15 @@
     void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type);
     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);
+    void addIdentity(const std::optional<identityRef_>& base, const identityRef_& 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;
+    void getIdentSet(const identityRef_& ident, std::set<identityRef_>& res) const;
     bool nodeExists(const std::string& location, const std::string& node) const;
 
     std::unordered_map<std::string, std::unordered_map<std::string, NodeType>> m_nodes;
     std::set<std::string> m_modules;
 
-    // FIXME: Change the template arguments to identityRef_
-    std::map<ModuleValuePair, std::set<ModuleValuePair>> m_identities;
+    std::map<identityRef_, std::set<identityRef_>> m_identities;
 };
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 7115ad7..b40b9f1 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -40,6 +40,13 @@
         return std::string(value->data()->get_string());
     case SR_ENUM_T:
         return enum_{std::string(value->data()->get_enum())};
+    case SR_IDENTITYREF_T:
+    {
+        auto pair = splitModuleNode(value->data()->get_identityref());
+        return identityRef_{*pair.first, pair.second};
+    }
+    case SR_BINARY_T:
+        return binary_{value->data()->get_binary()};
     case SR_DECIMAL64_T:
         return value->data()->get_decimal64();
     case SR_CONTAINER_T:
@@ -66,7 +73,7 @@
 
     sysrepo::S_Val operator()(const identityRef_& value) const
     {
-        auto res = value.m_prefix.value().m_name + ":" + value.m_value;
+        auto res = value.m_prefix ? (value.m_prefix.value().m_name + ":" + value.m_value) : value.m_value;
         return std::make_shared<sysrepo::Val>(res.c_str(), SR_IDENTITYREF_T);
     }
 
diff --git a/src/utils.cpp b/src/utils.cpp
index 1ed25c7..665aedd 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -53,6 +53,15 @@
     return schemaPath_{path.m_scope, decltype(schemaPath_::m_nodes)(path.m_nodes.begin(), path.m_nodes.end() - 1)};
 }
 
+ModuleNodePair splitModuleNode(const std::string& input)
+{
+    auto colonLocation = input.find_first_of(':');
+    if (colonLocation != std::string::npos) {
+        return ModuleNodePair{input.substr(0, colonLocation), input.substr(colonLocation + 1)};
+    }
+    throw std::logic_error("Internal error: got module-unqualified node name");
+}
+
 struct impl_leafDataTypeToString {
     std::string operator()(const yang::String)
     {
@@ -156,7 +165,7 @@
 
     std::string operator()(const identityRef_& data) const
     {
-        return data.m_prefix.value().m_name + ":" + data.m_value;
+        return data.m_prefix ? (data.m_prefix.value().m_name + ":" + data.m_value) : data.m_value;
     }
 
     std::string operator()(const special_& data) const
diff --git a/src/utils.hpp b/src/utils.hpp
index 7e62ae5..781170d 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -19,6 +19,7 @@
 std::string stripLastNodeFromPath(const std::string& path);
 schemaPath_ pathWithoutLastNode(const schemaPath_& path);
 dataPath_ pathWithoutLastNode(const dataPath_& path);
+ModuleNodePair splitModuleNode(const std::string& input);
 std::string leafDataTypeToString(const yang::LeafDataType& type);
 std::string fullNodeName(const schemaPath_& location, const ModuleNodePair& pair);
 std::string fullNodeName(const dataPath_& location, const ModuleNodePair& pair);
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index d5f3e12..51bcd81 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -319,6 +319,46 @@
         REQUIRE(datastore.getItems("/example-schema:leafDecimal") == expected);
     }
 
+    SECTION("unions")
+    {
+        datastore.setLeaf("/example-schema:unionIntString", int32_t{10});
+        REQUIRE_CALL(mock, write("/example-schema:unionIntString", std::nullopt, "10"s));
+        datastore.commitChanges();
+        DatastoreAccess::Tree expected {
+            {"/example-schema:unionIntString", int32_t{10}},
+        };
+        REQUIRE(datastore.getItems("/example-schema:unionIntString") == expected);
+    }
+
+    SECTION("identityref") {
+        datastore.setLeaf("/example-schema:beast", identityRef_{"example-schema", "Mammal"});
+        REQUIRE_CALL(mock, write("/example-schema:beast", std::nullopt, "example-schema:Mammal"s));
+        datastore.commitChanges();
+        DatastoreAccess::Tree expected {
+            {"/example-schema:beast", identityRef_{"example-schema", "Mammal"}},
+        };
+        REQUIRE(datastore.getItems("/example-schema:beast") == expected);
+
+        datastore.setLeaf("/example-schema:beast", identityRef_{"Whale"});
+        REQUIRE_CALL(mock, write("/example-schema:beast", "example-schema:Mammal", "example-schema:Whale"s));
+        datastore.commitChanges();
+        expected = {
+            {"/example-schema:beast", identityRef_{"example-schema", "Whale"}},
+        };
+        REQUIRE(datastore.getItems("/example-schema:beast") == expected);
+    }
+
+    SECTION("binary")
+    {
+        datastore.setLeaf("/example-schema:blob", binary_{"cHduegByIQ=="s});
+        REQUIRE_CALL(mock, write("/example-schema:blob", std::nullopt, "cHduegByIQ=="s));
+        datastore.commitChanges();
+        DatastoreAccess::Tree expected {
+            {"/example-schema:blob", binary_{"cHduegByIQ=="s}},
+        };
+        REQUIRE(datastore.getItems("/example-schema:blob") == expected);
+    }
+
     SECTION("operational data")
     {
         MockDataSupplier mockOpsData;
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index ac7724a..5d84eda 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -214,4 +214,33 @@
         type int32;
         config false;
     }
+
+    identity Animal {
+    }
+
+    identity Mammal {
+        base "Animal";
+    }
+
+    identity Dog {
+        base "Mammal";
+    }
+
+    identity Whale {
+        base "Mammal";
+    }
+
+    identity Velociraptor {
+        base "Animal";
+    }
+
+    leaf beast {
+        type identityref {
+            base "Animal";
+        }
+    }
+
+    leaf blob {
+        type binary;
+    }
 }
diff --git a/tests/leaf_editing.cpp b/tests/leaf_editing.cpp
index 64abbf2..986139d 100644
--- a/tests/leaf_editing.cpp
+++ b/tests/leaf_editing.cpp
@@ -37,11 +37,11 @@
     schema->addLeaf("/", "mod:leafUint32", yang::Uint32{});
     schema->addLeaf("/", "mod:leafUint64", yang::Uint64{});
     schema->addLeaf("/", "mod:leafBinary", yang::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->addIdentity(std::nullopt, identityRef_{"mod", "food"});
+    schema->addIdentity(std::nullopt, identityRef_{"mod", "vehicle"});
+    schema->addIdentity(identityRef_{"mod", "food"}, identityRef_{"mod", "pizza"});
+    schema->addIdentity(identityRef_{"mod", "food"}, identityRef_{"mod", "spaghetti"});
+    schema->addIdentity(identityRef_{"mod", "pizza"}, identityRef_{"pizza-module", "hawaii"});
     schema->addLeaf("/", "mod:foodIdentRef", yang::IdentityRef{schema->validIdentities("mod", "food")});
     schema->addLeaf("/", "mod:pizzaIdentRef", yang::IdentityRef{schema->validIdentities("mod", "pizza")});
     schema->addLeaf("/mod:contA", "mod:identInCont", yang::IdentityRef{schema->validIdentities("mod", "pizza")});