From db86451c7fe34aac6c59671803e47ad363b7c663 Mon Sep 17 00:00:00 2001 From: WebFreak001 Date: Sun, 17 Mar 2019 10:33:55 +0100 Subject: [PATCH] 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. --- Readme.md | 4 +- examples/client/source/app.d | 8 +-- examples/server/source/app.d | 6 +- source/ddbus/bus.d | 16 ++++-- source/ddbus/conv.d | 36 ++++++------ source/ddbus/router.d | 56 +++++++++++-------- source/ddbus/simple.d | 39 ++++++++----- source/ddbus/thin.d | 105 ++++++++++++++++++++++++++++++++--- source/ddbus/util.d | 12 ++-- 9 files changed, 206 insertions(+), 76 deletions(-) diff --git a/Readme.md b/Readme.md index 8c2cb77..403511d 100644 --- a/Readme.md +++ b/Readme.md @@ -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: diff --git a/examples/client/source/app.d b/examples/client/source/app.d index a6055ab..cc8e807 100644 --- a/examples/client/source/app.d +++ b/examples/client/source/app.d @@ -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); diff --git a/examples/server/source/app.d b/examples/server/source/app.d index 0c4c0f7..126c2e6 100644 --- a/examples/server/source/app.d +++ b/examples/server/source/app.d @@ -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); } diff --git a/source/ddbus/bus.d b/source/ddbus/bus.d index 9d500d2..70a9bdd 100644 --- a/source/ddbus/bus.d +++ b/source/ddbus/bus.d @@ -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(); } diff --git a/source/ddbus/conv.d b/source/ddbus/conv.d index 1fa16db..8644055 100644 --- a/source/ddbus/conv.d +++ b/source/ddbus/conv.d @@ -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); diff --git a/source/ddbus/router.d b/source/ddbus/router.d index 9aeb55d..ea02fc0 100644 --- a/source/ddbus/router.d +++ b/source/ddbus/router.d @@ -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 = ` `; + 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, ``, iface.front.iface); + formattedWrite(app, ``, cast(string)iface.front.iface); foreach (methodPatt; iface.array()) { formattedWrite(app, ``, methodPatt.method); @@ -174,14 +184,11 @@ class MessageRouter { app.put(""); } - 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 = ` `; - router.introspectXML("/root").assertEqual(introspectResult); + router.introspectXML(ObjectPath("/root")).assertEqual(introspectResult); static string introspectResult2 = ` `; - router.introspectXML("/root/foo").assertEqual(introspectResult2); + router.introspectXML(ObjectPath("/root/foo")).assertEqual(introspectResult2); static string introspectResult3 = ` `; - router.introspectXML("/root/fancy").assertEqual(introspectResult3); - router.introspectXML("/") + router.introspectXML(ObjectPath("/root/fancy")).assertEqual(introspectResult3); + router.introspectXML(ObjectPath("/")) .assertEndsWith(``); } diff --git a/source/ddbus/simple.d b/source/ddbus/simple.d index b602390..fde57ea 100644 --- a/source/ddbus/simple.d +++ b/source/ddbus/simple.d @@ -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; diff --git a/source/ddbus/thin.d b/source/ddbus/thin.d index e92ad1f..4f86c61 100644 --- a/source/ddbus/thin.d +++ b/source/ddbus/thin.d @@ -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; diff --git a/source/ddbus/util.d b/source/ddbus/util.d index 7ffc108..4d469ce 100644 --- a/source/ddbus/util.d +++ b/source/ddbus/util.d @@ -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)