import Vapor // Modified from Vapor.ErrorMiddleware public final class CustomErrorMiddleware: Middleware { // Default response public struct ErrorResponse: Codable { var error: Bool var reason: String } public init(environment: Environment) { self.environment = environment self.errorMiddleware = ErrorMiddleware.`default`(environment: environment) } public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { // Let ErrorMiddleware handle API endpoint errors if let acceptHeader = request.headers.first(name: "Accept"), acceptHeader == "application/json" { return errorMiddleware.respond(to: request, chainingTo: next) } return next.respond(to: request).flatMapErrorThrowing { error in try self.makeResponse(with: request, reason: error) } } // ------------------------------------- private func makeResponse(with req: Request, reason error: Error) throws -> Response { let reason: String let status: HTTPResponseStatus var headers: HTTPHeaders let source: ErrorSource // Inspect the error type and extract what data we can. switch error { case let debugAbort as (DebuggableError & AbortError): (reason, status, headers, source) = (debugAbort.reason, debugAbort.status, debugAbort.headers, debugAbort.source ?? .capture()) case let abort as AbortError: (reason, status, headers, source) = (abort.reason, abort.status, abort.headers, .capture()) case let debugErr as DebuggableError: (reason, status, headers, source) = (debugErr.reason, .internalServerError, [:], debugErr.source ?? .capture()) default: // In debug mode, provide the error description; otherwise hide it to avoid sensitive data disclosure. reason = environment.isRelease ? "Something went wrong." : String(describing: error) (status, headers, source) = (.internalServerError, [:], .capture()) } // Report the error req.logger.report(error: error, file: source.file, function: source.function, line: source.line) headers.contentType = .html let statusCode = String(status.code) var body = Response.Body() // Display error in toast message for HTMX requests if let isHTMXHeader = req.headers.first(name: "HX-Request"), isHTMXHeader == "true" { let state = try getState(request: req) // Only set a new toast if the endpoint hasnt already if state.toast.message.isEmpty { state.toast = ToastState(message: reason, title: "Error \(statusCode)", level: ToastState.Level.error) } headers.add(name: "HX-Trigger", value: "toast") } // Render error to a page else { body = Response.Body(string: MainLayout(title: "Error \(statusCode))") { ErrorPage(status: statusCode, reason: reason) }.render()) } // Create a Response with appropriate status return Response(status: status, headers: headers, body: body) } private let environment: Environment private let errorMiddleware: ErrorMiddleware }