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;
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." 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); ~ " 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'));