Add support for union leafs

Change-Id: Ifc1a53eed2c059f6fe5d75544ecaa3e63028f78f
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index f876779..5ef67d5 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -80,6 +80,13 @@
         }
     }
 
+    leaf unionIntString {
+        type union {
+            type int32;
+            type string;
+        }
+    }
+
     grouping upAndDown {
         leaf up {
             type boolean;
diff --git a/tests/leaf_editing.cpp b/tests/leaf_editing.cpp
index 9ce5c35..ad88a99 100644
--- a/tests/leaf_editing.cpp
+++ b/tests/leaf_editing.cpp
@@ -53,6 +53,25 @@
     schema->addLeaf("/", "mod:refToString", yang::LeafRef{"/mod:leafString", std::make_unique<yang::LeafDataType>(schema->leafType("/mod:leafString"))});
     schema->addLeaf("/", "mod:refToInt8", yang::LeafRef{"/mod:leafInt8", std::make_unique<yang::LeafDataType>(schema->leafType("/mod:leafInt8"))});
     schema->addLeaf("/", "mod:refToLeafInCont", yang::LeafRef{"/mod:contA/identInCont", std::make_unique<yang::LeafDataType>(schema->leafType("/mod:contA/mod:identInCont"))});
+    schema->addLeaf("/", "mod:intOrString", yang::Union{{yang::Int32{}, yang::String{}}});
+    schema->addLeaf("/", "mod:twoInts", yang::Union{{yang::Uint8{}, yang::Int16{}}});
+    schema->addLeaf("/", "mod:unionStringEnumLeafref", yang::Union{{
+        yang::String{},
+        createEnum({"foo", "bar"}),
+        yang::LeafRef{"/mod:leafEnum", std::make_unique<yang::LeafDataType>(schema->leafType("/mod:leafEnum"))}
+    }});
+
+    schema->addList("/", "mod:portSettings", {"port"});
+    schema->addLeaf("/mod:portSettings", "mod:port", createEnum({"eth0", "eth1", "eth2"}));
+    schema->addList("/", "mod:portMapping", {"port"});
+    schema->addLeaf("/mod:portMapping", "mod:port", createEnum({"utf1", "utf2", "utf3"}));
+    schema->addLeaf("/", "mod:activeMappedPort", yang::LeafRef{"/mod:portMapping/mod:port", std::make_unique<yang::LeafDataType>(schema->leafType("/mod:portMapping/mod:port"))});
+    schema->addLeaf("/", "mod:activePort", yang::Union{{
+        createEnum({"wlan0", "wlan1"}),
+        yang::LeafRef{"/mod:portSettings/mod:port", std::make_unique<yang::LeafDataType>(schema->leafType("/mod:portSettings/mod:port"))},
+        yang::LeafRef{"/mod:activeMappedPort", std::make_unique<yang::LeafDataType>(schema->leafType("/mod:activeMappedPort"))},
+    }});
+
     Parser parser(schema);
     std::string input;
     std::ostringstream errorStream;
@@ -203,6 +222,112 @@
                 expected.m_data = true;
             }
 
+            SECTION("union")
+            {
+                SECTION("int")
+                {
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("intOrString")});
+                    input = "set mod:intOrString 90";
+                    expected.m_data = int32_t{90};
+                }
+                SECTION("string")
+                {
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("intOrString")});
+                    input = "set mod:intOrString \"test\"";
+                    expected.m_data = std::string{"test"};
+                }
+
+                SECTION("union with two integral types")
+                {
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("twoInts")});
+                    SECTION("uint8")
+                    {
+                        input = "set mod:twoInts 100";
+                        expected.m_data = uint8_t{100};
+                    }
+                    SECTION("int16")
+                    {
+                        input = "set mod:twoInts 6666";
+                        expected.m_data = int16_t{6666};
+                    }
+                }
+
+                SECTION("union with enum and leafref to enum")
+                {
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("unionStringEnumLeafref")});
+                    SECTION("string")
+                    {
+                        input = "set mod:unionStringEnumLeafref \"AHOJ\"";
+                        expected.m_data = std::string{"AHOJ"};
+                    }
+                    SECTION("enum")
+                    {
+                        input = "set mod:unionStringEnumLeafref bar";
+                        expected.m_data = enum_("bar");
+                    }
+                    SECTION("enum leafref")
+                    {
+                        input = "set mod:unionStringEnumLeafref coze";
+                        expected.m_data = enum_("coze");
+                    }
+                }
+
+                SECTION("activePort")
+                {
+                    expected.m_path.m_nodes.push_back(dataNode_{module_{"mod"}, leaf_("activePort")});
+                    input = "set mod:activePort ";
+                    SECTION("1. anonymous enum")
+                    {
+                        SECTION("wlan0")
+                        {
+                            input += "wlan0";
+                            expected.m_data = enum_("wlan0");
+                        }
+                        SECTION("wlan1")
+                        {
+                            input += "wlan1";
+                            expected.m_data = enum_("wlan1");
+                        }
+                    }
+                    SECTION("2. leafref to enum")
+                    {
+                        SECTION("eth0")
+                        {
+                            input += "eth0";
+                            expected.m_data = enum_("eth0");
+                        }
+                        SECTION("eth1")
+                        {
+                            input += "eth1";
+                            expected.m_data = enum_("eth1");
+                        }
+                        SECTION("eth2")
+                        {
+                            input += "eth2";
+                            expected.m_data = enum_("eth2");
+                        }
+                    }
+                    SECTION("3. leafref to leafref")
+                    {
+                        SECTION("utf1")
+                        {
+                            input += "utf1";
+                            expected.m_data = enum_("utf1");
+                        }
+                        SECTION("utf2")
+                        {
+                            input += "utf2";
+                            expected.m_data = enum_("utf2");
+                        }
+                        SECTION("utf3")
+                        {
+                            input += "utf3";
+                            expected.m_data = enum_("utf3");
+                        }
+                    }
+                }
+            }
+
             SECTION("binary")
             {
                 SECTION("zero ending '='")
@@ -454,6 +579,11 @@
             input = "set mod:contA/identInCont pizza-module:";
         }
 
