Skip to content

Commit

Permalink
Merge pull request #18 from brokenhandsio/custom-context
Browse files Browse the repository at this point in the history
Allow a custom context to be used
  • Loading branch information
0xTim authored May 11, 2021
2 parents 0614e2a + a246d0e commit a2b5fca
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 51 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ on:
jobs:
xenial:
container:
image: vapor/swift:5.2-xenial
image: swift:5.2-xenial
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: swift test --enable-test-discovery --enable-code-coverage
bionic:
container:
image: vapor/swift:5.2-bionic
image: swift:5.4-bionic
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Run Bionic Tests
run: swift test --enable-test-discovery --enable-code-coverage
run: swift test --enable-code-coverage
- name: Setup container for codecov upload
run: apt-get update && apt-get install -y curl
- name: Process coverage file
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let package = Package(
.library(name: "LeafErrorMiddleware", targets: ["LeafErrorMiddleware"]),
],
dependencies: [
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
],
targets: [
.target(name: "LeafErrorMiddleware", dependencies: [
Expand Down
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,58 @@ First, add LeafErrorMiddleware as a dependency in your `Package.swift` file:
```swift
dependencies: [
// ...,
.package(name: "LeafErrorMiddleware", url: "https://github.com/brokenhandsio/leaf-error-middleware.git", from: "2.0.0")
.package(name: "LeafErrorMiddleware", url: "https://github.com/brokenhandsio/leaf-error-middleware.git", from: "3.0.0")
],
targets: [
.target(name: "App", dependencies: ["Vapor", ..., "LeafErrorMiddleware"]),
// ...
]
```

To use the LeafErrorMiddleware, register the middleware service in `configure.swift` to your `Application`'s middleware (make sure you `import LeafErrorMiddleware` at the top):
## Default Context

To use the LeafErrorMiddleware with the default context passed to templates, register the middleware service in `configure.swift` to your `Application`'s middleware (make sure you `import LeafErrorMiddleware` at the top):

```swift
app.middleware.use(LeafErrorMiddleware())
app.middleware.use(LeafErorrMiddlewareDefaultGenerator.build())
```

Make sure it appears before all other middleware to catch errors.

## Custom Context

Leaf Error Middleware allows you to pass a closure to `LeafErrorMiddleware` to generate a custom context for the error middleware. This is useful if you want to be able to tell if a user is logged in on a 404 page for instance.

Register the middleware as follows:

```swift
let leafMiddleware = LeafErrorMiddleware() { status, error, req -> EventLoopFuture<SomeContext> in
return req.eventLoop.future(SomeContext())
}
app.middleware.use(leafMiddleware)
```

The closure recevies three parameters:

* `HTTPStatus` - the status code of the response returned.
* `Error` - the error caught to be handled.
* `Request` - the request currently being handled. This can be used to log information, make external API calls or check the session.

# Setting Up

You need to include two [Leaf](https://github.com/vapor/leaf) templates in your application:

* `404.leaf`
* `serverError.leaf`

When Leaf Error Middleware catches a 404 error, it will return the `404.leaf` template. Any other error caught will return the `serverError.leaf` template. The `serverError.leaf` template will be passed up to three parameters in its context:
When Leaf Error Middleware catches a 404 error, it will return the `404.leaf` template. Any other error caught will return the `serverError.leaf` template.

## Default Context

If using the default context, the `serverError.leaf` template will be passed up to three parameters in its context:

* `status` - the status code of the error caught
* `statusMessage` - a reason for the status code
* `reason` - the reason for the error, if known. Otherwise this won't be passed in.

The `404.leaf` template will get a `reason` parameter in the context if one is known.
The `404.leaf` template will get a `reason` parameter in the context if one is known.
7 changes: 7 additions & 0 deletions Sources/LeafErrorMiddleware/DefaultContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Foundation

public struct DefaultContext: Encodable {
public let status: String?
public let statusMessage: String?
public let reason: String?
}
45 changes: 19 additions & 26 deletions Sources/LeafErrorMiddleware/LeafErrorMiddleware.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import Vapor

/// Captures all errors and transforms them into an internal server error.
public final class LeafErrorMiddleware: Middleware {

public init() {}
public final class LeafErrorMiddleware<T: Encodable>: Middleware {

let contextGenerator: ((HTTPStatus, Error, Request) -> EventLoopFuture<T>)

public init(contextGenerator: @escaping ((HTTPStatus, Error, Request) -> EventLoopFuture<T>)) {
self.contextGenerator = contextGenerator
}

/// See `Middleware.respond`
public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture<Response> {
Expand Down Expand Up @@ -35,37 +39,26 @@ public final class LeafErrorMiddleware: Middleware {

private func handleError(for req: Request, status: HTTPStatus, error: Error) -> EventLoopFuture<Response> {
if status == .notFound {
var parameters = [String:String]()
if let abortError = error as? AbortError {
parameters["reason"] = abortError.reason
}
return req.view.render("404", parameters).encodeResponse(for: req).map { res in
res.status = status
return res
return contextGenerator(status, error, req).flatMap { context in
return req.view.render("404", context).encodeResponse(for: req).map { res in
res.status = status
return res
}
}.flatMapError { newError in
return self.renderServerErrorPage(for: status, request: req, error: newError)
}
}

return renderServerErrorPage(for: status, request: req, error: error)
}

private func renderServerErrorPage(for status: HTTPStatus, request: Request, error: Error) -> EventLoopFuture<Response> {
var parameters: [String:String] = [
"status": status.code.description,
"statusMessage": status.reasonPhrase
]

if let abortError = error as? AbortError {
parameters["reason"] = abortError.reason
}

request.logger.error("Internal server error. Status: \(status.code) - path: \(request.url)")

return request.view.render("serverError", parameters).encodeResponse(for: request).map { res in
res.status = status
return res
}.flatMapError { error -> EventLoopFuture<Response> in
return contextGenerator(status, error, request).flatMap { context in
request.logger.error("Internal server error. Status: \(status.code) - path: \(request.url)")
return request.view.render("serverError", context).encodeResponse(for: request).map { res in
res.status = status
return res
}
}.flatMapError { error -> EventLoopFuture<Response> in
let body = "<h1>Internal Error</h1><p>There was an internal error. Please try again later.</p>"
request.logger.error("Failed to render custom error page - \(error)")
return body.encodeResponse(for: request).map { res in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Vapor

public enum LeafErorrMiddlewareDefaultGenerator {
static func generate(_ status: HTTPStatus, _ error: Error, _ req: Request) -> EventLoopFuture<DefaultContext> {
let reason: String?
if let abortError = error as? AbortError {
reason = abortError.reason
} else {
reason = nil
}
let context = DefaultContext(status: status.code.description, statusMessage: status.reasonPhrase, reason: reason)
return req.eventLoop.future(context )
}

public static func build() -> LeafErrorMiddleware<DefaultContext> {
LeafErrorMiddleware(contextGenerator: generate)
}
}
Loading

0 comments on commit a2b5fca

Please sign in to comment.