diff --git a/source/ddbus/exception.d b/source/ddbus/exception.d index fc97d7b..1ce45e9 100644 --- a/source/ddbus/exception.d +++ b/source/ddbus/exception.d @@ -36,7 +36,9 @@ class DBusException : Exception { } /++ - Thrown when the signature of a message does not match the requested types. + Thrown when the signature of a message does not match the requested types or + when trying to get a value from a DBusAny object that does not match the type + of its actual value. +/ class TypeMismatchException : Exception { package this( @@ -45,16 +47,34 @@ class TypeMismatchException : Exception { string file = __FILE__, size_t line = __LINE__, Throwable next = null + ) pure nothrow @safe { + string message; + + if (expectedType == 'v') { + message = "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 { + message = "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 ~ '\''; + } + + this(message, expectedType, actualType, file, line, next); + } + + this( + string message, + int expectedType, + int actualType, + string file = __FILE__, + size_t line = __LINE__, + Throwable next = null ) pure nothrow @safe { _expectedType = expectedType; _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." - ~ " Expected: " ~ cast(char) expectedType ~ ", Got: " ~ cast(char) actualType); - } + super(message, file, line, next); } int expectedType() @property pure const nothrow @safe @nogc { diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index 03f0c43..b3cf39a 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -3,7 +3,10 @@ module ddbus.thin; import ddbus.c_lib; import ddbus.conv; +import ddbus.exception : TypeMismatchException; import ddbus.util; + +import std.meta : staticIndexOf; import std.string; import std.typecons; import std.exception; @@ -127,7 +130,7 @@ struct DBusAny { /// DBus type of the value (never 'v'), see typeSig!T int type; /// Child signature for Arrays & Tuples - const(char)[] signature; + string signature; /// If true, this value will get serialized as variant value, otherwise it is serialized like it wasn't in a DBusAny wrapper. /// Same functionality as Variant!T but with dynamic types if true. bool explicitVariant; @@ -159,7 +162,7 @@ struct DBusAny { /// DBusAny[] array; /// - DBusAny[] tuple; + alias tuple = array; /// DictionaryEntry!(DBusAny, DBusAny)* entry; /// @@ -167,7 +170,7 @@ struct DBusAny { } /// Manually creates a DBusAny object using a type, signature and implicit specifier. - this(int type, const(char)[] signature, bool explicit) { + this(int type, string signature, bool explicit) { this.type = type; this.signature = signature; this.explicitVariant = explicit; @@ -246,8 +249,8 @@ struct DBusAny { } else static if(isInputRange!T) { this.type = 'a'; static assert(!is(ElementType!T == DBusAny), "Array must consist of the same type, use Variant!DBusAny or DBusAny(tuple(...)) instead"); - static assert(typeSig!(ElementType!T) != "y"); - this.signature = typeSig!(ElementType!T); + static assert(.typeSig!(ElementType!T) != "y"); + this.signature = .typeSig!(ElementType!T); this.explicitVariant = false; foreach(elem; value) array ~= DBusAny(elem); @@ -319,7 +322,7 @@ struct DBusAny { valueStr = '[' ~ array.map!(a => a.toString).join(", ") ~ ']'; break; case 'r': - valueStr = '(' ~ array.map!(a => a.toString).join(", ") ~ ')'; + valueStr = '(' ~ tuple.map!(a => a.toString).join(", ") ~ ')'; break; case 'e': valueStr = entry.key.toString ~ ": " ~ entry.value.toString; @@ -334,6 +337,71 @@ struct DBusAny { ~ ", " ~ valueStr ~ ")"; } + /++ + Get the value stored in the DBusAny object. + + Parameters: + T = The requested type. The currently stored value must match the + requested type exactly. + + Returns: + The current value of the DBusAny object. + + Throws: + TypeMismatchException if the DBus type of the current value of the + DBusAny object is not the same as the DBus type used to represent T. + +/ + T get(T)() @property const + if(staticIndexOf!(T, BasicTypes) >= 0) + { + enforce(type == typeCode!T, + new TypeMismatchException( + "Cannot get a " ~ T.stringof ~ " from a DBusAny with" + ~ " a value of DBus type '" ~ typeSig ~ "'.", typeCode!T, type)); + + static if(isIntegral!T) { + enum memberName = + (isUnsigned!T ? "uint" : "int") ~ (T.sizeof * 8).to!string; + return __traits(getMember, this, memberName); + } else static if(is(T == double)) { + return float64; + } else static if(is(T == string)) { + return str; + } else static if(is(T == ObjectPath)) { + return obj; + } else static if(is(T == bool)) { + return boolean; + } else { + static assert(false); + } + } + + /// ditto + T get(T)() @property const + if(is(T == const(DBusAny)[])) + { + enforce((type == 'a' && signature != "y") || type == 'r', + new TypeMismatchException( + "Cannot get a " ~ T.stringof ~ " from a DBusAny with" + ~ " a value of DBus type '" ~ this.typeSig ~ "'.", + typeCode!T, type)); + + return array; + } + + /// ditto + T get(T)() @property const + if (is(T == const(ubyte)[])) + { + enforce(type == 'a' && signature == "y", + new TypeMismatchException( + "Cannot get a " ~ T.stringof ~ " from a DBusAny with" + ~ " a value of DBus type '" ~ this.typeSig ~ "'.", + typeCode!T, type)); + + return binaryData; + } + /// If the value is an array of DictionaryEntries this will return a HashMap DBusAny[DBusAny] toAA() { enforce(type == 'a' && signature && signature[0] == '{'); @@ -345,6 +413,27 @@ struct DBusAny { return aa; } + /++ + Get the DBus type signature of the value stored in the DBusAny object. + + Returns: + The type signature of the value stored in this DBusAny object. + +/ + string typeSig() @property const pure nothrow @safe + { + if(type == 'a') { + return "a" ~ signature; + } else if(type == 'r') { + return signature; + } else if(type == 'e') { + return () @trusted { + return "{" ~ entry.key.signature ~ entry.value.signature ~ "}"; + } (); + } else { + return [ cast(char) type ]; + } + } + /// Converts a basic type, a tuple or an array to the D type with type checking. Tuples can get converted to an array too. T to(T)() { static if(is(T == Variant!R, R)) { @@ -457,6 +546,15 @@ unittest { void test(T)(T value, DBusAny b) { assertEqual(DBusAny(value), b); + + static if(is(T == Variant!R, R)) { + static if(__traits(compiles, b.get!R)) + assertEqual(b.get!R, value.data); + } else { + static if(__traits(compiles, b.get!T)) + assertEqual(b.get!T, value); + } + assertEqual(b.to!T, value); b.toString(); } diff --git a/source/ddbus/util.d b/source/ddbus/util.d index d69d541..4254a19 100644 --- a/source/ddbus/util.d +++ b/source/ddbus/util.d @@ -1,9 +1,10 @@ module ddbus.util; import ddbus.thin; -import std.typecons; +import std.meta : AliasSeq, staticIndexOf; import std.range; import std.traits; +import std.typecons : BitFlags, isTuple, Tuple; import std.variant : VariantN; struct DictionaryEntry(K, V) { @@ -51,11 +52,26 @@ template allCanDBus(TS...) { } } +/++ + AliasSeq of all basic types in terms of the DBus typesystem + +/ +package // Don't add to the API yet, 'cause I intend to move it later +alias BasicTypes = AliasSeq!( + bool, + byte, + short, + ushort, + int, + uint, + long, + ulong, + double, + string, + ObjectPath +); + template basicDBus(T) { - static if(is(T == byte) || is(T == short) || is (T == ushort) || is (T == int) - || is (T == uint) || is (T == long) || is (T == ulong) - || is (T == double) || is (T == string) || is(T == bool) - || is (T == ObjectPath)) { + static if(staticIndexOf!(T, BasicTypes) >= 0) { enum basicDBus = true; } else static if(is(T B == enum)) { enum basicDBus = basicDBus!B;