+        SECTION("set a union path to a wrong type")
+        {
+            input = "set mod:intOrString true";
+        }
+
         REQUIRE_THROWS_AS(parser.parseCommand(input, errorStream), InvalidCommandException);
         REQUIRE(errorStream.str().find(expectedError) != std::string::npos);
     }
diff --git a/tests/pretty_printers.hpp b/tests/pretty_printers.hpp
index 3f592a5..e5b6659 100644
--- a/tests/pretty_printers.hpp
+++ b/tests/pretty_printers.hpp
@@ -63,6 +63,14 @@
         });
         s << "}";
     }
+    if (std::holds_alternative<yang::LeafRef>(type)) {
+        s << "{" << std::get<yang::LeafRef>(type).m_targetXPath << "," << *std::get<yang::LeafRef>(type).m_targetType  << "}";
+    }
+    if (std::holds_alternative<yang::Union>(type)) {
+        s << "{" << std::endl;
+        auto types = std::get<yang::Union>(type).m_unionTypes;
+        std::copy(types.begin(), types.end(), std::experimental::make_ostream_joiner(s, ",\n"));
+    }
     s << std::endl;
     return s;
 }
diff --git a/tests/utils.cpp b/tests/utils.cpp
index 91c61cb..cea698b 100644
--- a/tests/utils.cpp
+++ b/tests/utils.cpp
@@ -8,6 +8,7 @@
 
 #include "trompeloeil_doctest.hpp"
 #include "completion.hpp"
+#include "leaf_data_helpers.hpp"
 #include "utils.hpp"
 
 TEST_CASE("utils")
@@ -80,4 +81,24 @@
 
         REQUIRE(joinPaths(prefix, suffix) == result);
     }
