As Papyrus has been built out, I've found myself repeating a lot of similar code, particularly around things HTTP method annotations or request coding annotations.
For each of these, I find myself having to either:
- Define a separate
macro
and either create a bunch of separate macro types in the compiler plugin - Have the macro's type run some custom logic based on the literal string of the macro definition
What do we think about something similar to a typealias for macros to reduce the amount of boilerplate required for similar macros and make them easier to extend? It could take some arguments and pass those through to another macro, like how typealises can pass types through to another type.
For example:
// using default arguments
@attached(peer)
macro HTTP(_ method: String) = #externalMacro(module: "MyPlugin", type: "HTTPMacro")
// more refined HTTP macros - no separate Plugin type needed
typealias GET = HTTP("GET")
typealias POST = HTTP("POST")
typealias DELETE = HTTP("DELETE")
// passing arguments to add a layer of type safety without an extra macro
@attached(peer)
macro Converter(_ requestEncoder: EncoderProtocol? = nil, _ responseDecoder: EncoderProtocol? = nil) = #externalMacro(module: "MyPlugin", type: "EndpointConverterMacro")
// more refined Converters - no separate Plugin type needed
typealias JSON(encoder: JSONEncoder, decoder: JSONDecoder) = Converter(encoder, decoder)
typealias URLForm(_ encoder: URLFormEncoder) = Converter(encoder)
typealias MultiPart(_ encoder: MultipartEncoder) = Converter(encoder)
This would also make it much easier to extend the library without adding additional macro types (that would inherently requiring a separate build plugin).
Continuing with the HTTP request library use case consider how easy additional functionality would be to add in separate libraries with something like this.
protocol RequestMiddleware {
func handle(request: Request, next: (Request) async throws -> Response) async throws
}
/// Adds a middleware to run when a request is made.
@attached(peer)
macro Middleware(_ middleware: RequestMiddleware)
/* ... and in separate library ... */
/// @Curl will add a `CurlLoggingMiddleware` using the `Middleware` macro.
typealias Curl = Middleware(CurlLoggingMiddleware())
struct CurlLoggingMiddleware: RequestMiddleware {
func handle(request: Request, next: (Request) async throws -> Response) async throws {
// log a cURL command of the request for debugging purposes
}
}