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
This commit is contained in:
Harry T. Vennik 2017-07-26 09:45:39 +02:00
parent fc9a604e85
commit da536be3d1
2 changed files with 118 additions and 7 deletions

View file

@ -5,6 +5,7 @@ import ddbus.exception : InvalidValueException, TypeMismatchException;
import ddbus.util; import ddbus.util;
import ddbus.thin; import ddbus.thin;
import std.exception : enforce; import std.exception : enforce;
import std.meta: allSatisfy;
import std.string; import std.string;
import std.typecons; import std.typecons;
import std.range; import std.range;
@ -116,7 +117,22 @@ void buildIter(TS...)(DBusMessageIter *iter, TS args) if(allCanDBus!TS) {
} else static if(is(T == struct)) { } else static if(is(T == struct)) {
DBusMessageIter sub; DBusMessageIter sub;
dbus_message_iter_open_container(iter, 'r', null, &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); dbus_message_iter_close_container(iter, &sub);
} else static if(basicDBus!T) { } else static if(basicDBus!T) {
dbus_message_iter_append_basic(iter,typeCode!T,&arg); 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)) { void readIterStruct(S)(DBusMessageIter *iter, ref S s) if(is(S == struct) && allCanDBus!(Fields!S)) {
alias FieldNameTuple!S names;
foreach(index, T; Fields!S) { 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 { unittest {
import dunit.toolkit; import dunit.toolkit;
import ddbus.thin; import ddbus.thin;

View file

@ -573,14 +573,49 @@ struct Connection {
unittest { unittest {
import dunit.toolkit; import dunit.toolkit;
struct S1 { private int a; double b; string s; } struct S1 {
struct S2 { Variant!int c; string d; S1 e; uint f; } 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"); 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.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) { Connection connectToBus(DBusBusType bus = DBusBusType.DBUS_BUS_SESSION) {