diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index 11683d7..7979667 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -10,6 +10,7 @@ import std.string; import std.typecons; import std.range; import std.traits; +import std.variant : VariantN; void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { foreach(index, arg; args) { @@ -56,6 +57,19 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { dbus_message_iter_close_container(&sub, &entry); } dbus_message_iter_close_container(iter, &sub); + } else static if(isInstanceOf!(VariantN, T)) { + enforce(arg.hasValue, + new InvalidValueException(arg, "dbus:" ~ cast(char) typeCode!T)); + + DBusMessageIter sub; + foreach(AT; T.AllowedTypes) { + if (arg.peek!AT) { + dbus_message_iter_open_container(iter, 'v', typeSig!AT.ptr, &sub); + buildIter(&sub, arg.get!AT); + dbus_message_iter_close_container(iter, &sub); + break; + } + } } else static if(is(T == DBusAny) || is(T == Variant!DBusAny)) { static if(is(T == Variant!DBusAny)) { auto val = arg.data; @@ -171,10 +185,11 @@ T readIter(T)(DBusMessageIter *iter) if (isInstanceOf!(BitFlags, T)) { } T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFlags, T) && canDBus!T) { + auto argType = dbus_message_iter_get_arg_type(iter); T ret; static if(!isVariant!T || is(T == Variant!DBusAny)) { - if(dbus_message_iter_get_arg_type(iter) == 'v') { + if(argType == 'v') { DBusMessageIter sub; dbus_message_iter_recurse(iter, &sub); static if(is(T == Variant!DBusAny)) { @@ -188,8 +203,12 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla return ret; } } - static if(!is(T == DBusAny) && !is(T == Variant!DBusAny)) { - auto argType = dbus_message_iter_get_arg_type(iter); + + static if( + !is(T == DBusAny) + && !is(T == Variant!DBusAny) + && !isInstanceOf!(VariantN, T) + ) { enforce(argType == typeCode!T(), new TypeMismatchException(typeCode!T(), argType)); } @@ -227,6 +246,25 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla DBusMessageIter sub; dbus_message_iter_recurse(iter, &sub); ret.data = readIter!(VariantType!T)(&sub); + } else static if(isInstanceOf!(VariantN, T)) { + scope const(char)[] argSig = + dbus_message_iter_get_signature(iter).fromStringz(); + scope(exit) + dbus_free(cast(void*) argSig.ptr); + + foreach(AT; T.AllowedTypes) { + // We have to compare the full signature here, not just the typecode. + // Otherwise, in case of container types, we might select the wrong one. + // We would then be calling an incorrect instance of readIter, which would + // probably throw a TypeMismatchException. + if (typeSig!AT == argSig) { + ret = readIter!AT(iter); + break; + } + } + + // If no value is in ret, apparently none of the types matched. + enforce(ret.hasValue, new TypeMismatchException(typeCode!T, argType)); } else static if(isAssociativeArray!T) { DBusMessageIter sub; dbus_message_iter_recurse(iter, &sub); @@ -239,7 +277,7 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla dbus_message_iter_next(&sub); } } else static if(is(T == DBusAny)) { - ret.type = dbus_message_iter_get_arg_type(iter); + ret.type = argType; ret.explicitVariant = false; if(ret.type == 's') { ret.str = readIter!string(iter); @@ -428,11 +466,16 @@ unittest { import dunit.toolkit; import ddbus.thin; + import std.variant : Algebraic; + enum E : int { a, b, c } enum F : uint { x = 1, y = 2, z = 4 } + alias V = Algebraic!(byte, short, int, long, string); Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth2"); - msg.build(E.c, 4, 5u, 8u); + V v1 = "hello from variant"; + V v2 = cast(short) 345; + msg.build(E.c, 4, 5u, 8u, v1, v2); DBusMessageIter iter, iter2; dbus_message_iter_init(msg.msg, &iter); @@ -446,5 +489,8 @@ unittest { readIter!F(&iter).assertThrow!InvalidValueException(); readIter!(BitFlags!F)(&iter2).assertThrow!InvalidValueException(); + + readIter!V(&iter).assertEqual(v1); + readIter!short(&iter).assertEqual(v2.get!short); } diff --git a/source/ddbus/exception.d b/source/ddbus/exception.d index a5a840f..fc97d7b 100644 --- a/source/ddbus/exception.d +++ b/source/ddbus/exception.d @@ -48,15 +48,20 @@ class TypeMismatchException : Exception { ) pure nothrow @safe { _expectedType = expectedType; _actualType = actualType; - super("The type of value at the current position in the message does not match the type of value to be read." - ~ " Expected: " ~ cast(char) expectedType ~ ", Got: " ~ cast(char) actualType); + if (expectedType == 'v') { + super("The type of value at the current position in the message is incompatible to the target variant type." + ~ " Type code of the value: " ~ cast(char) actualType); + } else { + super("The type of value at the current position in the message does not match the type of value to be read." + ~ " Expected: " ~ cast(char) expectedType ~ ", Got: " ~ cast(char) actualType); + } } - int expectedType() @property pure const nothrow @nogc { + int expectedType() @property pure const nothrow @safe @nogc { return _expectedType; } - int actualType() @property pure const nothrow @nogc { + int actualType() @property pure const nothrow @safe @nogc { return _actualType; } diff --git a/source/ddbus/util.d b/source/ddbus/util.d index 960624d..7aa55ee 100644 --- a/source/ddbus/util.d +++ b/source/ddbus/util.d @@ -4,6 +4,7 @@ import ddbus.thin; import std.typecons; import std.range; import std.traits; +import std.variant : VariantN; struct DictionaryEntry(K, V) { K key; @@ -62,6 +63,9 @@ template canDBus(T) { enum canDBus = true; } else static if(isVariant!T) { enum canDBus = canDBus!(VariantType!T); + } else static if(isInstanceOf!(VariantN, T)) { + // Phobos-style variants are supported if limited to DBus compatible types. + enum canDBus = (T.AllowedTypes.length > 0) && allCanDBus!(T.AllowedTypes); } else static if(isTuple!T) { enum canDBus = allCanDBus!(T.Types); } else static if(isInputRange!T) { @@ -112,7 +116,7 @@ string typeSig(T)() if(canDBus!T) { return "s"; } else static if(is(T == ObjectPath)) { return "o"; - } else static if(isVariant!T) { + } else static if(isVariant!T || isInstanceOf!(VariantN, T)) { return "v"; } else static if(is(T B == enum)) { return typeSig!B; @@ -212,6 +216,9 @@ unittest { typeSig!(DictionaryEntry!(string, int)[]).assertEqual("a{si}"); // multiple arguments typeSigAll!(int,bool).assertEqual("ib"); + // Phobos-style variants + canDBus!(std.variant.Variant).assertFalse(); + typeSig!(std.variant.Algebraic!(int, double, string)).assertEqual("v"); // type codes typeCode!int().assertEqual(cast(int)('i')); typeCode!bool().assertEqual(cast(int)('b'));