safer API with ObjectPath, InterfaceName, BusName

the busName and interfacePath types transparently cast down to a string,
(but not the other way around) so they are a perfect upgrade to
 type-safety. The only downside to them is that templates can often get
them wrong. std.conv.to will return with a cast(T) prefix, which could
slightly break existing code.

I think the type-safety advantages are outweighing this corner case
though. The current API has been fully upgraded and examples still run
and work.
This commit is contained in:
WebFreak001 2019-03-17 10:33:55 +01:00
parent c4de569809
commit db86451c7f
9 changed files with 206 additions and 76 deletions

View file

@ -132,7 +132,9 @@ msg.readTuple!(typeof(args))().assertEqual(args);
```
### Basic types
These are the basic types supported by `ddbus`:
`bool`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `double`, `string`, `ObjectPath`
`bool`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `double`, `string`, `ObjectPath`, `InterfaceName`, `BusName`
ObjectPath, InterfaceName and BusName are typesafe wrappers or aliases around strings which should be used to ensure type-safety. They do not allow implicit casts to each other but can be manually converted to strings either by casting to string.
### Overview of mappings of other types:

View file

@ -4,12 +4,12 @@ import ddbus;
void testCall(Connection conn) {
for(int i = 0; i < 50; i++) {
Message msg = Message("ca.thume.transience","/ca/thume/transience/screensurface",
"ca.thume.transience.screensurface","testDot");
Message msg = Message(busName("ca.thume.transience"), ObjectPath("/ca/thume/transience/screensurface"),
interfaceName("ca.thume.transience.screensurface"), "testDot");
conn.sendBlocking(msg);
}
Message msg2 = Message("ca.thume.transience","/ca/thume/transience/screensurface",
"ca.thume.transience.screensurface","testPing");
Message msg2 = Message(busName("ca.thume.transience"), ObjectPath("/ca/thume/transience/screensurface"),
interfaceName("ca.thume.transience.screensurface"), "testPing");
Message res = conn.sendWithReplyBlocking(msg2, 3.seconds);
int result = res.read!int();
writeln(result);

View file

@ -3,18 +3,18 @@ import ddbus;
void testServe(Connection conn) {
auto router = new MessageRouter();
MessagePattern patt = MessagePattern("/root","ca.thume.test","test");
MessagePattern patt = MessagePattern(ObjectPath("/root"), interfaceName("ca.thume.test"), "test");
router.setHandler!(int,int)(patt,(int par) {
writeln("Called with ", par);
return par;
});
patt = MessagePattern("/signaler","ca.thume.test","signal",true);
patt = MessagePattern(ObjectPath("/signaler"), interfaceName("ca.thume.test"), "signal",true);
router.setHandler!(void,int)(patt,(int par) {
writeln("Signalled with ", par);
});
registerRouter(conn, router);
writeln("Getting name...");
bool gotem = requestName(conn, "ca.thume.ddbus.test");
bool gotem = requestName(conn, busName("ca.thume.ddbus.test"));
writeln("Got name: ",gotem);
simpleMainLoop(conn);
}

View file

@ -5,9 +5,9 @@ import ddbus.thin;
import ddbus.c_lib;
import std.string;
enum BusService = "org.freedesktop.DBus";
enum BusPath = "/org/freedesktop/DBus";
enum BusInterface = "org.freedesktop.DBus";
enum BusService = busName("org.freedesktop.DBus");
enum BusInterface = interfaceName("org.freedesktop.DBus");
enum BusPath = ObjectPath("/org/freedesktop/DBus");
enum NameFlags {
AllowReplace = 1,
@ -15,10 +15,16 @@ enum NameFlags {
NoQueue = 4
}
deprecated("Use the overload taking a BusName instead")
bool requestName(Connection conn, string name,
NameFlags flags = NameFlags.NoQueue | NameFlags.AllowReplace) {
return requestName(conn, busName(name), flags);
}
/// Requests a DBus well-known name.
/// returns if the name is owned after the call.
/// Involves blocking call on a DBus method, may throw an exception on failure.
bool requestName(Connection conn, string name,
bool requestName(Connection conn, BusName name,
NameFlags flags = NameFlags.NoQueue | NameFlags.AllowReplace) {
auto msg = Message(BusService, BusPath, BusInterface, "RequestName");
msg.build(name, cast(uint)(flags));
@ -44,5 +50,5 @@ unittest {
import dunit.toolkit;
Connection conn = connectToBus();
conn.requestName("ca.thume.ddbus.testing").assertTrue();
conn.requestName(busName("ca.thume.ddbus.testing")).assertTrue();
}

View file

@ -18,7 +18,7 @@ void buildIter(TS...)(DBusMessageIter* iter, TS args)
if (allCanDBus!TS) {
foreach (index, arg; args) {
alias TS[index] T;
static if (is(T == string)) {
static if (is(T == string) || is(T == InterfaceName) || is(T == BusName)) {
immutable(char)* cStr = arg.toStringz();
dbus_message_iter_append_basic(iter, typeCode!T, &cStr);
} else static if (is(T == ObjectPath)) {
@ -172,7 +172,7 @@ void buildIter(TS...)(DBusMessageIter* iter, TS args)
}
T readIter(T)(DBusMessageIter* iter)
if (is(T == enum)) {
if (is(T == enum) && !is(T == InterfaceName) && !is(T == BusName)) {
import std.algorithm.searching : canFind;
alias OriginalType!T B;
@ -198,7 +198,7 @@ T readIter(T)(DBusMessageIter* iter)
}
T readIter(T)(DBusMessageIter* iter)
if (!is(T == enum) && !isInstanceOf!(BitFlags, T) && canDBus!T) {
if (!(is(T == enum) && !is(T == InterfaceName) && !is(T == BusName)) && !isInstanceOf!(BitFlags, T) && canDBus!T) {
auto argType = dbus_message_iter_get_arg_type(iter);
T ret;
@ -222,12 +222,12 @@ T readIter(T)(DBusMessageIter* iter)
enforce(argType == typeCode!T(), new TypeMismatchException(typeCode!T(), argType));
}
static if (is(T == string) || is(T == ObjectPath)) {
static if (is(T == string) || is(T == InterfaceName) || is(T == BusName) || is(T == ObjectPath)) {
const(char)* cStr;
dbus_message_iter_get_basic(iter, &cStr);
string str = cStr.fromStringz().idup; // copy string
static if (is(T == string)) {
ret = str;
static if (is(T == string) || is(T == InterfaceName) || is(T == BusName)) {
ret = cast(T)str;
} else {
ret = ObjectPath(str);
}
@ -378,7 +378,7 @@ unittest {
return Variant!T(data);
}
Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth");
Message msg = Message(busName("org.example.wow"), ObjectPath("/wut"), interfaceName("org.test.iface"), "meth");
bool[] emptyB;
string[string] map;
map["hello"] = "world";
@ -388,22 +388,25 @@ unittest {
anyVar.explicitVariant.assertEqual(false);
auto tupleMember = DBusAny(tuple(Variant!int(45), Variant!ushort(5), 32,
[1, 2], tuple(variant(4), 5), map));
Variant!DBusAny complexVar = variant(DBusAny(["hello world" : variant(DBusAny(1337)),
"array value" : variant(DBusAny([42, 64])), "tuple value"
: variant(tupleMember), "optimized binary data"
: variant(DBusAny(cast(ubyte[])[1, 2, 3, 4, 5, 6]))]));
Variant!DBusAny complexVar = variant(DBusAny([
"hello world": variant(DBusAny(1337)),
"array value": variant(DBusAny([42, 64])),
"tuple value": variant(tupleMember),
"optimized binary data": variant(DBusAny(cast(ubyte[])[1, 2, 3, 4, 5, 6]))
]));
complexVar.data.type.assertEqual('a');
complexVar.data.signature.assertEqual("{sv}".dup);
tupleMember.signature.assertEqual("(vviai(vi)a{ss})");
auto args = tuple(5, true, "wow", var(5.9), [6, 5], tuple(6.2, 4, [["lol"]],
auto args = tuple(5, true, "wow", interfaceName("methodName"), var(5.9), [6, 5], tuple(6.2, 4, [["lol"]],
emptyB, var([4, 2])), map, anyVar, complexVar);
msg.build(args.expand);
msg.signature().assertEqual("ibsvai(diaasabv)a{ss}tv");
msg.signature().assertEqual("ibssvai(diaasabv)a{ss}tv");
msg.read!string().assertThrow!TypeMismatchException();
msg.readTuple!(Tuple!(int, bool, double)).assertThrow!TypeMismatchException();
msg.readTuple!(Tuple!(int, bool, string, double)).assertEqual(tuple(5, true, "wow", 5.9));
msg.readTuple!(Tuple!(int, bool, string, InterfaceName, double))
.assertEqual(tuple(5, true, "wow", interfaceName("methodName"), 5.9));
msg.readTuple!(typeof(args))().assertEqual(args);
DBusMessageIter iter;
@ -411,6 +414,7 @@ unittest {
readIter!int(&iter).assertEqual(5);
readIter!bool(&iter).assertEqual(true);
readIter!string(&iter).assertEqual("wow");
readIter!InterfaceName(&iter).assertEqual(interfaceName("methodName"));
readIter!double(&iter).assertEqual(5.9);
readIter!(int[])(&iter).assertEqual([6, 5]);
readIter!(Tuple!(double, int, string[][], bool[], Variant!(int[])))(&iter).assertEqual(
@ -418,7 +422,7 @@ unittest {
// There are two ways to read a dictionary, so duplicate the iterator to test both.
auto iter2 = iter;
readIter!(string[string])(&iter).assertEqual(["hello" : "world"]);
readIter!(string[string])(&iter).assertEqual(["hello": "world"]);
auto dict = readIter!(DictionaryEntry!(string, string)[])(&iter2);
dict.length.assertEqual(1);
dict[0].key.assertEqual("hello");
@ -448,7 +452,7 @@ unittest {
alias V = Algebraic!(byte, short, int, long, string);
Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth2");
Message msg = Message(busName("org.example.wow"), ObjectPath("/wut"), interfaceName("org.test.iface"), "meth2");
V v1 = "hello from variant";
V v2 = cast(short) 345;
msg.build(E.c, 4, 5u, 8u, v1, v2);

View file

@ -11,8 +11,8 @@ import std.algorithm;
import std.format;
struct MessagePattern {
string path;
string iface;
ObjectPath path;
InterfaceName iface;
string method;
bool signal;
@ -23,7 +23,12 @@ struct MessagePattern {
signal = (msg.type() == MessageType.Signal);
}
deprecated("Use the constructor taking a ObjectPath and InterfaceName instead")
this(string path, string iface, string method, bool signal = false) {
this(ObjectPath(path), interfaceName(iface), method, signal);
}
this(ObjectPath path, InterfaceName iface, string method, bool signal = false) {
this.path = path;
this.iface = iface;
this.method = method;
@ -48,7 +53,7 @@ struct MessagePattern {
unittest {
import dunit.toolkit;
auto msg = Message("org.example.test", "/test", "org.example.testing", "testMethod");
auto msg = Message(busName("org.example.test"), ObjectPath("/test"), interfaceName("org.example.testing"), "testMethod");
auto patt = new MessagePattern(msg);
patt.assertEqual(patt);
patt.signal.assertFalse();
@ -141,7 +146,12 @@ class MessageRouter {
static string introspectHeader = `<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node name="%s">`;
deprecated("Use introspectXML(ObjectPath path) instead")
string introspectXML(string path) {
return introspectXML(ObjectPath(path));
}
string introspectXML(ObjectPath path) {
// dfmt off
auto methods = callTable
.byKey()
@ -154,7 +164,7 @@ class MessageRouter {
auto app = appender!string;
formattedWrite(app, introspectHeader, path);
foreach (iface; ifaces) {
formattedWrite(app, `<interface name="%s">`, iface.front.iface);
formattedWrite(app, `<interface name="%s">`, cast(string)iface.front.iface);
foreach (methodPatt; iface.array()) {
formattedWrite(app, `<method name="%s">`, methodPatt.method);
@ -174,14 +184,11 @@ class MessageRouter {
app.put("</interface>");
}
string childPath = path;
if (!childPath.endsWith("/")) {
childPath ~= "/";
}
auto childPath = path;
auto children = callTable.byKey()
.filter!(a => (a.path.startsWith(childPath)) && !a.signal)().map!(
(s) => s.path.chompPrefix(childPath)).map!((s) => s.findSplit("/")[0])
auto children = callTable.byKey().filter!(a => a.path.startsWith(childPath)
&& a.path != childPath && !a.signal)().map!((s) => s.path.chompPrefix(childPath))
.map!((s) => s.value[1 .. $].findSplit("/")[0])
.array().sort().uniq();
foreach (child; children) {
@ -192,7 +199,12 @@ class MessageRouter {
return app.data;
}
deprecated("Use the method taking an ObjectPath instead")
void handleIntrospect(string path, Message call, Connection conn) {
handleIntrospect(ObjectPath(path), call, conn);
}
void handleIntrospect(ObjectPath path, Message call, Connection conn) {
auto retMsg = call.createReturn();
retMsg.build(introspectXML(path));
conn.sendBlocking(retMsg);
@ -233,17 +245,17 @@ unittest {
auto router = new MessageRouter();
// set up test messages
MessagePattern patt = MessagePattern("/root", "ca.thume.test", "test");
MessagePattern patt = MessagePattern(ObjectPath("/root"), interfaceName("ca.thume.test"), "test");
router.setHandler!(int, int)(patt, (int p) { return 6; });
patt = MessagePattern("/root", "ca.thume.tester", "lolwut");
patt = MessagePattern(ObjectPath("/root"), interfaceName("ca.thume.tester"), "lolwut");
router.setHandler!(void, int, string)(patt, (int p, string p2) { });
patt = MessagePattern("/root/wat", "ca.thume.tester", "lolwut");
patt = MessagePattern(ObjectPath("/root/wat"), interfaceName("ca.thume.tester"), "lolwut");
router.setHandler!(int, int)(patt, (int p) { return 6; });
patt = MessagePattern("/root/bar", "ca.thume.tester", "lolwut");
patt = MessagePattern(ObjectPath("/root/bar"), interfaceName("ca.thume.tester"), "lolwut");
router.setHandler!(Variant!DBusAny, int)(patt, (int p) {
return variant(DBusAny(p));
});
patt = MessagePattern("/root/foo", "ca.thume.tester", "lolwut");
patt = MessagePattern(ObjectPath("/root/foo"), interfaceName("ca.thume.tester"), "lolwut");
router.setHandler!(Tuple!(string, string, int), int,
Variant!DBusAny)(patt, (int p, Variant!DBusAny any) {
Tuple!(string, string, int) ret;
@ -252,10 +264,10 @@ unittest {
ret[2] = p;
return ret;
});
patt = MessagePattern("/troll", "ca.thume.tester", "wow");
patt = MessagePattern(ObjectPath("/troll"), interfaceName("ca.thume.tester"), "wow");
router.setHandler!(void)(patt, { return; });
patt = MessagePattern("/root/fancy", "ca.thume.tester", "crazyTest");
patt = MessagePattern(ObjectPath("/root/fancy"), interfaceName("ca.thume.tester"), "crazyTest");
enum F : ushort {
a = 1,
b = 8,
@ -294,13 +306,13 @@ unittest {
// 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">
<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(ObjectPath("/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">
<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(ObjectPath("/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("/")
router.introspectXML(ObjectPath("/root/fancy")).assertEqual(introspectResult3);
router.introspectXML(ObjectPath("/"))
.assertEndsWith(`<node name="/"><node name="root"/><node name="troll"/></node>`);
}

View file

@ -8,17 +8,23 @@ import std.string;
import std.traits;
class PathIface {
this(Connection conn, string dest, ObjectPath path, string iface) {
this(conn, dest, path.value, iface);
}
this(Connection conn, string dest, string path, string iface) {
this(Connection conn, BusName dest, ObjectPath path, InterfaceName iface) {
this.conn = conn;
this.dest = dest.toStringz();
this.path = path.toStringz();
this.path = path.value.toStringz();
this.iface = iface.toStringz();
}
deprecated("Use the constructor taking BusName, ObjectPath and InterfaceName instead")
this(Connection conn, string dest, ObjectPath path, string iface) {
this(conn, busName(dest), path, interfaceName(iface));
}
deprecated("Use the constructor taking BusName, ObjectPath and InterfaceName instead")
this(Connection conn, string dest, string path, string iface) {
this(conn, busName(dest), ObjectPath(path), interfaceName(iface));
}
Ret call(Ret, Args...)(string meth, Args args)
if (allCanDBus!Args && canDBus!Ret) {
Message msg = Message(dbus_message_new_method_call(dest, path, iface, meth.toStringz()));
@ -43,15 +49,20 @@ unittest {
import dunit.toolkit;
Connection conn = connectToBus();
PathIface obj = new PathIface(conn, "org.freedesktop.DBus",
"/org/freedesktop/DBus", "org.freedesktop.DBus");
auto names = obj.GetNameOwner("org.freedesktop.DBus").to!string();
names.assertEqual("org.freedesktop.DBus");
obj.call!string("GetNameOwner", "org.freedesktop.DBus").assertEqual("org.freedesktop.DBus");
PathIface obj = new PathIface(conn, busName("org.freedesktop.DBus"),
ObjectPath("/org/freedesktop/DBus"), interfaceName("org.freedesktop.DBus"));
auto names = obj.GetNameOwner(interfaceName("org.freedesktop.DBus")).to!BusName();
names.assertEqual(busName("org.freedesktop.DBus"));
obj.call!BusName("GetNameOwner", interfaceName("org.freedesktop.DBus")).assertEqual(busName("org.freedesktop.DBus"));
}
enum SignalMethod;
deprecated("Use the registerMethods overload taking an ObjectPath and InterfaceName instead")
void registerMethods(T : Object)(MessageRouter router, string path, string iface, T obj) {
registerMethods(router, ObjectPath(path), interfaceName(iface), obj);
}
/**
Registers all *possible* methods of an object in a router.
It will not register methods that use types that ddbus can't handle.
@ -64,7 +75,7 @@ enum SignalMethod;
and basically do what MessageRouter.setHandler does but avoiding duplication. Then this DBusWrapper!Class
could be instantiated with any object efficiently and placed in the router table with minimal duplication.
*/
void registerMethods(T : Object)(MessageRouter router, string path, string iface, T obj) {
void registerMethods(T : Object)(MessageRouter router, ObjectPath path, InterfaceName iface, T obj) {
MessagePattern patt = MessagePattern(path, iface, "", false);
foreach (member; __traits(allMembers, T)) {
// dfmt off
@ -96,8 +107,8 @@ unittest {
auto o = new Tester;
auto router = new MessageRouter;
registerMethods(router, "/", "ca.thume.test", o);
MessagePattern patt = MessagePattern("/", "ca.thume.test", "wat");
registerMethods(router, ObjectPath("/"), interfaceName("ca.thume.test"), o);
MessagePattern patt = MessagePattern(ObjectPath("/"), interfaceName("ca.thume.test"), "wat");
router.callTable.assertHasKey(patt);
patt.method = "signalRecv";
patt.signal = true;

View file

@ -22,6 +22,8 @@ import std.algorithm;
public import ddbus.exception : wrapErrors, DBusException;
struct ObjectPath {
enum root = ObjectPath("/");
private string _value;
this(string objPath) pure @safe {
@ -44,10 +46,22 @@ struct ObjectPath {
return hashOf(_value);
}
T opCast(T : string)() const pure @nogc nothrow @safe {
return value;
}
bool opEquals(ref const typeof(this) b) const pure @nogc nothrow @safe {
return _value == b._value;
}
bool opEquals(const typeof(this) b) const pure @nogc nothrow @safe {
return _value == b._value;
}
bool opEquals(string b) const pure @nogc nothrow @safe {
return _value == b;
}
ObjectPath opBinary(string op : "~")(string rhs) const pure @safe {
if (!rhs.startsWith("/")) {
return opBinary!"~"(ObjectPath("/" ~ rhs));
@ -83,6 +97,26 @@ struct ObjectPath {
_value = opBinary!"~"(rhs)._value;
}
bool startsWith(ObjectPath withThat) pure @nogc nothrow @safe {
if (withThat._value == "/")
return true;
else if (_value == "/")
return false;
else
return _value.representation.splitter('/').startsWith(withThat._value.representation.splitter('/'));
}
/// Removes a prefix from this path and returns the remainder. Keeps leading slashes.
/// Returns this unmodified if the prefix doesn't match.
ObjectPath chompPrefix(ObjectPath prefix) pure @nogc nothrow @safe {
if (prefix._value == "/" || !startsWith(prefix))
return this;
else if (prefix._value == _value)
return ObjectPath.root;
else
return ObjectPath.assumePath(_value[prefix._value.length .. $]);
}
/++
Returns: `false` for empty strings or strings that don't match the
pattern `(/[0-9A-Za-z_]+)+|/`.
@ -106,6 +140,35 @@ struct ObjectPath {
return objPath.representation.splitter('/').drop(1).all!(a => a.length
&& a.all!(c => c.isAlphaNum || c == '_'));
}
/// Does an unsafe assignment to an ObjectPath.
static ObjectPath assumePath(string path) pure @nogc nothrow @safe {
ObjectPath ret;
ret._value = path;
return ret;
}
}
/// Serves as typesafe alias. Instances should be created using busName instead of casting.
/// It prevents accidental usage of bus names in other string parameter fields and makes the API clearer.
enum BusName : string {
none = null
}
/// Casts a bus name argument to a BusName type. May include additional validation in the future.
BusName busName(string name) pure @nogc nothrow @safe {
return cast(BusName) name;
}
/// Serves as typesafe alias. Instances should be created using interfaceName instead of casting.
/// It prevents accidental usage of interface paths in other string parameter fields and makes the API clearer.
enum InterfaceName : string {
none = null
}
/// Casts a interface path argument to an InterfaceName type. May include additional validation in the future.
InterfaceName interfaceName(string path) pure @nogc nothrow @safe {
return cast(InterfaceName) path;
}
unittest {
@ -118,6 +181,20 @@ unittest {
auto obj = ObjectPath(path);
obj.value.assertEqual(path);
obj.toHash().assertEqual(path.hashOf);
ObjectPath("/some/path").startsWith(ObjectPath("/some")).assertTrue();
ObjectPath("/some/path").startsWith(ObjectPath("/path")).assertFalse();
ObjectPath("/some/path").startsWith(ObjectPath("/")).assertTrue();
ObjectPath("/").startsWith(ObjectPath("/")).assertTrue();
ObjectPath("/").startsWith(ObjectPath("/some/path")).assertFalse();
ObjectPath("/some/path").chompPrefix(ObjectPath("/some")).assertEqual(ObjectPath("/path"));
ObjectPath("/some/path").chompPrefix(ObjectPath("/bar")).assertEqual(ObjectPath("/some/path"));
ObjectPath("/some/path").chompPrefix(ObjectPath("/")).assertEqual(ObjectPath("/some/path"));
ObjectPath("/some/path").chompPrefix(ObjectPath("/some/path")).assertEqual(ObjectPath("/"));
ObjectPath("/some/path").chompPrefix(ObjectPath("/some/path/extra")).assertEqual(ObjectPath("/some/path"));
ObjectPath("/").chompPrefix(ObjectPath("/some")).assertEqual(ObjectPath("/"));
ObjectPath("/").chompPrefix(ObjectPath("/")).assertEqual(ObjectPath("/"));
}
unittest {
@ -418,6 +495,8 @@ struct DBusAny {
return float64;
} else static if (is(T == string)) {
return str;
} else static if (is(T == InterfaceName) || is(T == BusName)) {
return cast(T) str;
} else static if (is(T == ObjectPath)) {
return obj;
} else static if (is(T == bool)) {
@ -697,14 +776,23 @@ enum MessageType {
Signal
}
/// Represents a message in the dbus system. Use the constructor to
struct Message {
DBusMessage* msg;
deprecated("Use the constructor taking a BusName, ObjectPath and InterfaceName instead")
this(string dest, string path, string iface, string method) {
msg = dbus_message_new_method_call(dest.toStringz(), path.toStringz(),
iface.toStringz(), method.toStringz());
}
/// Prepares a new method call to an "instance" "object" "interface" "method".
this(BusName dest, ObjectPath path, InterfaceName iface, string method) {
msg = dbus_message_new_method_call(dest.toStringz(),
path.value.toStringz(), iface.toStringz(), method.toStringz());
}
/// Wraps an existing low level message object.
this(DBusMessage* m) {
msg = m;
}
@ -714,7 +802,10 @@ struct Message {
}
~this() {
dbus_message_unref(msg);
if (msg) {
dbus_message_unref(msg);
msg = null;
}
}
/// Creates a new iterator and puts in the arguments for calling a method.
@ -769,16 +860,16 @@ struct Message {
return cStr.fromStringz().idup;
}
string path() {
ObjectPath path() {
const(char)* cStr = dbus_message_get_path(msg);
assert(cStr != null);
return cStr.fromStringz().idup;
return ObjectPath(cStr.fromStringz().idup);
}
string iface() {
InterfaceName iface() {
const(char)* cStr = dbus_message_get_interface(msg);
assert(cStr != null);
return cStr.fromStringz().idup;
return interfaceName(cStr.fromStringz().idup);
}
string member() {
@ -798,7 +889,7 @@ struct Message {
unittest {
import dunit.toolkit;
auto msg = Message("org.example.test", "/test", "org.example.testing", "testMethod");
auto msg = Message(busName("org.example.test"), ObjectPath("/test"), interfaceName("org.example.testing"), "testMethod");
msg.path().assertEqual("/test");
}
@ -876,7 +967,7 @@ unittest {
@(No.DBusMarshal) uint g;
}
Message msg = Message("org.example.wow", "/wut", "org.test.iface", "meth3");
Message msg = Message(busName("org.example.wow"), ObjectPath("/wut"), interfaceName("org.test.iface"), "meth3");
__gshared int dummy;

View file

@ -55,7 +55,7 @@ template allCanDBus(TS...) {
+/
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);
double, string, ObjectPath, InterfaceName, BusName);
template basicDBus(T) {
static if (staticIndexOf!(T, BasicTypes) >= 0) {
@ -126,7 +126,7 @@ string typeSig(T)()
return "t";
} else static if (is(T == double)) {
return "d";
} else static if (is(T == string)) {
} else static if (is(T == string) || is(T == InterfaceName) || is(T == BusName)) {
return "s";
} else static if (is(T == ObjectPath)) {
return "o";
@ -208,6 +208,10 @@ int typeCode(T)()
unittest {
import dunit.toolkit;
static assert(canDBus!ObjectPath);
static assert(canDBus!InterfaceName);
static assert(canDBus!BusName);
// basics
typeSig!int().assertEqual("i");
typeSig!bool().assertEqual("b");
@ -276,8 +280,8 @@ unittest {
sig.assertEqual("t");
static string sig2 = typeSig!(Tuple!(int, string, string));
sig2.assertEqual("(iss)");
static string sig3 = typeSigAll!(int, string, string);
sig3.assertEqual("iss");
static string sig3 = typeSigAll!(int, string, InterfaceName, BusName);
sig3.assertEqual("isss");
}
private template AllowedFieldTypes(S)