Add support for union leafs
Change-Id: Ifc1a53eed2c059f6fe5d75544ecaa3e63028f78f
diff --git a/src/leaf_data.hpp b/src/leaf_data.hpp
index 7139fc4..1c4176e 100644
--- a/src/leaf_data.hpp
+++ b/src/leaf_data.hpp
@@ -173,6 +173,12 @@
{
return std::visit(*this, *leafRef.m_targetType);
}
+ bool operator()(const yang::Union& unionInfo) const
+ {
+ return std::any_of(unionInfo.m_unionTypes.begin(), unionInfo.m_unionTypes.end(), [this](const auto& type) {
+ return std::visit(*this, type);
+ });
+ }
};
struct LeafData : x3::parser<LeafData> {
diff --git a/src/leaf_data_type.cpp b/src/leaf_data_type.cpp
index 815d0aa..8048efa 100644
--- a/src/leaf_data_type.cpp
+++ b/src/leaf_data_type.cpp
@@ -39,6 +39,10 @@
{
return this->m_targetXPath == other.m_targetXPath && *this->m_targetType == *other.m_targetType;
}
+bool Union::operator==(const Union& other) const
+{
+ return this->m_unionTypes == other.m_unionTypes;
+}
bool String::operator==(const String&) const
{
return true;
diff --git a/src/leaf_data_type.hpp b/src/leaf_data_type.hpp
index 66f4522..a86691b 100644
--- a/src/leaf_data_type.hpp
+++ b/src/leaf_data_type.hpp
@@ -10,6 +10,7 @@
#include <set>
#include <string>
#include <variant>
+#include <vector>
struct enum_;
struct identityRef_;
@@ -62,6 +63,7 @@
std::set<identityRef_> m_allowedValues;
};
struct LeafRef;
+struct Union;
using LeafDataType = std::variant<
yang::String,
yang::Decimal,
@@ -77,7 +79,8 @@
yang::Enum,
yang::Binary,
yang::IdentityRef,
- yang::LeafRef
+ yang::LeafRef,
+ yang::Union
>;
struct LeafRef {
LeafRef(const LeafRef& src);
@@ -86,4 +89,9 @@
std::string m_targetXPath;
std::unique_ptr<LeafDataType> m_targetType;
};
+
+struct Union {
+ bool operator==(const Union& other) const;
+ std::vector<LeafDataType> m_unionTypes;
+};
}
diff --git a/src/utils.cpp b/src/utils.cpp
index 719f28b..31854b2 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -5,6 +5,7 @@
* Written by Václav Kubernát <kubervac@fit.cvut.cz>
*
*/
+#include <experimental/iterator>
#include <sstream>
#include "completion.hpp"
#include "utils.hpp"
@@ -113,6 +114,14 @@
{
return "a leafref";
}
+ std::string operator()(const yang::Union& type)
+ {
+ std::ostringstream ss;
+ std::transform(type.m_unionTypes.begin(), type.m_unionTypes.end(), std::experimental::make_ostream_joiner(ss, ", "), [this](const auto& unionType) {
+ return std::visit(*this, unionType);
+ });
+ return ss.str();
+ }
};
std::string leafDataTypeToString(const yang::LeafDataType& type)
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 0f1a3fd..a453b50 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -154,9 +154,9 @@
}
namespace {
-std::set<enum_> enumValues(const libyang::S_Schema_Node_Leaf& leaf)
+std::set<enum_> enumValues(const libyang::S_Type& typeArg)
{
- auto type = leaf->type();
+ auto type = typeArg;
auto enm = type->info()->enums()->enm();
// The enum can be a derived type and enm() only returns values,
// if that specific typedef changed the possible values. So we go
@@ -177,13 +177,13 @@
return enumSet;
}
-std::set<identityRef_> validIdentities(const libyang::S_Schema_Node_Leaf& leaf)
+std::set<identityRef_> validIdentities(const libyang::S_Type& type)
{
std::set<identityRef_> identSet;
// auto topLevelModule = leaf->module();
- auto info = leaf->type()->info();
+ auto info = type->info();
for (auto base : info->ident()->ref()) { // Iterate over all bases
identSet.emplace(base->module()->name(), base->name());
// Iterate over derived identities (this is recursive!)
@@ -195,9 +195,9 @@
return identSet;
}
-std::string leafrefPath(const libyang::S_Schema_Node_Leaf& leaf)
+std::string leafrefPath(const libyang::S_Type& type)
{
- return leaf->type()->info()->lref()->target()->path(LYS_PATH_FIRST_PREFIX);
+ return type->info()->lref()->target()->path(LYS_PATH_FIRST_PREFIX);
}
}
@@ -205,42 +205,53 @@
{
using namespace std::string_literals;
auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(node);
- auto baseType{leaf->type()->base()};
- switch (baseType) {
- case LY_TYPE_STRING:
- return yang::String{};
- case LY_TYPE_DEC64:
- return yang::Decimal{};
- case LY_TYPE_BOOL:
- return yang::Bool{};
- case LY_TYPE_INT8:
- return yang::Int8{};
- case LY_TYPE_INT16:
- return yang::Int16{};
- case LY_TYPE_INT32:
- return yang::Int32{};
- case LY_TYPE_INT64:
- return yang::Int64{};
- case LY_TYPE_UINT8:
- return yang::Uint8{};
- case LY_TYPE_UINT16:
- return yang::Uint16{};
- case LY_TYPE_UINT32:
- return yang::Uint32{};
- case LY_TYPE_UINT64:
- return yang::Uint64{};
- case LY_TYPE_BINARY:
- return yang::Binary{};
- case LY_TYPE_ENUM:
- return yang::Enum{enumValues(leaf)};
- case LY_TYPE_IDENT:
- return yang::IdentityRef{validIdentities(leaf)};
- case LY_TYPE_LEAFREF:
- return yang::LeafRef{::leafrefPath(leaf), std::make_unique<yang::LeafDataType>(leafType(::leafrefPath(leaf)))};
- default:
- using namespace std::string_literals;
- throw UnsupportedYangTypeException("the type of "s + node->name() + " is not supported: " + std::to_string(baseType));
- }
+ std::function<yang::LeafDataType(std::shared_ptr<libyang::Type>)> resolveType;
+ resolveType = [this, &resolveType, leaf] (auto type) -> yang::LeafDataType {
+ switch (type->base()) {
+ case LY_TYPE_STRING:
+ return yang::String{};
+ case LY_TYPE_DEC64:
+ return yang::Decimal{};
+ case LY_TYPE_BOOL:
+ return yang::Bool{};
+ case LY_TYPE_INT8:
+ return yang::Int8{};
+ case LY_TYPE_INT16:
+ return yang::Int16{};
+ case LY_TYPE_INT32:
+ return yang::Int32{};
+ case LY_TYPE_INT64:
+ return yang::Int64{};
+ case LY_TYPE_UINT8:
+ return yang::Uint8{};
+ case LY_TYPE_UINT16:
+ return yang::Uint16{};
+ case LY_TYPE_UINT32:
+ return yang::Uint32{};
+ case LY_TYPE_UINT64:
+ return yang::Uint64{};
+ case LY_TYPE_BINARY:
+ return yang::Binary{};
+ case LY_TYPE_ENUM:
+ return yang::Enum{enumValues(type)};
+ case LY_TYPE_IDENT:
+ return yang::IdentityRef{validIdentities(type)};
+ case LY_TYPE_LEAFREF:
+ return yang::LeafRef{::leafrefPath(type), std::make_unique<yang::LeafDataType>(leafType(::leafrefPath(type)))};
+ case LY_TYPE_UNION:
+ {
+ auto res = yang::Union{};
+ for (auto unionType : type->info()->uni()->types()) {
+ res.m_unionTypes.push_back(resolveType(unionType));
+ }
+ return res;
+ }
+ default:
+ using namespace std::string_literals;
+ throw UnsupportedYangTypeException("the type of "s + leaf->name() + " is not supported: " + std::to_string(leaf->type()->base()));
+ }
+ };
+ return resolveType(leaf->type());
}
yang::LeafDataType YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
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);
}