Skip to content

Commit bd4d565

Browse files
authored
Merge pull request #21 from ptoffy/main
Add support for Swift 5.5 and async/await
2 parents f8d4517 + 6b06aff commit bd4d565

File tree

8 files changed

+145
-209
lines changed

8 files changed

+145
-209
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ on:
99
jobs:
1010
xenial:
1111
container:
12-
image: swift:5.2-xenial
12+
image: swift:5.5-xenial
1313
runs-on: ubuntu-latest
1414
steps:
1515
- uses: actions/checkout@v2
1616
- run: swift test --enable-test-discovery --enable-code-coverage
1717
bionic:
1818
container:
19-
image: swift:5.4-bionic
19+
image: swift:5.5-bionic
2020
runs-on: ubuntu-latest
2121
steps:
2222
- uses: actions/checkout@v2

Package.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
// swift-tools-version:5.2
1+
// swift-tools-version:5.5
22
import PackageDescription
33

44
let package = Package(
55
name: "LeafErrorMiddleware",
66
platforms: [
7-
.macOS(.v10_15),
7+
.macOS(.v12),
88
],
99
products: [
1010
.library(name: "LeafErrorMiddleware", targets: ["LeafErrorMiddleware"]),

Sources/LeafErrorMiddleware/LeafErrorMiddleware.swift

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,70 @@
11
import Vapor
22

33
/// Captures all errors and transforms them into an internal server error.
4-
public final class LeafErrorMiddleware<T: Encodable>: Middleware {
4+
public final class LeafErrorMiddleware<T: Encodable>: AsyncMiddleware {
5+
let contextGenerator: (HTTPStatus, Error, Request) async throws -> T
56

6-
let contextGenerator: ((HTTPStatus, Error, Request) -> EventLoopFuture<T>)
7-
8-
public init(contextGenerator: @escaping ((HTTPStatus, Error, Request) -> EventLoopFuture<T>)) {
7+
public init(contextGenerator: @escaping ((HTTPStatus, Error, Request) async throws -> T)) {
98
self.contextGenerator = contextGenerator
109
}
11-
10+
1211
/// See `Middleware.respond`
13-
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
14-
return next.respond(to: request).flatMap { res in
12+
public func respond(to request: Request, chainingTo next: AsyncResponder) async throws -> Response {
13+
do {
14+
let res = try await next.respond(to: request)
1515
if res.status.code >= HTTPResponseStatus.badRequest.code {
16-
return self.handleError(for: request, status: res.status, error: Abort(res.status))
16+
return try await handleError(for: request, status: res.status, error: Abort(res.status))
1717
} else {
18-
return res.encodeResponse(for: request)
18+
return try await res.encodeResponse(for: request)
1919
}
20-
}.flatMapError { error in
20+
} catch {
2121
request.logger.report(error: error)
22-
switch (error) {
23-
case let abort as AbortError:
24-
guard
25-
abort.status.representsError
22+
switch error {
23+
case let abort as AbortError:
24+
guard
25+
abort.status.representsError
2626
else {
2727
if let location = abort.headers[.location].first {
28-
return request.eventLoop.future(request.redirect(to: location))
28+
return request.redirect(to: location)
2929
} else {
30-
return self.handleError(for: request, status: abort.status, error: error)
30+
return try await handleError(for: request, status: abort.status, error: error)
3131
}
32-
}
33-
return self.handleError(for: request, status: abort.status, error: error)
34-
default:
35-
return self.handleError(for: request, status: .internalServerError, error: error)
32+
}
33+
return try await handleError(for: request, status: abort.status, error: error)
34+
default:
35+
return try await handleError(for: request, status: .internalServerError, error: error)
3636
}
3737
}
3838
}
39-
40-
private func handleError(for req: Request, status: HTTPStatus, error: Error) -> EventLoopFuture<Response> {
39+
40+
private func handleError(for request: Request, status: HTTPStatus, error: Error) async throws -> Response {
4141
if status == .notFound {
42-
return contextGenerator(status, error, req).flatMap { context in
43-
return req.view.render("404", context).encodeResponse(for: req).map { res in
44-
res.status = status
45-
return res
46-
}
47-
}.flatMapError { newError in
48-
return self.renderServerErrorPage(for: status, request: req, error: newError)
42+
do {
43+
let context = try await contextGenerator(status, error, request)
44+
let res = try await request.view.render("404", context).encodeResponse(for: request).get()
45+
res.status = status
46+
return res
47+
} catch {
48+
return try await renderServerErrorPage(for: status, request: request, error: error)
4949
}
5050
}
51-
return renderServerErrorPage(for: status, request: req, error: error)
51+
return try await renderServerErrorPage(for: status, request: request, error: error)
5252
}
53-
54-
private func renderServerErrorPage(for status: HTTPStatus, request: Request, error: Error) -> EventLoopFuture<Response> {
55-
return contextGenerator(status, error, request).flatMap { context in
56-
request.logger.error("Internal server error. Status: \(status.code) - path: \(request.url)")
57-
return request.view.render("serverError", context).encodeResponse(for: request).map { res in
58-
res.status = status
59-
return res
60-
}
61-
}.flatMapError { error -> EventLoopFuture<Response> in
53+
54+
private func renderServerErrorPage(for status: HTTPStatus, request: Request, error: Error) async throws -> Response {
55+
do {
56+
let context = try await contextGenerator(status, error, request)
57+
request.logger.error("Internal server error. Status: \(status.code) - path: \(request.url)")
58+
let res = try await request.view.render("serverError", context).encodeResponse(for: request).get()
59+
res.status = status
60+
return res
61+
} catch {
6262
let body = "<h1>Internal Error</h1><p>There was an internal error. Please try again later.</p>"
6363
request.logger.error("Failed to render custom error page - \(error)")
64-
return body.encodeResponse(for: request).map { res in
65-
res.status = status
66-
res.headers.replaceOrAdd(name: .contentType, value: "text/html; charset=utf-8")
67-
return res
68-
}
64+
let res = try await body.encodeResponse(for: request)
65+
res.status = status
66+
res.headers.replaceOrAdd(name: .contentType, value: "text/html; charset=utf-8")
67+
return res
6968
}
7069
}
7170
}

Sources/LeafErrorMiddleware/LeafErrorMiddlewareDefaultGenerator.swift

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,15 @@
11
import Vapor
22

3-
@available(*, deprecated, renamed: "LeafErrorMiddlewareDefaultGenerator")
4-
public enum LeafErorrMiddlewareDefaultGenerator {
5-
static func generate(_ status: HTTPStatus, _ error: Error, _ req: Request) -> EventLoopFuture<DefaultContext> {
6-
let reason: String?
7-
if let abortError = error as? AbortError {
8-
reason = abortError.reason
9-
} else {
10-
reason = nil
11-
}
12-
let context = DefaultContext(status: status.code.description, statusMessage: status.reasonPhrase, reason: reason)
13-
return req.eventLoop.future(context )
14-
}
15-
16-
public static func build() -> LeafErrorMiddleware<DefaultContext> {
17-
LeafErrorMiddleware(contextGenerator: generate)
18-
}
19-
}
20-
213
public enum LeafErrorMiddlewareDefaultGenerator {
22-
static func generate(_ status: HTTPStatus, _ error: Error, _ req: Request) -> EventLoopFuture<DefaultContext> {
4+
static func generate(_ status: HTTPStatus, _ error: Error, _ req: Request) async throws -> DefaultContext {
235
let reason: String?
246
if let abortError = error as? AbortError {
257
reason = abortError.reason
268
} else {
279
reason = nil
2810
}
2911
let context = DefaultContext(status: status.code.description, statusMessage: status.reasonPhrase, reason: reason)
30-
return req.eventLoop.future(context )
12+
return context
3113
}
3214

3315
public static func build() -> LeafErrorMiddleware<DefaultContext> {

Tests/LeafErrorMiddlewareTests/CustomGeneratorTests.swift

Lines changed: 37 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,76 @@
1-
import XCTest
21
import LeafErrorMiddleware
3-
import Vapor
42
@testable import Logging
3+
import Vapor
4+
import XCTest
55

66
struct AContext: Encodable {
77
let trigger: Bool
88
}
99

1010
class CustomGeneratorTests: XCTestCase {
11-
1211
// MARK: - Properties
12+
1313
var app: Application!
1414
var viewRenderer: ThrowingViewRenderer!
1515
var logger = CapturingLogger()
1616
var eventLoopGroup: EventLoopGroup!
1717

1818
// MARK: - Overrides
19+
1920
override func setUpWithError() throws {
2021
eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
2122
viewRenderer = ThrowingViewRenderer(eventLoop: eventLoopGroup.next())
2223
LoggingSystem.bootstrapInternal { _ in
23-
return self.logger
24+
self.logger
2425
}
2526
app = Application(.testing, .shared(eventLoopGroup))
2627

2728
app.views.use { _ in
28-
return self.viewRenderer
29+
self.viewRenderer
2930
}
3031

3132
func routes(_ router: RoutesBuilder) throws {
33+
router.get("ok") { _ in
34+
"ok"
35+
}
3236

33-
router.get("ok") { req in
34-
return "ok"
37+
router.get("404") { _ -> HTTPStatus in
38+
.notFound
3539
}
3640

37-
router.get("404") { req -> HTTPStatus in
38-
return .notFound
41+
router.get("403") { _ -> Response in
42+
throw Abort(.forbidden)
3943
}
4044

41-
router.get("serverError") { req -> EventLoopFuture<Response> in
45+
router.get("serverError") { _ -> Response in
4246
throw Abort(.internalServerError)
4347
}
4448

45-
router.get("unknownError") { req -> EventLoopFuture<Response> in
49+
router.get("unknownError") { _ -> Response in
4650
throw TestError()
4751
}
4852

49-
router.get("unauthorized") { req -> EventLoopFuture<Response> in
53+
router.get("unauthorized") { _ -> Response in
5054
throw Abort(.unauthorized)
5155
}
5256

53-
router.get("future404") { req -> EventLoopFuture<Response> in
54-
return req.eventLoop.future(error: Abort(.notFound))
55-
}
56-
57-
router.get("future403") { req -> EventLoopFuture<Response> in
58-
return req.eventLoop.future(error: Abort(.forbidden))
59-
}
60-
61-
router.get("future303") { req -> EventLoopFuture<Response> in
62-
return req.eventLoop.future(error: Abort.redirect(to: "ok"))
63-
}
64-
65-
router.get("future404NoAbort") { req -> EventLoopFuture<HTTPStatus> in
66-
return req.eventLoop.future(.notFound)
57+
router.get("303") { _ -> Response in
58+
throw Abort.redirect(to: "ok")
6759
}
6860

69-
router.get("404withReason") { req -> HTTPStatus in
61+
router.get("404withReason") { _ -> HTTPStatus in
7062
throw Abort(.notFound, reason: "Could not find it")
7163
}
7264

73-
router.get("500withReason") { req -> HTTPStatus in
65+
router.get("500withReason") { _ -> HTTPStatus in
7466
throw Abort(.badGateway, reason: "I messed up")
7567
}
7668
}
7769

7870
try routes(app)
7971

80-
let leafMiddleware = LeafErrorMiddleware() { status, error, req -> EventLoopFuture<AContext> in
81-
return req.eventLoop.future(AContext(trigger: true))
72+
let leafMiddleware = LeafErrorMiddleware { status, error, req async throws -> AContext in
73+
AContext(trigger: true)
8274
}
8375
app.middleware.use(leafMiddleware)
8476
}
@@ -139,36 +131,24 @@ class CustomGeneratorTests: XCTestCase {
139131
XCTAssertEqual(viewRenderer.leafPath, "serverError")
140132
}
141133

142-
func testNonAbort404IsCaughtCorrectly() throws {
143-
let response = try app.getResponse(to: "/404")
144-
XCTAssertEqual(response.status, .notFound)
145-
XCTAssertEqual(viewRenderer.leafPath, "404")
146-
}
147-
148-
func testThatFuture404IsCaughtCorrectly() throws {
149-
let response = try app.getResponse(to: "/future404")
150-
XCTAssertEqual(response.status, .notFound)
151-
XCTAssertEqual(viewRenderer.leafPath, "404")
134+
func testThatRedirectIsNotCaught() throws {
135+
let response = try app.getResponse(to: "/303")
136+
XCTAssertEqual(response.status, .seeOther)
137+
XCTAssertEqual(response.headers[.location].first, "ok")
152138
}
153139

154-
func testFutureNonAbort404IsCaughtCorrectly() throws {
155-
let response = try app.getResponse(to: "/future404NoAbort")
140+
func testNonAbort404IsCaughtCorrectly() throws {
141+
let response = try app.getResponse(to: "/404")
156142
XCTAssertEqual(response.status, .notFound)
157143
XCTAssertEqual(viewRenderer.leafPath, "404")
158144
}
159145

160-
func testThatFuture403IsCaughtCorrectly() throws {
161-
let response = try app.getResponse(to: "/future403")
146+
func testThat403IsCaughtCorrectly() throws {
147+
let response = try app.getResponse(to: "/403")
162148
XCTAssertEqual(response.status, .forbidden)
163149
XCTAssertEqual(viewRenderer.leafPath, "serverError")
164150
}
165151

166-
func testThatRedirectIsNotCaught() throws {
167-
let response = try app.getResponse(to: "/future303")
168-
XCTAssertEqual(response.status, .seeOther)
169-
XCTAssertEqual(response.headers[.location].first, "ok")
170-
}
171-
172152
func testContextGeneratedOn404Page() throws {
173153
let response = try app.getResponse(to: "/404")
174154
XCTAssertEqual(response.status, .notFound)
@@ -189,20 +169,19 @@ class CustomGeneratorTests: XCTestCase {
189169
app.shutdown()
190170
app = Application(.testing, .shared(eventLoopGroup))
191171
app.views.use { _ in
192-
return self.viewRenderer
172+
self.viewRenderer
193173
}
194-
let leafErrorMiddleware = LeafErrorMiddleware() { status, error, req -> EventLoopFuture<AContext> in
195-
return req.eventLoop.future(error: Abort(.internalServerError))
196-
174+
let leafErrorMiddleware = LeafErrorMiddleware { _, _, _ -> AContext in
175+
throw Abort(.internalServerError)
197176
}
198177
app.middleware = .init()
199178
app.middleware.use(leafErrorMiddleware)
200179

201-
app.get("404") { req -> EventLoopFuture<Response> in
202-
req.eventLoop.makeFailedFuture(Abort(.notFound))
180+
app.get("404") { _ async throws -> Response in
181+
throw Abort(.notFound)
203182
}
204-
app.get("500") { req -> EventLoopFuture<Response> in
205-
req.eventLoop.makeFailedFuture(Abort(.internalServerError))
183+
app.get("500") { _ async throws -> Response in
184+
throw Abort(.internalServerError)
206185
}
207186

208187
let response404 = try app.getResponse(to: "404")
@@ -213,6 +192,4 @@ class CustomGeneratorTests: XCTestCase {
213192
XCTAssertEqual(response500.status, .internalServerError)
214193
XCTAssertNil(viewRenderer.leafPath)
215194
}
216-
217-
218195
}

0 commit comments

Comments
 (0)