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 ### Basic types
These are the basic types supported by `ddbus`: 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: ### Overview of mappings of other types:

View file

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

View file

@ -3,18 +3,18 @@ import ddbus;
void testServe(Connection conn) { void testServe(Connection conn) {
auto router = new MessageRouter(); 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) { router.setHandler!(int,int)(patt,(int par) {
writeln("Called with ", par); writeln("Called with ", par);
return 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) { router.setHandler!(void,int)(patt,(int par) {
writeln("Signalled with ", par); writeln("Signalled with ", par);
}); });
registerRouter(conn, router); registerRouter(conn, router);
writeln("Getting name..."); writeln("Getting name...");
bool gotem = requestName(conn, "ca.thume.ddbus.test"); bool gotem = requestName(conn, busName("ca.thume.ddbus.test"));
writeln("Got name: ",gotem); writeln("Got name: ",gotem);
simpleMainLoop(conn); simpleMainLoop(conn);
} }

View file

@ -5,9 +5,9 @@ import ddbus.thin;
import ddbus.c_lib; import ddbus.c_lib;
import std.string; import std.string;
enum BusService = "org.freedesktop.DBus"; enum BusService = busName("org.freedesktop.DBus");
enum BusPath = "/org/freedesktop/DBus"; enum BusInterface = interfaceName("org.freedesktop.DBus");
enum BusInterface = "org.freedesktop.DBus"; enum BusPath = ObjectPath("/org/freedesktop/DBus");
enum NameFlags { enum NameFlags {
AllowReplace = 1, AllowReplace = 1,
@ -15,10 +15,16 @@ enum NameFlags {
NoQueue = 4 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. /// Requests a DBus well-known name.
/// returns if the name is owned after the call. /// returns if the name is owned after the call.
/// Involves blocking call on a DBus method, may throw an exception on failure. /// 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) { NameFlags flags = NameFlags.NoQueue | NameFlags.AllowReplace) {
auto msg = Message(BusService, BusPath, BusInterface, "RequestName"); auto msg = Message(BusService, BusPath, BusInterface, "RequestName");
msg.build(name, cast(uint)(flags)); msg.build(name, cast(uint)(flags));
@ -44,5 +50,5 @@ unittest {
import dunit.toolkit; import dunit.toolkit;
Connection conn = connectToBus(); 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) { if (allCanDBus!TS) {
foreach (index, arg; args) { foreach (index, arg; args) {
alias TS[index] T; 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(); immutable(char)* cStr = arg.toStringz();
dbus_message_iter_append_basic(iter, typeCode!T, &cStr); dbus_message_iter_append_basic(iter, typeCode!T, &cStr);
} else static if (is(T == ObjectPath)) { } else static if (is(T == ObjectPath)) {
@ -172,7 +172,7 @@ void buildIter(TS...)(DBusMessageIter* iter, TS args)
} }
T readIter(T)(DBusMessageIter* iter) T readIter(T)(DBusMessageIter* iter)
if (is(T == enum)) { if (is(T == enum) && !is(T == InterfaceName) && !is(T == BusName)) {
import std.algorithm.searching : canFind; import std.algorithm.searching : canFind;
alias OriginalType!T B; alias OriginalType!T B;
@ -198,7 +198,7 @@ T readIter(T)(DBusMessageIter* iter)
} }
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); auto argType = dbus_message_iter_get_arg_type(iter);
T ret; T ret;
@ -222,12 +222,12 @@ T readIter(T)(DBusMessageIter* iter)
enforce(argType == typeCode!T(), new TypeMismatchException(typeCode!T(), argType)); 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; const(char)* cStr;
dbus_message_iter_get_basic(iter, &cStr); dbus_message_iter_get_basic(iter, &cStr);
string str = cStr.fromStringz().idup; // copy string string str = cStr.fromStringz().idup; // copy string
static if (is(T == string)) { static if (is(T == string) || is(T == InterfaceName) || is(T == BusName)) {
ret = str; ret = cast(T)str;
} else { } else {
ret = ObjectPath(str); ret = ObjectPath(str);
} }
@ -378,7 +378,7 @@ unittest {
return Variant!T(data); 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; bool[] emptyB;
string[string] map; string[string] map;
map["hello"] = "world"; map["hello"] = "world";
@ -388,22 +388,25 @@ unittest {
anyVar.explicitVariant.assertEqual(false); anyVar.explicitVariant.assertEqual(false);
auto tupleMember = DBusAny(tuple(Variant!int(45), Variant!ushort(5), 32, auto tupleMember = DBusAny(tuple(Variant!int(45), Variant!ushort(5), 32,
[1, 2], tuple(variant(4), 5), map)); [1, 2], tuple(variant(4), 5), map));
Variant!DBusAny complexVar = variant(DBusAny(["hello world" : variant(DBusAny(1337)), Variant!DBusAny complexVar = variant(DBusAny([
"array value" : variant(DBusAny([42, 64])), "tuple value" "hello world": variant(DBusAny(1337)),
: variant(tupleMember), "optimized binary data" "array value": variant(DBusAny([42, 64])),
: variant(DBusAny(cast(ubyte[])[1, 2, 3, 4, 5, 6]))])); "tuple value": variant(tupleMember),
"optimized binary data": variant(DBusAny(cast(ubyte[])[1, 2, 3, 4, 5, 6]))
]));
complexVar.data.type.assertEqual('a'); complexVar.data.type.assertEqual('a');
complexVar.data.signature.assertEqual("{sv}".dup); complexVar.data.signature.assertEqual("{sv}".dup);
tupleMember.signature.assertEqual("(vviai(vi)a{ss})"); 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); emptyB, var([4, 2])), map, anyVar, complexVar);
msg.build(args.expand); 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.read!string().assertThrow!TypeMismatchException();
msg.readTuple!(Tuple!(int, bool, double)).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); msg.readTuple!(typeof(args))().assertEqual(args);
DBusMessageIter iter; DBusMessageIter iter;
@ -411,6 +414,7 @@ unittest {
readIter!int(&iter).assertEqual(5); readIter!int(&iter).assertEqual(5);
readIter!bool(&iter).assertEqual(true); readIter!bool(&iter).assertEqual(true);
readIter!string(&iter).assertEqual("wow"); readIter!string(&iter).assertEqual("wow");
readIter!InterfaceName(&iter).assertEqual(interfaceName("methodName"));
readIter!double(&iter).assertEqual(5.9); readIter!double(&iter).assertEqual(5.9);
readIter!(int[])(&iter).assertEqual([6, 5]); readIter!(int[])(&iter).assertEqual([6, 5]);
readIter!(Tuple!(double, int, string[][], bool[], Variant!(int[])))(&iter).assertEqual( 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. // There are two ways to read a dictionary, so duplicate the iterator to test both.
auto iter2 = iter; auto iter2 = iter;
readIter!(string[string])(&iter).assertEqual(["hello" : "world"]); readIter!(string[string])(&iter).assertEqual(["hello": "world"]);
auto dict = readIter!(DictionaryEntry!(string, string)[])(&iter2); auto dict = readIter!(DictionaryEntry!(string, string)[])(&iter2);
dict.length.assertEqual(1); dict.length.assertEqual(1);
dict[0].key.assertEqual("hello"); dict[0].key.assertEqual("hello");
@ -448,7 +452,7 @@ unittest {
alias V = Algebraic!(byte, short, int, long, string); 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 v1 = "hello from variant";
V v2 = cast(short) 345; V v2 = cast(short) 345;
msg.build(E.c, 4, 5u, 8u, v1, v2); msg.build(E.c, 4, 5u, 8u, v1, v2);

View file

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

View file

@ -8,17 +8,23 @@ import std.string;
import std.traits; import std.traits;
class PathIface { class PathIface {
this(Connection conn, string dest, ObjectPath path, string iface) { this(Connection conn, BusName dest, ObjectPath path, InterfaceName iface) {
this(conn, dest, path.value, iface);
}
this(Connection conn, string dest, string path, string iface) {
this.conn = conn; this.conn = conn;
this.dest = dest.toStringz(); this.dest = dest.toStringz();
this.path = path.toStringz(); this.path = path.value.toStringz();
this.iface = iface.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) Ret call(Ret, Args...)(string meth, Args args)
if (allCanDBus!Args && canDBus!Ret) { if (allCanDBus!Args && canDBus!Ret) {
Message msg = Message(dbus_message_new_method_call(dest, path, iface, meth.toStringz())); Message msg = Message(dbus_message_new_method_call(dest, path, iface, meth.toStringz()));
@ -43,15 +49,20 @@ unittest {
import dunit.toolkit; import dunit.toolkit;
Connection conn = connectToBus(); Connection conn = connectToBus();
PathIface obj = new PathIface(conn, "org.freedesktop.DBus", PathIface obj = new PathIface(conn, busName("org.freedesktop.DBus"),
"/org/freedesktop/DBus", "org.freedesktop.DBus"); ObjectPath("/org/freedesktop/DBus"), interfaceName("org.freedesktop.DBus"));
auto names = obj.GetNameOwner("org.freedesktop.DBus").to!string(); auto names = obj.GetNameOwner(interfaceName("org.freedesktop.DBus")).to!BusName();
names.assertEqual("org.freedesktop.DBus"); names.assertEqual(busName("org.freedesktop.DBus"));
obj.call!string("GetNameOwner", "org.freedesktop.DBus").assertEqual("org.freedesktop.DBus"); obj.call!BusName("GetNameOwner", interfaceName("org.freedesktop.DBus")).assertEqual(busName("org.freedesktop.DBus"));
} }
enum SignalMethod; 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. Registers all *possible* methods of an object in a router.
It will not register methods that use types that ddbus can't handle. 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 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. 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); MessagePattern patt = MessagePattern(path, iface, "", false);
foreach (member; __traits(allMembers, T)) { foreach (member; __traits(allMembers, T)) {
// dfmt off // dfmt off
@ -96,8 +107,8 @@ unittest {
auto o = new Tester; auto o = new Tester;
auto router = new MessageRouter; auto router = new MessageRouter;
registerMethods(router, "/", "ca.thume.test", o); registerMethods(router, ObjectPath("/"), interfaceName("ca.thume.test"), o);
MessagePattern patt = MessagePattern("/", "ca.thume.test", "wat"); MessagePattern patt = MessagePattern(ObjectPath("/"), interfaceName("ca.thume.test"), "wat");
router.callTable.assertHasKey(patt); router.callTable.assertHasKey(patt);
patt.method = "signalRecv"; patt.method = "signalRecv";
patt.signal = true; patt.signal = true;

View file

@ -22,6 +22,8 @@ import std.algorithm;
public import ddbus.exception : wrapErrors, DBusException; public import ddbus.exception : wrapErrors, DBusException;
struct ObjectPath { struct ObjectPath {
enum root = ObjectPath("/");
private string _value; private string _value;
this(string objPath) pure @safe { this(string objPath) pure @safe {
@ -44,10 +46,22 @@ struct ObjectPath {
return hashOf(_value); 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 { bool opEquals(ref const typeof(this) b) const pure @nogc nothrow @safe {
return _value == b._value; 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 { ObjectPath opBinary(string op : "~")(string rhs) const pure @safe {
if (!rhs.startsWith("/")) { if (!rhs.startsWith("/")) {
return opBinary!"~"(ObjectPath("/" ~ rhs)); return opBinary!"~"(ObjectPath("/" ~ rhs));
@ -83,6 +97,26 @@ struct ObjectPath {
_value = opBinary!"~"(rhs)._value; _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 Returns: `false` for empty strings or strings that don't match the
pattern `(/[0-9A-Za-z_]+)+|/`. pattern `(/[0-9A-Za-z_]+)+|/`.
@ -106,6 +140,35 @@ struct ObjectPath {
return objPath.representation.splitter('/').drop(1).all!(a => a.length return objPath.representation.splitter('/').drop(1).all!(a => a.length
&& a.all!(c => c.isAlphaNum || c == '_')); && 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 { unittest {
@ -118,6 +181,20 @@ unittest {
auto obj = ObjectPath(path); auto obj = ObjectPath(path);
obj.value.assertEqual(path); obj.value.assertEqual(path);
obj.toHash().assertEqual(path.hashOf); 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 { unittest {
@ -418,6 +495,8 @@ struct DBusAny {
return float64; return float64;
} else static if (is(T == string)) { } else static if (is(T == string)) {
return str; return str;
} else static if (is(T == InterfaceName) || is(T == BusName)) {
return cast(T) str;
} else static if (is(T == ObjectPath)) { } else static if (is(T == ObjectPath)) {
return obj; return obj;
} else static if (is(T == bool)) { } else static if (is(T == bool)) {
@ -697,14 +776,23 @@ enum MessageType {
Signal Signal
} }
/// Represents a message in the dbus system. Use the constructor to
struct Message { struct Message {
DBusMessage* msg; DBusMessage* msg;
deprecated("Use the constructor taking a BusName, ObjectPath and InterfaceName instead")
this(string dest, string path, string iface, string method) { this(string dest, string path, string iface, string method) {
msg = dbus_message_new_method_call(dest.toStringz(), path.toStringz(), msg = dbus_message_new_method_call(dest.toStringz(), path.toStringz(),
iface.toStringz(), method.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) { this(DBusMessage* m) {
msg = m; msg = m;
} }
@ -714,7 +802,10 @@ struct Message {
} }
~this() { ~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. /// Creates a new iterator and puts in the arguments for calling a method.
@ -769,16 +860,16 @@ struct Message {
return cStr.fromStringz().idup; return cStr.fromStringz().idup;
} }
string path() { ObjectPath path() {
const(char)* cStr = dbus_message_get_path(msg); const(char)* cStr = dbus_message_get_path(msg);
assert(cStr != null); assert(cStr != null);
return cStr.fromStringz().idup; return ObjectPath(cStr.fromStringz().idup);
} }
string iface() { InterfaceName iface() {
const(char)* cStr = dbus_message_get_interface(msg); const(char)* cStr = dbus_message_get_interface(msg);
assert(cStr != null); assert(cStr != null);
return cStr.fromStringz().idup; return interfaceName(cStr.fromStringz().idup);
} }
string member() { string member() {
@ -798,7 +889,7 @@ struct Message {
unittest { unittest {
import dunit.toolkit; 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"); msg.path().assertEqual("/test");
} }
@ -876,7 +967,7 @@ unittest {
@(No.DBusMarshal) uint g; @(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; __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 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, alias BasicTypes = AliasSeq!(bool, byte, short, ushort, int, uint, long, ulong,
double, string, ObjectPath); double, string, ObjectPath, InterfaceName, BusName);
template basicDBus(T) { template basicDBus(T) {
static if (staticIndexOf!(T, BasicTypes) >= 0) { static if (staticIndexOf!(T, BasicTypes) >= 0) {
@ -126,7 +126,7 @@ string typeSig(T)()
return "t"; return "t";
} else static if (is(T == double)) { } else static if (is(T == double)) {
return "d"; return "d";
} else static if (is(T == string)) { } else static if (is(T == string) || is(T == InterfaceName) || is(T == BusName)) {
return "s"; return "s";
} else static if (is(T == ObjectPath)) { } else static if (is(T == ObjectPath)) {
return "o"; return "o";
@ -208,6 +208,10 @@ int typeCode(T)()
unittest { unittest {
import dunit.toolkit; import dunit.toolkit;
static assert(canDBus!ObjectPath);
static assert(canDBus!InterfaceName);
static assert(canDBus!BusName);
// basics // basics
typeSig!int().assertEqual("i"); typeSig!int().assertEqual("i");
typeSig!bool().assertEqual("b"); typeSig!bool().assertEqual("b");
@ -276,8 +280,8 @@ unittest {
sig.assertEqual("t"); sig.assertEqual("t");
static string sig2 = typeSig!(Tuple!(int, string, string)); static string sig2 = typeSig!(Tuple!(int, string, string));
sig2.assertEqual("(iss)"); sig2.assertEqual("(iss)");
static string sig3 = typeSigAll!(int, string, string); static string sig3 = typeSigAll!(int, string, InterfaceName, BusName);
sig3.assertEqual("iss"); sig3.assertEqual("isss");
} }
private template AllowedFieldTypes(S) private template AllowedFieldTypes(S)