From da536be3d1fad0815640c4a82fd72264c5b940e4 Mon Sep 17 00:00:00 2001 From: "Harry T. Vennik" Date: Wed, 26 Jul 2017 09:45:39 +0200 Subject: [PATCH] Implemented selective marshaling of struct fields - Skips private fields by default - Allows overriding of that default by an UDA on the struct type - Allows per-field overriding using UDA on field --- source/ddbus/conv.d | 82 +++++++++++++++++++++++++++++++++++++++++++-- source/ddbus/thin.d | 43 +++++++++++++++++++++--- 2 files changed, 118 insertions(+), 7 deletions(-) diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index fa5abcb..11683d7 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -5,6 +5,7 @@ import ddbus.exception : InvalidValueException, TypeMismatchException; import ddbus.util; import ddbus.thin; import std.exception : enforce; +import std.meta: allSatisfy; import std.string; import std.typecons; import std.range; @@ -116,7 +117,22 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) { } else static if(is(T == struct)) { DBusMessageIter sub; dbus_message_iter_open_container(iter, 'r', null, &sub); - buildIter(&sub, arg.tupleof); + + // Following failed because of missing 'this' for members of arg. + // That sucks. It worked without Filter. + // Reported: https://issues.dlang.org/show_bug.cgi?id=17692 +// buildIter(&sub, Filter!(isAllowedField, arg.tupleof)); + + // Using foreach to work around the issue + foreach(i, member; arg.tupleof) { + // Ugly, but we need to use tupleof again in the condition, because when + // we use `member`, isAllowedField will fail because it'll find this + // nice `buildIter` function instead of T when it looks up the parent + // scope of its argument. + static if (isAllowedField!(arg.tupleof[i])) + buildIter(&sub, member); + } + dbus_message_iter_close_container(iter, &sub); } else static if(basicDBus!T) { dbus_message_iter_append_basic(iter,typeCode!T,&arg); @@ -289,12 +305,72 @@ void readIterTuple(Tup)(DBusMessageIter *iter, ref Tup tuple) if(isTuple!Tup && } void readIterStruct(S)(DBusMessageIter *iter, ref S s) if(is(S == struct) && allCanDBus!(Fields!S)) { - alias FieldNameTuple!S names; foreach(index, T; Fields!S) { - s.tupleof[index] = readIter!T(iter); + static if (isAllowedField!(s.tupleof[index])) { + s.tupleof[index] = readIter!T(iter); + } } } +/++ + Flags for use with dbusMarshaling UDA + + Default is to include public fields only ++/ +enum MarshalingFlag : ubyte { + includePrivateFields = 1 << 0, /// Automatically include private fields + manualOnly = 1 << 7 /// Only include fields with explicit + /// `@Yes.DBusMarshal`. This overrides any + /// `include` flags. +} + +private template isMarshalingFlag(T) { + enum isMarshalingFlag = is(T == MarshalingFlag); +} + +/++ + UDA for specifying DBus marshaling options on structs ++/ +auto dbusMarshaling(Args)(Args args ...) + if (allSatisfy!(isMarshalingFlag, Args)) { + return BitFlags!MarshalingFlag(args); +} + +private template marshalingFlags(S) if (is(S == struct)) { + private alias getUDAs!(S, BitFlags!MarshalingFlag) UDAs; + + static if (UDAs.length == 0) + enum marshalingFlags = BitFlags!MarshalingFlag.init; + else { + static assert (UDAs.length == 1, + "Only one @dbusMarshaling UDA allowed on type."); + static assert (is(typeof(UDAs[0]) == BitFlags!MarshalingFlag), + "Huh? Did you intend to use @dbusMarshaling UDA?"); + enum marshalingFlags = UDAs[0]; + } +} + +private template isAllowedField(alias field) { + private enum flags = marshalingFlags!(__traits(parent, field)); + private alias getUDAs!(field, Flag!"DBusMarshal") UDAs; + + static if (UDAs.length != 0) { + static assert (UDAs.length == 1, + "Only one UDA of type Flag!\"DBusMarshal\" allowed on struct field."); + static assert (is(typeof(UDAs[0]) == Flag!"DBusMarshal"), + "Did you intend to add UDA Yes.DBusMarshal or No.DBusMarshal?"); + enum isAllowedField = cast(bool) UDAs[0]; + } else static if (!(flags & MarshalingFlag.manualOnly)) { + static if (__traits(getProtection, field) == "public") + enum isAllowedField = true; + else static if (cast(bool) (flags & MarshalingFlag.includePrivateFields)) + enum isAllowedField = true; + else + enum isAllowedField = false; + } else + enum isAllowedField = false; +} + unittest { import dunit.toolkit; import ddbus.thin; diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index 7710502..c98527e 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -573,14 +573,49 @@ struct Connection { unittest { import dunit.toolkit; - struct S1 { private int a; double b; string s; } - struct S2 { Variant!int c; string d; S1 e; uint f; } + struct S1 { + private int a; + private @(Yes.DBusMarshal) double b; + string s; + } + + @dbusMarshaling(MarshalingFlag.manualOnly) + struct S2 { + int h, i; + @(Yes.DBusMarshal) int j, k; + } + + @dbusMarshaling(MarshalingFlag.includePrivateFields) + struct S3 { + private Variant!int c; + string d; + S1 e; + S2 f; + @(No.DBusMarshal) uint g; + } Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3"); - enum testStruct = S2(variant(5), "blah", S1(-7, 63.5, "test"), 16); + enum testStruct = S3( + variant(5), "blah", + S1(-7, 63.5, "test"), + S2(84, -123, 78, 432), + 16 + ); + msg.build(testStruct); - msg.read!S2().assertEqual(testStruct); + + // Non-marshaled fields should appear as freshly initialized + enum expectedResult = S3( + variant(5), "blah", + S1(int.init, 63.5, "test"), + S2(int.init, int.init, 78, 432), + uint.init + ); + + msg.read!S3().assertEqual(expectedResult); + + } Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) {