forked from vapor/vapor
/
ErrorMiddleware.swift
109 lines (98 loc) 路 4.28 KB
/
ErrorMiddleware.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/// Captures all errors and transforms them into an internal server error HTTP response.
public final class ErrorMiddleware: Middleware, ServiceType {
/// Structure of `ErrorMiddleware` default response.
internal struct ErrorResponse: Codable {
/// Always `true` to indicate this is a non-typical JSON response.
var error: Bool
/// The reason for the error.
var reason: String
}
/// See `ServiceType`.
public static func makeService(for worker: Container) throws -> ErrorMiddleware {
return try .default(environment: worker.environment, log: worker.make())
}
/// Create a default `ErrorMiddleware`. Logs errors to a `Logger` based on `Environment`
/// and converts `Error` to `Response` based on conformance to `AbortError` and `Debuggable`.
///
/// - parameters:
/// - environment: The environment to respect when presenting errors.
/// - log: Log destination.
public static func `default`(environment: Environment, log: Logger) -> ErrorMiddleware {
return .init { req, error in
// log the error
log.report(error: error,
path: req.http.urlString,
verbose: !environment.isRelease)
// variables to determine
let status: HTTPResponseStatus
let reason: String
let headers: HTTPHeaders
// inspect the error type
switch error {
case let abort as AbortError:
// this is an abort error, we should use its status, reason, and headers
reason = abort.reason
status = abort.status
headers = abort.headers
case let validation as ValidationError:
// this is a validation error
reason = validation.reason
status = .badRequest
headers = [:]
case let debuggable as Debuggable where !environment.isRelease:
// if not release mode, and error is debuggable, provide debug
// info directly to the developer
reason = debuggable.reason
status = .internalServerError
headers = [:]
default:
// not an abort error, and not debuggable or in dev mode
// just deliver a generic 500 to avoid exposing any sensitive error info
reason = "Something went wrong."
status = .internalServerError
headers = [:]
}
// create a Response with appropriate status
let res = req.response(http: .init(status: status, headers: headers))
// attempt to serialize the error to json
do {
let errorResponse = ErrorResponse(error: true, reason: reason)
res.http.body = try HTTPBody(data: JSONEncoder().encode(errorResponse))
res.http.headers.replaceOrAdd(name: .contentType, value: "application/json; charset=utf-8")
} catch {
res.http.body = HTTPBody(string: "Oops: \(error)")
res.http.headers.replaceOrAdd(name: .contentType, value: "text/plain; charset=utf-8")
}
return res
}
}
/// Error-handling closure.
private let closure: (Request, Error) -> (Response)
/// Create a new `ErrorMiddleware`.
///
/// - parameters:
/// - closure: Error-handling closure. Converts `Error` to `Response`.
public init(_ closure: @escaping (Request, Error) -> (Response)) {
self.closure = closure
}
/// See `Middleware`.
public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {
let response: Future<Response>
do {
response = try next.respond(to: req)
} catch {
response = req.eventLoop.newFailedFuture(error: error)
}
return response.catchFlatMap { error in
if let response = error as? ResponseEncodable {
do {
return try response.encode(for: req)
} catch {
return req.future(self.closure(req, error))
}
} else {
return req.future(self.closure(req, error))
}
}
}
}