+
+    SECTION("leafDataTypeToString")
+    {
+        yang::LeafDataType type;
+        std::string expected;
+        SECTION("union")
+        {
+            type = yang::Union{{
+                yang::String{},
+                createEnum({"foo", "bar"}),
+                yang::Int8{},
+                yang::Int64{},
+            }};
+            expected = "a string, an enum, an 8-bit integer, a 64-bit integer";
+        }
+
+        REQUIRE(leafDataTypeToString(type) == expected);
+
+    }
+
 }
diff --git a/tests/yang.cpp b/tests/yang.cpp
index dcf4354..a9fb65e 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -299,6 +299,61 @@
 
     rpc myRpc {}
 
+    leaf numberOrString {
+        type union {
+            type int32;
+            type string;
+        }
+        description "Can be an int32 or a string.";
+    }
+
+    list portSettings {
+        key "port";
+        leaf port {
+            type enumeration {
+                enum eth0;
+                enum eth1;
+                enum eth2;
+            }
+        }
+    }
+
+    feature weirdPortNames;
+
+    list portMapping {
+        key "port";
+        leaf port {
+            type enumeration {
+                enum WEIRD {
+                    if-feature "weirdPortNames";
+                }
+                enum utf2;
+                enum utf3;
+            }
+        }
+    }
+
+    leaf activeMappedPort {
+        type leafref {
+            path "../portMapping/port";
+        }
+    }
+
+    leaf activePort {
+        type union {
+            type enumeration {
+                enum wlan0;
+                enum wlan1;
+            }
+            type leafref {
+                path "../portSettings/port";
+            }
+            type leafref {
+                path "../activeMappedPort";
+            }
+        }
+    }
+
 })";
 
 namespace std {
@@ -644,6 +699,39 @@
                 );
             }
 
+            SECTION("activePort")
+            {
+                node.first = "example-schema";
+                node.second = "activePort";
+
+                yang::Enum enums = [&ys]() {
+                    SECTION("weird ports disabled")
+                    {
+                        return createEnum({"utf2", "utf3"});
+                    }
+                    SECTION("weird ports enabled")
+                    {
+                        ys.enableFeature("example-schema", "weirdPortNames");
+                        return createEnum({"WEIRD", "utf2", "utf3"});
+                    }
+                    __builtin_unreachable();
+                }();
+
+                type = yang::Union{{
+                    createEnum({"wlan0", "wlan1"}),
+                    yang::LeafRef{
+                        "/example-schema:portSettings/port",
+                        std::make_unique<yang::LeafDataType>(createEnum({"eth0", "eth1", "eth2"}))
+                    },
+                    yang::LeafRef{
+                        "/example-schema:activeMappedPort",
+                        std::make_unique<yang::LeafDataType>(yang::LeafRef{
+                                "/example-schema:portMapping/port",
+                                std::make_unique<yang::LeafDataType>(enums)
+                        })},
+                }};
+            }
+
             REQUIRE(ys.leafType(path, node) == type);
         }
         SECTION("childNodes")
@@ -668,7 +756,12 @@
                        "example-schema:pizzaSize",
                        "example-schema:length", "example-schema:wavelength",
                        "example-schema:duration", "example-schema:another-duration",
-                       "example-schema:activeNumber"};
+                       "example-schema:activeNumber",
+                       "example-schema:numberOrString",
+                       "example-schema:portSettings",
+                       "example-schema:portMapping",
+                       "example-schema:activeMappedPort",
+                       "example-schema:activePort"};
             }
 
             SECTION("example-schema:a")
@@ -737,6 +830,12 @@
                 path.m_nodes.push_back(schemaNode_(module_{"example-schema"}, leaf_("leafString")));
             }
 
+            SECTION("numberOrString")
+            {
+                path.m_nodes.push_back(schemaNode_(module_{"example-schema"}, leaf_("numberOrString")));
+                expected = "Can be an int32 or a string.";
+            }
+
             REQUIRE(ys.description(pathToSchemaString(path, Prefixes::WhenNeeded)) == expected);
         }