Skip to content
This repository has been archived by the owner on Apr 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #12 from nodes-vapor/feature/use-new-error-protocol
Browse files Browse the repository at this point in the history
Feature/use new error protocol
  • Loading branch information
Casperhr authored Jan 6, 2017
2 parents 590597b + 991b058 commit 17f7287
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 60 deletions.
52 changes: 27 additions & 25 deletions Sources/Bugsnag/BugsnagMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,44 @@ import Vapor
import HTTP

public final class BugsnagMiddleware: Middleware {

let drop: Droplet
let configuration: Configuration
let connectionManager: ConnectionMananger
let configuration: ConfigurationType
let connectionManager: ConnectionManagerType

public init(drop: Droplet) throws {
self.drop = drop
self.configuration = try Configuration(drop: drop)
self.connectionManager = ConnectionMananger(drop: drop, config: configuration)
}

internal init(connectionManager: ConnectionManagerType) {
self.drop = connectionManager.drop
self.configuration = connectionManager.config
self.connectionManager = connectionManager
}


// MARK: - Middleware

public func respond(to request: Request, chainingTo next: Responder) throws -> Response {

do {
return try next.respond(to: request)
} catch Abort.badRequest {
try report(status: .badRequest, message: "Bad request", request: request)
throw Abort.badRequest
} catch Abort.serverError {
try report(status: .internalServerError, message: "Server error", request: request)
throw Abort.serverError
} catch Abort.notFound {
try report(status: .notFound, message: "Not found", request: request)
throw Abort.notFound
} catch Abort.custom(let status, let message) {
try report(status: status, message: message, request: request)
throw Abort.custom(status: status, message: message)
} catch let error as AbortError {
if error.metadata?["report"]?.bool ?? true {
try report(status: error.status, message: error.message, metadata: error.metadata, request: request)
}
throw error
} catch {
try report(status: .internalServerError, message: Status.internalServerError.reasonPhrase, metadata: nil, request: request)
throw error
}
// This can be incommented when Vapor updates
/*catch Abort.customWithCode(let status, let message, let code) {
try report(status: status, message: message, request: request)
throw Abort.customWithCode(status: status, message: message, code: code)
}*/
}

public func report(status: Status, message: String, request: Request) throws {
_ = try connectionManager.post(status: status, message: message, request: request)


// MARK: - Private

private func report(status: Status, message: String, metadata: Node?, request: Request) throws {
_ = try connectionManager.post(status: status, message: message, metadata: metadata, request: request)
}
}

11 changes: 10 additions & 1 deletion Sources/Bugsnag/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@ import Foundation
import Vapor
import HTTP

public struct Configuration {
public protocol ConfigurationType {
var apiKey: String { get }
var notifyReleaseStages: [String] { get }
var endpoint: String { get }
var filters: [String] { get }

init(drop: Droplet) throws
}

public struct Configuration: ConfigurationType {

public enum Field: String {
case apiKey = "bugsnag.apiKey"
Expand Down
44 changes: 26 additions & 18 deletions Sources/Bugsnag/ConnectionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,41 @@ import Vapor
import HTTP
import Foundation

public final class ConnectionMananger {

public protocol ConnectionManagerType {
var drop: Droplet { get }
var config: ConfigurationType { get }
init(drop: Droplet, config: ConfigurationType)
func post(status: Status, message: String, metadata: Node?, request: Request) throws -> Status
}

public final class ConnectionMananger: ConnectionManagerType {

let drop: Droplet
let config: Configuration
public let drop: Droplet
public let config: ConfigurationType

public init(drop: Droplet, config: Configuration) {
public init(drop: Droplet, config: ConfigurationType) {
self.drop = drop
self.config = config
}

func headers() -> [HeaderKey: String] {

private func headers() -> [HeaderKey: String] {
let headers = [
HeaderKey("Content-Type"): "application/json",
]

return headers
}

func body(message: String, request: Request) throws -> JSON {
private func body(message: String, metadata: Node?, request: Request) throws -> JSON {
var code: [String: Node] = [:]

var index = 0
//FIXME: Temporary workaround for Linux breaking when calling Thread.
#if os(OSX)
for entry in Thread.callStackSymbols {
code[String(index)] = Node(entry)

index = index + 1
}
#endif
Expand All @@ -53,17 +60,18 @@ public final class ConnectionMananger {
for (key, value) in request.headers {
headers[key.key] = Node(value)
}


let customMetaData = metadata ?? Node([])
let metaData = Node([
"request": Node([
"method": Node(request.method.description),
"headers": Node(headers),
"params": request.parameters,
"url": Node(request.uri.path)
])
]),
"metaData": customMetaData
])



let event: Node = Node([
Node([
"payloadVersion": 2,
Expand All @@ -83,22 +91,22 @@ public final class ConnectionMananger {
return try JSON(node: [
"apiKey": self.config.apiKey,
"notifier": Node([
"name": "Bugsnag Vapor",
"version": "1.0.11",
"url": "https://github.com/nodes-vapor/bugsnag"
"name": "Bugsnag Vapor",
"version": "1.0.11",
"url": "https://github.com/nodes-vapor/bugsnag"
]),
"events": event,
])
}

func post(json: JSON) throws -> Status {
private func post(json: JSON) throws -> Status {
let response = try drop.client.post(self.config.endpoint, headers: headers(), body: json.makeBody())

return response.status
}

func post(status: Status, message: String, request: Request) throws -> Status {
return try post(json: body(message: message, request: request))
public func post(status: Status, message: String, metadata: Node? = nil, request: Request) throws -> Status {
return try post(json: body(message: message, metadata: metadata, request: request))
}

}
147 changes: 147 additions & 0 deletions Tests/BugsnagTests/BugsnagMiddlewareTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import XCTest
@testable import Vapor
@testable import Bugsnag
import HTTP

class BugsnagMiddlewareTests: XCTestCase {
private var connectionManager: ConnectionManagerMock!
private var middleware: BugsnagMiddleware!

static var allTests : [(String, (BugsnagMiddlewareTests) -> () throws -> Void)] {
return [
("testThatErrorsNotConformingToAbortErrorAreRethrown", testThatErrorsNotConformingToAbortErrorAreRethrown),
("testThatAbortErrorsAreRethrown", testThatAbortErrorsAreRethrown),
("testErrorNotConformingToAbortErrorWillBeReported", testErrorNotConformingToAbortErrorWillBeReported),
("testBadRequestAbortErrorWillBeReported", testBadRequestAbortErrorWillBeReported),
("testCustomErrorWillBeReported", testCustomErrorWillBeReported),
("testErrorNotReportedWhenExplicitlyToldNotTo", testErrorNotReportedWhenExplicitlyToldNotTo),
("testErrorReportedWhenExplicitlyToldSo", testErrorReportedWhenExplicitlyToldSo)
]
}

override func setUp() {
let drop = Droplet()
let config = ConfigurationMock()
self.connectionManager = ConnectionManagerMock(drop: drop, config: config)
self.middleware = BugsnagMiddleware(connectionManager: connectionManager)
}

override func tearDown() {
self.connectionManager = nil
self.middleware = nil
}


// MARK: Rethrowing.

func testThatErrorsNotConformingToAbortErrorAreRethrown() {
let next = ErrorResponderMock(error: MyCustomError())
let req = try? Request(method: .get, uri: "some-random-uri")

do {
_ = try middleware.respond(to: req!, chainingTo: next)
XCTFail("Error not conforming to AbortError wasn't rethrown.")
} catch {}
}

func testThatAbortErrorsAreRethrown() {
let next = ErrorResponderMock(error: Abort.badRequest)
let req = try? Request(method: .get, uri: "some-random-uri")

do {
_ = try middleware.respond(to: req!, chainingTo: next)
XCTFail("Error conforming to AbortError wasn't rethrown.")
} catch {}
}


// MARK: Automatic reporting.

func testErrorNotConformingToAbortErrorWillBeReported() {
let error = MyCustomError()
let next = ErrorResponderMock(error: error)
let req = try? Request(method: .get, uri: "some-random-uri")
_ = try? middleware.respond(to: req!, chainingTo: next)

XCTAssertEqual(connectionManager.lastPost!.status, .internalServerError)
XCTAssertEqual(connectionManager.lastPost!.message, Status.internalServerError.reasonPhrase)
XCTAssertEqual(connectionManager.lastPost!.metadata, nil)
XCTAssertEqual(connectionManager.lastPost!.request.uri.description, req!.uri.description)
}

func testBadRequestAbortErrorWillBeReported() {
let next = ErrorResponderMock(error: Abort.badRequest)
let req = try? Request(method: .get, uri: "some-random-uri")
_ = try? middleware.respond(to: req!, chainingTo: next)

XCTAssertEqual(connectionManager.lastPost!.status, .badRequest)
XCTAssertEqual(connectionManager.lastPost!.message, Abort.badRequest.message)
XCTAssertEqual(connectionManager.lastPost!.metadata, nil)
XCTAssertEqual(connectionManager.lastPost!.request.uri.description, req!.uri.description)
}

func testCustomErrorWillBeReported() {
let message = "My custom error"
let code = 1337
let status = Status.conflict
let metadata: Node? = Node([
"key1": "value1",
"key2": "value2"
])
let error = MyCustomAbortError(message: message, code: code, status: status, metadata: metadata)

let next = ErrorResponderMock(error: error)
let req = try? Request(method: .get, uri: "some-random-uri")
_ = try? middleware.respond(to: req!, chainingTo: next)

XCTAssertEqual(connectionManager.lastPost!.status, error.status)
XCTAssertEqual(connectionManager.lastPost!.message, error.message)
XCTAssertEqual(connectionManager.lastPost!.metadata, error.metadata)
XCTAssertEqual(connectionManager.lastPost!.request.uri.description, req!.uri.description)
}


// MARK: Manual reporting.

func testErrorNotReportedWhenExplicitlyToldNotTo() {
let error = MyCustomAbortError(
message: "",
code: 0,
status: .accepted,
metadata: Node(["report": false])
)

let next = ErrorResponderMock(error: error)
let req = try? Request(method: .get, uri: "some-random-uri")
_ = try? middleware.respond(to: req!, chainingTo: next)

XCTAssertNil(connectionManager.lastPost)
}

func testErrorReportedWhenExplicitlyToldSo() {
let error = MyCustomAbortError(
message: "",
code: 0,
status: .accepted,
metadata: Node(["report": true])
)

let next = ErrorResponderMock(error: error)
let req = try? Request(method: .get, uri: "some-random-uri")
_ = try? middleware.respond(to: req!, chainingTo: next)

XCTAssertNotNil(connectionManager.lastPost)
}
}


// MARK: - Misc.

private struct MyCustomError: Error {}

private struct MyCustomAbortError: AbortError {
let message: String
let code: Int
let status: Status
let metadata: Node?
}
15 changes: 0 additions & 15 deletions Tests/BugsnagTests/BugsnagTests.swift

This file was deleted.

12 changes: 12 additions & 0 deletions Tests/BugsnagTests/Mocks/ConfigurationMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Vapor
import Bugsnag

internal class ConfigurationMock: ConfigurationType {
let apiKey = ""
let notifyReleaseStages: [String] = []
let endpoint = ""
let filters: [String] = []

required init(drop: Droplet) throws {}
init() {}
}
19 changes: 19 additions & 0 deletions Tests/BugsnagTests/Mocks/ConnectionManagerMock.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import Vapor
import Bugsnag
import HTTP

internal class ConnectionManagerMock: ConnectionManagerType {
let drop: Droplet
let config: ConfigurationType
var lastPost: (status: Status, message: String, metadata: Node?, request: Request)? = nil

required init(drop: Droplet, config: ConfigurationType) {
self.drop = drop
self.config = config
}

func post(status: Status, message: String, metadata: Node?, request: Request) throws -> Status {
self.lastPost = (status, message, metadata, request)
return Status.accepted // Just some random status.
}
}
Loading

0 comments on commit 17f7287

Please sign in to comment.