diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index 11683d7..1a85bdd 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; @@ -108,7 +122,7 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { } if(val.explicitVariant) dbus_message_iter_close_container(iter, sub); - } else static if(isVariant!T) { + } else static if(isInstanceOf!(Variant, T)) { DBusMessageIter sub; const(char)* subSig = typeSig!(VariantType!T).toStringz(); dbus_message_iter_open_container(iter, 'v', subSig, &sub); @@ -159,7 +173,7 @@ T readIter(T)(DBusMessageIter *iter) if (isInstanceOf!(BitFlags, T)) { alias TemplateArgsOf!T[0] E; alias OriginalType!E B; - B mask = only(EnumMembers!E).fold!((a, b) => a | b); + B mask = only(EnumMembers!E).fold!((a, b) => cast(B) (a | b)); B value = readIter!B(iter); enforce( @@ -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') { + static if(!isInstanceOf!(Variant, T) || is(T == Variant!DBusAny)) { + 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)); } @@ -223,10 +242,29 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla ret ~= readIter!U(&sub); } } - } else static if(isVariant!T) { + } else static if(isInstanceOf!(Variant, T)) { 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/router.d b/source/ddbus/router.d index e999eca..e107963 100644 --- a/source/ddbus/router.d +++ b/source/ddbus/router.d @@ -194,6 +194,10 @@ void registerRouter(Connection conn, MessageRouter router) { unittest{ import dunit.toolkit; + + import std.typecons : BitFlags; + import std.variant : Algebraic; + auto router = new MessageRouter(); // set up test messages MessagePattern patt = MessagePattern("/root","ca.thume.test","test"); @@ -209,6 +213,24 @@ unittest{ patt = MessagePattern("/troll","ca.thume.tester","wow"); router.setHandler!(void)(patt,{return;}); + patt = MessagePattern("/root/fancy","ca.thume.tester","crazyTest"); + enum F : ushort { a = 1, b = 8, c = 16 } + struct S { byte b; ulong ul; F f; } + router.setHandler!(int)(patt, (Algebraic!(ushort, BitFlags!F, S) v) { + if (v.type is typeid(ushort) || v.type is typeid(BitFlags!F)) { + return v.coerce!int; + } else if (v.type is typeid(S)) { + auto s = v.get!S; + final switch (s.f) { + case F.a: return s.b; + case F.b: return cast(int) s.ul; + case F.c: return cast(int) s.ul + s.b; + } + } + + assert(false); + }); + static assert(!__traits(compiles, { patt = MessagePattern("/root/bar","ca.thume.tester","lolwut"); router.setHandler!(void, DBusAny)(patt,(DBusAny wrongUsage){return;}); @@ -216,10 +238,13 @@ unittest{ // TODO: these tests rely on nondeterministic hash map ordering static string introspectResult = ` -`; +`; router.introspectXML("/root").assertEqual(introspectResult); static string introspectResult2 = ` `; router.introspectXML("/root/foo").assertEqual(introspectResult2); + static string introspectResult3 = ` +`; + router.introspectXML("/root/fancy").assertEqual(introspectResult3); router.introspectXML("/").assertEndsWith(``); } diff --git a/source/ddbus/util.d b/source/ddbus/util.d index 960624d..a381ca0 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; @@ -16,6 +17,15 @@ auto byDictionaryEntries(K, V)(V[K] aa) { return aa.byKeyValue.map!(pair => DictionaryEntry!(K, V)(pair.key, pair.value)); } +/+ + Predicate template indicating whether T is an instance of ddbus.thin.Variant. + + Deprecated: + This template used to be undocumented and user code should not depend on it. + Its meaning became unclear when support for Phobos-style variants was added. + It seemed best to remove it at that point. ++/ +deprecated("Use std.traits.isInstanceOf instead.") template isVariant(T) { static if(isBasicType!T || isInputRange!T) { enum isVariant = false; @@ -60,8 +70,11 @@ template basicDBus(T) { template canDBus(T) { static if(basicDBus!T || is(T == DBusAny)) { enum canDBus = true; - } else static if(isVariant!T) { + } else static if(isInstanceOf!(Variant, 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 +125,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(isInstanceOf!(Variant, T) || isInstanceOf!(VariantN, T)) { return "v"; } else static if(is(T B == enum)) { return typeSig!B; @@ -212,6 +225,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'));