Merge pull request #23 from thaven/feature/native-variant

Support Phobos variants
This commit is contained in:
Tristan Hume 2017-08-21 15:23:08 -04:00 committed by GitHub
commit 89c7736ccf
4 changed files with 108 additions and 16 deletions

View file

@ -10,6 +10,7 @@ import std.string;
import std.typecons; import std.typecons;
import std.range; import std.range;
import std.traits; import std.traits;
import std.variant : VariantN;
void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) {
foreach(index, arg; args) { 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(&sub, &entry);
} }
dbus_message_iter_close_container(iter, &sub); 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)) { } else static if(is(T == DBusAny) || is(T == Variant!DBusAny)) {
static if(is(T == Variant!DBusAny)) { static if(is(T == Variant!DBusAny)) {
auto val = arg.data; auto val = arg.data;
@ -108,7 +122,7 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) {
} }
if(val.explicitVariant) if(val.explicitVariant)
dbus_message_iter_close_container(iter, sub); dbus_message_iter_close_container(iter, sub);
} else static if(isVariant!T) { } else static if(isInstanceOf!(Variant, T)) {
DBusMessageIter sub; DBusMessageIter sub;
const(char)* subSig = typeSig!(VariantType!T).toStringz(); const(char)* subSig = typeSig!(VariantType!T).toStringz();
dbus_message_iter_open_container(iter, 'v', subSig, &sub); 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 TemplateArgsOf!T[0] E;
alias OriginalType!E B; 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); B value = readIter!B(iter);
enforce( 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) { 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; T ret;
static if(!isVariant!T || is(T == Variant!DBusAny)) { static if(!isInstanceOf!(Variant, T) || is(T == Variant!DBusAny)) {
if(dbus_message_iter_get_arg_type(iter) == 'v') { if(argType == 'v') {
DBusMessageIter sub; DBusMessageIter sub;
dbus_message_iter_recurse(iter, &sub); dbus_message_iter_recurse(iter, &sub);
static if(is(T == Variant!DBusAny)) { static if(is(T == Variant!DBusAny)) {
@ -188,8 +203,12 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla
return ret; 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(), enforce(argType == typeCode!T(),
new TypeMismatchException(typeCode!T(), argType)); new TypeMismatchException(typeCode!T(), argType));
} }
@ -223,10 +242,29 @@ T readIter(T)(DBusMessageIter *iter) if (!is(T == enum) && !isInstanceOf!(BitFla
ret ~= readIter!U(&sub); ret ~= readIter!U(&sub);
} }
} }
} else static if(isVariant!T) { } else static if(isInstanceOf!(Variant, T)) {
DBusMessageIter sub; DBusMessageIter sub;
dbus_message_iter_recurse(iter, &sub); dbus_message_iter_recurse(iter, &sub);
ret.data = readIter!(VariantType!T)(&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) { } else static if(isAssociativeArray!T) {
DBusMessageIter sub; DBusMessageIter sub;
dbus_message_iter_recurse(iter, &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); dbus_message_iter_next(&sub);
} }
} else static if(is(T == DBusAny)) { } else static if(is(T == DBusAny)) {
ret.type = dbus_message_iter_get_arg_type(iter); ret.type = argType;
ret.explicitVariant = false; ret.explicitVariant = false;
if(ret.type == 's') { if(ret.type == 's') {
ret.str = readIter!string(iter); ret.str = readIter!string(iter);
@ -428,11 +466,16 @@ unittest {
import dunit.toolkit; import dunit.toolkit;
import ddbus.thin; import ddbus.thin;
import std.variant : Algebraic;
enum E : int { a, b, c } enum E : int { a, b, c }
enum F : uint { x = 1, y = 2, z = 4 } 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"); 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; DBusMessageIter iter, iter2;
dbus_message_iter_init(msg.msg, &iter); dbus_message_iter_init(msg.msg, &iter);
@ -446,5 +489,8 @@ unittest {
readIter!F(&iter).assertThrow!InvalidValueException(); readIter!F(&iter).assertThrow!InvalidValueException();
readIter!(BitFlags!F)(&iter2).assertThrow!InvalidValueException(); readIter!(BitFlags!F)(&iter2).assertThrow!InvalidValueException();
readIter!V(&iter).assertEqual(v1);
readIter!short(&iter).assertEqual(v2.get!short);
} }

View file

@ -48,15 +48,20 @@ class TypeMismatchException : Exception {
) pure nothrow @safe { ) pure nothrow @safe {
_expectedType = expectedType; _expectedType = expectedType;
_actualType = actualType; _actualType = actualType;
super("The type of value at the current position in the message does not match the type of value to be read." if (expectedType == 'v') {
~ " Expected: " ~ cast(char) expectedType ~ ", Got: " ~ cast(char) actualType); 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; return _expectedType;
} }
int actualType() @property pure const nothrow @nogc { int actualType() @property pure const nothrow @safe @nogc {
return _actualType; return _actualType;
} }

View file

@ -194,6 +194,10 @@ void registerRouter(Connection conn, MessageRouter router) {
unittest{ unittest{
import dunit.toolkit; import dunit.toolkit;
import std.typecons : BitFlags;
import std.variant : Algebraic;
auto router = new MessageRouter(); auto router = new MessageRouter();
// set up test messages // set up test messages
MessagePattern patt = MessagePattern("/root","ca.thume.test","test"); MessagePattern patt = MessagePattern("/root","ca.thume.test","test");
@ -209,6 +213,24 @@ unittest{
patt = MessagePattern("/troll","ca.thume.tester","wow"); patt = MessagePattern("/troll","ca.thume.tester","wow");
router.setHandler!(void)(patt,{return;}); 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, { static assert(!__traits(compiles, {
patt = MessagePattern("/root/bar","ca.thume.tester","lolwut"); patt = MessagePattern("/root/bar","ca.thume.tester","lolwut");
router.setHandler!(void, DBusAny)(patt,(DBusAny wrongUsage){return;}); router.setHandler!(void, DBusAny)(patt,(DBusAny wrongUsage){return;});
@ -216,10 +238,13 @@ unittest{
// TODO: these tests rely on nondeterministic hash map ordering // TODO: these tests rely on nondeterministic hash map ordering
static string introspectResult = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> static string introspectResult = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/root"><interface name="ca.thume.test"><method name="test"><arg type="i" direction="in"/><arg type="i" direction="out"/></method></interface><interface name="ca.thume.tester"><method name="lolwut"><arg type="i" direction="in"/><arg type="s" direction="in"/></method></interface><node name="bar"/><node name="foo"/><node name="wat"/></node>`; <node name="/root"><interface name="ca.thume.test"><method name="test"><arg type="i" direction="in"/><arg type="i" direction="out"/></method></interface><interface name="ca.thume.tester"><method name="lolwut"><arg type="i" direction="in"/><arg type="s" direction="in"/></method></interface><node name="bar"/><node name="fancy"/><node name="foo"/><node name="wat"/></node>`;
router.introspectXML("/root").assertEqual(introspectResult); router.introspectXML("/root").assertEqual(introspectResult);
static string introspectResult2 = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd"> static string introspectResult2 = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/root/foo"><interface name="ca.thume.tester"><method name="lolwut"><arg type="i" direction="in"/><arg type="v" direction="in"/><arg type="s" direction="out"/><arg type="s" direction="out"/><arg type="i" direction="out"/></method></interface></node>`; <node name="/root/foo"><interface name="ca.thume.tester"><method name="lolwut"><arg type="i" direction="in"/><arg type="v" direction="in"/><arg type="s" direction="out"/><arg type="s" direction="out"/><arg type="i" direction="out"/></method></interface></node>`;
router.introspectXML("/root/foo").assertEqual(introspectResult2); router.introspectXML("/root/foo").assertEqual(introspectResult2);
static string introspectResult3 = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="/root/fancy"><interface name="ca.thume.tester"><method name="crazyTest"><arg type="v" direction="in"/><arg type="i" direction="out"/></method></interface></node>`;
router.introspectXML("/root/fancy").assertEqual(introspectResult3);
router.introspectXML("/").assertEndsWith(`<node name="/"><node name="root"/><node name="troll"/></node>`); router.introspectXML("/").assertEndsWith(`<node name="/"><node name="root"/><node name="troll"/></node>`);
} }

View file

@ -4,6 +4,7 @@ import ddbus.thin;
import std.typecons; import std.typecons;
import std.range; import std.range;
import std.traits; import std.traits;
import std.variant : VariantN;
struct DictionaryEntry(K, V) { struct DictionaryEntry(K, V) {
K key; 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)); 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) { template isVariant(T) {
static if(isBasicType!T || isInputRange!T) { static if(isBasicType!T || isInputRange!T) {
enum isVariant = false; enum isVariant = false;
@ -60,8 +70,11 @@ template basicDBus(T) {
template canDBus(T) { template canDBus(T) {
static if(basicDBus!T || is(T == DBusAny)) { static if(basicDBus!T || is(T == DBusAny)) {
enum canDBus = true; enum canDBus = true;
} else static if(isVariant!T) { } else static if(isInstanceOf!(Variant, T)) {
enum canDBus = canDBus!(VariantType!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) { } else static if(isTuple!T) {
enum canDBus = allCanDBus!(T.Types); enum canDBus = allCanDBus!(T.Types);
} else static if(isInputRange!T) { } else static if(isInputRange!T) {
@ -112,7 +125,7 @@ string typeSig(T)() if(canDBus!T) {
return "s"; return "s";
} else static if(is(T == ObjectPath)) { } else static if(is(T == ObjectPath)) {
return "o"; return "o";
} else static if(isVariant!T) { } else static if(isInstanceOf!(Variant, T) || isInstanceOf!(VariantN, T)) {
return "v"; return "v";
} else static if(is(T B == enum)) { } else static if(is(T B == enum)) {
return typeSig!B; return typeSig!B;
@ -212,6 +225,9 @@ unittest {
typeSig!(DictionaryEntry!(string, int)[]).assertEqual("a{si}"); typeSig!(DictionaryEntry!(string, int)[]).assertEqual("a{si}");
// multiple arguments // multiple arguments
typeSigAll!(int,bool).assertEqual("ib"); typeSigAll!(int,bool).assertEqual("ib");
// Phobos-style variants
canDBus!(std.variant.Variant).assertFalse();
typeSig!(std.variant.Algebraic!(int, double, string)).assertEqual("v");
// type codes // type codes
typeCode!int().assertEqual(cast(int)('i')); typeCode!int().assertEqual(cast(int)('i'));
typeCode!bool().assertEqual(cast(int)('b')); typeCode!bool().assertEqual(cast(int)('b'));