Hiding XPCRouteWith(out)MessageWith(out)Reply
from public API.
#32
Replies: 3 comments 11 replies
-
Related question: as part of #17, should "no reply payload but with an error handler" routes have their own new types (2 new types: I think the latter case might be nicer. The client's sending functions can use the existing reply-less route types, and have an optional error-handler. When the error handler is non-nil, the client can encode some metadata into the message that indicates to the server that an error response is welcome. When the server sees this indicator, it will reply with the error (if any). |
Beta Was this translation helpful? Give feedback.
-
(Out there idea) I've used this in a different context, but it's possible to use closure types as DSL for expressing the type of inputs/outputs of a process. It leads to this syntax at the call site: XPCRoute.named("1", signature: (() -> Void).self)
XPCRoute.named("2", signature: (() -> Int).self)
XPCRoute.named("3", signature: (() throws -> Void).self)
XPCRoute.named("4", signature: ((String) -> Void).self)
XPCRoute.named("5", signature: ((String) -> Int).self)
XPCRoute.named("6", signature: ((String) throws -> Void).self) Each one of these calls a different overload, and can return a different type. Here's a full self-contained impl/// A route that can't receive a message and is expected to reply.
public struct XPCRouteWithoutMessageWithReply<R: Codable> {
let route: XPCRoute
/// Initializes the route.
///
/// - Parameters:
/// - _: Zero or more `String`s naming the route.
/// - replyType: The expected type the server will respond with if successful.
public init(_ pathComponents: [String], replyType: R.Type) {
self.route = XPCRoute(pathComponents: pathComponents, messageType: nil, replyType: replyType, hasErrorHandler: false)
}
}
/// A route that receives a message and is expected to reply.
public struct XPCRouteWithMessageWithReply<M: Codable, R: Codable> {
let route: XPCRoute
/// Initializes the route.
///
/// - Parameters:
/// - _: Zero or more `String`s naming the route.
/// - messageType: The expected type the client will be passed when sending a message to this route.
/// - replyType: The expected type the server will respond with if successful.
public init(_ pathComponents: [String], messageType: M.Type, replyType: R.Type) {
self.route = XPCRoute(pathComponents: pathComponents, messageType: messageType, replyType: replyType, hasErrorHandler: false)
}
}
/// A route that can't receive a message and will not reply.
public struct XPCRouteWithoutMessageWithoutReply {
let route: XPCRoute
/// Initializes the route.
///
/// - Parameters:
/// - _: Zero or more `String`s naming the route.
public init(_ pathComponents: [String], hasErrorHandler: Bool = false) {
self.route = XPCRoute(pathComponents: pathComponents, messageType: nil, replyType: nil, hasErrorHandler: hasErrorHandler)
}
}
/// A route that receives a message and will not reply.
public struct XPCRouteWithMessageWithoutReply<M: Codable> {
let route: XPCRoute
/// Initializes the route.
///
/// - Parameters:
/// - _: Zero or more `String`s naming the route.
/// - messageType: The expected type the client will be passed when sending a message to this route.
public init(_ pathComponents: [String], messageType: M.Type, hasErrorHandler: Bool = false) {
self.route = XPCRoute(pathComponents: pathComponents, messageType: messageType, replyType: nil, hasErrorHandler: hasErrorHandler)
}
}
struct XPCRoute {
let pathComponents: [String]
let messageType: Any.Type?
let replyType: Any.Type?
let hasErrorHandler: Bool
}
extension XPCRoute {
static func named(_ name: String..., signature: (() -> Void).Type) -> XPCRouteWithoutMessageWithoutReply {
print("\(name): I have no message payload and no reply payload.")
return XPCRouteWithoutMessageWithoutReply(name, hasErrorHandler: false)
}
static func named<R: Codable>(_ name: String..., signature: (() -> R).Type) -> XPCRouteWithoutMessageWithReply<R> {
print("\(name): I have no message payload and a reply payload (\(R.self)).")
return XPCRouteWithoutMessageWithReply(name, replyType: R.self)
}
static func named(_ name: String..., signature: (() throws -> Void).Type) -> XPCRouteWithoutMessageWithoutReply {
print("\(name): I have no message payload and an error handler.")
return XPCRouteWithoutMessageWithoutReply(name, hasErrorHandler: true)
}
static func named<M: Codable>(_ name: String..., signature: ((M) -> Void).Type) -> XPCRouteWithMessageWithoutReply<M> {
print("\(name): I have a message payload (\(M.self)) and no reply payload.")
return XPCRouteWithMessageWithoutReply(name, messageType: M.self, hasErrorHandler: false)
}
static func named<M: Codable, R: Codable>(_ name: String..., signature: ((M) -> R).Type) -> XPCRouteWithMessageWithReply<M, R> {
print("\(name): I have a message payload (\(M.self)) and a reply payload (\(R.self)).")
return XPCRouteWithMessageWithReply(name, messageType: M.self, replyType: R.self)
}
static func named<M: Codable>(_ name: String..., signature: ((M) throws -> Void).Type) -> XPCRouteWithMessageWithoutReply<M> {
print("\(name): I have a message payload (\(M.self)) and an error handler.")
return XPCRouteWithMessageWithoutReply(name, messageType: M.self, hasErrorHandler: true)
}
}
_ = XPCRoute.named("1", signature: (() -> Void).self)
_ = XPCRoute.named("2", signature: (() -> Int).self)
_ = XPCRoute.named("3", signature: (() throws -> Void).self)
_ = XPCRoute.named("4", signature: ((String) -> Void).self)
_ = XPCRoute.named("5", signature: ((String) -> Int).self)
_ = XPCRoute.named("6", signature: ((String) throws -> Void).self) Just dropping this as a little "in case you're curious" because I thought it was neat. I wouldn't necessarily recommend it, though :) |
Beta Was this translation helpful? Give feedback.
-
What's the problem that's trying to be addressed with this? Is it that at the call site this approach is a bit more concise? If so, yes I can see some appeal to that. |
Beta Was this translation helpful? Give feedback.
-
Routes are currently constructed via the public initializers on these route types.
I think the type themselves are useful, because of the way they allow the typesystem to protect from a mismatch between the route type and the sending function.
Perhaps we should use some public factory methods on
XPCRoute
. We can't fully hide the route types (nor would we want to, because that's what protects against aforementioned mismatches), but they can be tucked away into the return type of the methods, rather than being an explicit part of the written code.Here's what the call sites might look like:
XPCRoute.withoutMessage()
XPCRoute.withoutMessage(withReply: R.self)
XPCRoute.withMessage(M.self)
XPCRoute.withMessage(M.self, withReply: R.self)
Thoughts?
Beta Was this translation helpful? Give feedback.
All reactions