Macros for FlyingFox allowing handlers to annotate functions with routes.
FlyingFoxMacros can be installed by using Swift Package Manager.
Note: FlyingFoxMacros requires Swift 6.1 on Xcode 16.4+. It runs on iOS 13+, tvOS 13+, watchOS 8+, macOS 10.15+ and Linux. Windows 10 support is experimental.
To install using Swift Package Manager, add this to the dependencies: section in your Package.swift file:
.package(url: "https://github.com/swhitty/FlyingFox.git", .upToNextMajor(from: "0.25.0")),
.package(url: "https://github.com/swhitty/FlyingFoxMacros.git", .upToNextMajor(from: "0.2.0"))Methods can be annotated with HTTPRoute to automatically syntesise a HTTPHandler.
import FlyingFox
import FlyingFoxMacros
@HTTPHandler
struct MyHandler {
@HTTPRoute("/ping")
func ping() { }
@HTTPRoute("/pong")
func getPong(_ request: HTTPRequest) -> HTTPResponse {
HTTPResponse(statusCode: .accepted)
}
@JSONRoute("POST /account")
func createAccount(body: AccountRequest) -> AccountResponse {
AccountResponse(id: UUID(), balance: body.balance)
}
}
let server = HTTPServer(port: 80, handler: MyHandler())
try await server.start()The annotations are implemented via SE-0389 Attached Macros.
The macro synthesises conformance to HTTPHandler delegating handling to the first matching route. Expanding the example above to the following:
func handleRequest(_ request: HTTPRequest) async throws -> HTTPResponse {
if await HTTPRoute("/ping") ~= request {
ping()
return HTTPResponse(statusCode: .ok, headers: [:])
}
if await HTTPRoute("/pong") ~= request {
return getPong(request)
}
if await HTTPRoute("POST /account") ~= request {
let body = try await JSONDecoder().decode(AccountRequest.self, from: request.bodyData)
let ret = createAccount(body: body)
return try HTTPResponse(
statusCode: .ok,
headers: [.contentType: "application/json"],
body: JSONEncoder().encode(ret)
)
}
throw HTTPUnhandledError()
}@HTTPRoute annotations can specify specific properties of the returned HTTPResponse:
@HTTPRoute("/refresh", statusCode: .teapot, headers: [.eTag: "t3a"])
func refresh()@JSONRoute annotations can be added to functions that accept Codable types. JSONDecoder decodes the body that is passed to the method, the returned object is encoded to the response body using JSONEncoder:
@JSONRoute("POST /account")
func createAccount(body: AccountRequest) -> AccountResponseThe original HTTPRequest can be optionally passed to the method:
@JSONRoute("POST /account")
func createAccount(request: HTTPRequest, body: AccountRequest) -> AccountResponseJSONEncoder / JSONDecoder instances can be passed for specific JSON coding strategies:
@JSONRoute("GET /account", encoder: JSONEncoder())
func getAccount() -> AccountResponse