/// It always serves a liveness check at `/health`; when a `Probe` is supplied it also serves a readiness check at `/health/ready` that reports
/// whether the service's database is reachable. The two are kept distinct so an orchestrator can restart on liveness failure but only withhold traffic on
/// readiness failure.
///
/// - Note: `Context` is the request context the routes are resolved against, and must match the context of the router the routes are added to.
/// The probe consulted for the readiness check, or `nil` when only liveness is served.
privateletprobe:Probe?
// MARK: Initializers
/// Creates a health controller.
/// - Parameter probe: the probe consulted by the readiness route; when `nil`, only the liveness route is served.
publicinit(
probe:Probe?=nil
){
self.probe=probe
}
}
// MARK: - RouterController
extensionHealthController:RouterController{
// MARK: Properties
publicvarroutes:RouteCollection<Context>{
letroutes=RouteCollection(context:Context.self)
routes.get(
.Health.check,
use:check
)
ifprobe!=nil{
routes.get(
.Health.ready,
use:ready
)
}
returnroutes
}
}
// MARK: - Helpers
privateextensionHealthController{
// MARK: Methods
/// Handles a request for the liveness check.
///
/// Returns a constant JSON body built directly per request — the payload is a tiny literal with no rendering step, so there is nothing to pre-render or
/// cache. It reports only that the process is up, with no dependency check, so an orchestrator restarts the process only when the process itself is
/// unresponsive.
/// - Parameters:
/// - request: the incoming request.
/// - context: the context the request is resolved against.
/// - Returns: a `200 OK` response carrying the static JSON status payload.
@Sendable
funccheck(
request:Request,
context:someRequestContext
)->Response{
json(
status:.ok,
payload:.Payload.live
)
}
/// Handles a request for the readiness check.
///
/// Consults the `Probe` supplied at initialization and reports `200 OK` when the service's database is reachable, or `503 Service Unavailable`
/// otherwise, so a load balancer withholds traffic from an instance that cannot yet serve it without restarting the process.
/// - Parameters:
/// - request: the incoming request.
/// - context: the context the request is resolved against.
/// - Returns: a `200 OK` response when ready, or `503 Service Unavailable` when not.
@Sendable
funcready(
request:Request,
context:someRequestContext
)async->Response{
guardawaitprobe?()==trueelse{
returnjson(
status:.serviceUnavailable,
payload:.Payload.unavailable
)
}
returnjson(
status:.ok,
payload:.Payload.ready
)
}
/// Builds a JSON response carrying the given status and payload.