Skip to content

Commit

Permalink
Merge pull request #3 from stefanrenne/feature/refactored-matcher
Browse files Browse the repository at this point in the history
Feature/refactored matcher
  • Loading branch information
Stefan Renne authored Dec 30, 2019
2 parents 32f3700 + 380328d commit 388dd3b
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 157 deletions.
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,29 @@ let user: User? = result.get(onError: errorHandler)

**Match on specific error type**

`.on(error: .type(NetworkError.authenticate), then: .doNothing)`
`errorHandler.on(error: .type(NetworkError.authenticate), then: .doNothing)`

#### Match on NSError code

`.on(error: .code(404), then: .doNothing)`
``errorHandler.on(error: .code(404), then: .doNothing)`

#### Match on NSError domain

`.on(error: .domain("remote"), then: .doNothing)`
`errorHandler.on(error: .domain("remote"), then: .doNothing)`

#### Custom matching

```
.on(error: .match({ error in
...
return true
}), then: .doNothing)
extension ErrorMatcher {
static func onCustomMatch() -> ErrorMatcher {
.init(matcher: { error in
...
return true
})
}
}
.on(error: .onCustomMatch()), then: .doNothing)
```

### Error Handling
Expand All @@ -172,14 +178,14 @@ let user: User? = result.get(onError: errorHandler)

It mainly exists to make documentation & unit tests easier to understand.

`.on(error: .code(404), then: .doNothing)`
`errorHandler.on(error: .code(404), then: .doNothing)`


#### Present Alert

The Alert is presented on the View provided in the ErrorHandler init

`.on(error: .code(404), then: .present(alert: ErrorAlert))`
`errorHandler.on(error: .code(404), then: .present(alert: ErrorAlert))`

By default there are two alert types you can present:

Expand All @@ -197,7 +203,7 @@ Would you like to use different alerts?

The only limitation is your mind.

`.on(error: .code(404), then: .perform(action: CustomActionHandler)`
`errorHandler.on(error: .code(404), then: .perform(action: CustomActionHandler)`

The **CustomActionHandler** provides the `Error` and an optional `onCompleted` completionblock that needs to be executed when your custom action has been performed.

Expand Down
6 changes: 4 additions & 2 deletions Sources/SwiftErrorHandler/Manager/ErrorHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ open class ErrorHandler {
self.view = errorHandlerView
}

private var specificErrorActions = [(ErrorMatcher, ActionHandler)]()
private var specificErrorActions = [(matcher: ErrorMatcher, action: ActionHandler)]()
private var alwaysActions = [ActionHandler]()
private var defaultActions = [ActionHandler]()

Expand Down Expand Up @@ -65,7 +65,9 @@ open class ErrorHandler {
public func handle(error: Error, onCompleted: OnErrorHandled) -> Bool {

// Check if we have a handler for this error:
let specificErrorHandlers: [ActionHandler] = specificErrorActions.actions(for: error)
let specificErrorHandlers: [ActionHandler] = specificErrorActions
.filter({ $0.matcher.catches(error) })
.map({ $0.action })

let actions: [ActionHandler]
if specificErrorHandlers.count > 0 {
Expand Down
49 changes: 28 additions & 21 deletions Sources/SwiftErrorHandler/Matchers/ErrorMatcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,35 @@

import Foundation

public enum ErrorMatcher {
case type(Error)
case code(Int)
case domain(String)
case match((Error) -> Bool)
public struct ErrorMatcher {

private let matcher: ((Error) -> Bool)

public init(matcher: @escaping ((Error) -> Bool)) {
self.matcher = matcher
}

public func catches(_ error: Error) -> Bool {
return matcher(error)
}
}

extension Array where Element == (ErrorMatcher, ActionHandler) {
func actions(for error: Error) -> [ActionHandler] {
return compactMap { (onError, action) -> ActionHandler? in
switch onError {
case .code(let code) where code == error._code:
return action
case .domain(let domain) where domain == error._domain:
return action
case .type(let matcher) where matcher.reflectedString == error.reflectedString:
return action
case .match(let matcher) where matcher(error):
return action
default:
return nil
}
}
public extension ErrorMatcher {

static func type(_ error: Error) -> ErrorMatcher {
return .init(matcher: { $0.reflectedString == error.reflectedString })
}

static func code(_ code: Int) -> ErrorMatcher {
return .init(matcher: { $0._code == code })
}

static func domain(_ domain: String) -> ErrorMatcher {
return .init(matcher: { $0._domain == domain })
}

@available(*, deprecated, message: "Create an extension on ErrorMatcher instead")
static func match(_ matcher: @escaping ((Error) -> Bool)) -> ErrorMatcher {
return .init(matcher: matcher)
}
}
3 changes: 1 addition & 2 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@ import XCTest

import SwiftErrorHandlerTests

var tests = [XCTestCaseEntry]()
tests += SwiftErrorHandlerTests.allTests()
let tests: [XCTestCaseEntry] = SwiftErrorHandlerTests.allTests()
XCTMain(tests)
4 changes: 2 additions & 2 deletions Tests/SwiftErrorHandlerTests/ActionHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ class ActionHandlerTests: XCTestCase {

}

extension ActionHandlerTests {
private enum SimpleError: Error {
private extension ActionHandlerTests {
enum SimpleError: Error {
case error1
}
}
Expand Down
6 changes: 3 additions & 3 deletions Tests/SwiftErrorHandlerTests/ErrorHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,14 @@ class ErrorHandlerTests: XCTestCase {

}

extension ErrorHandlerTests {
private enum HandlerError1: Error {
private extension ErrorHandlerTests {
enum HandlerError1: Error {
case error1
case error2
case error3
}

private enum HandlerError2: Error {
enum HandlerError2: Error {
case error4
}

Expand Down
140 changes: 27 additions & 113 deletions Tests/SwiftErrorHandlerTests/ErrorMatcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,144 +12,58 @@ import XCTest
class ErrorMatcherTests: XCTestCase {

func testItCanMatchError() throws {

let matchers: [(ErrorMatcher, ActionHandler)] = [
(ErrorMatcher.type(MatcherError1.error2), ActionHandler.perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.type(MatcherError2.error4), ActionHandler.perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.type(MatcherError1.error1), ActionHandler.perform(action: correctHandlerExecuted))
]

let matcher = ErrorMatcher.type(MatcherError1.error1)
let searchError = MatcherError1.error1

// Find the validator for this error
let actionHandlers = matchers.actions(for: searchError)
guard actionHandlers.count == 1,
let actionHandler = actionHandlers.first,
case ActionHandler.perform(action: let handler) = actionHandler else {
XCTFail("Couldn't find action handler for MatcherError1.error1")
return
}

// Validate if the correct handler is executed
handler(searchError, nil)
XCTAssertTrue(matcher.catches(searchError))
}

func testItCanMatchErrorCodes() throws {

let matchers: [(ErrorMatcher, ActionHandler)] = [
(ErrorMatcher.type(MatcherError1.error2), ActionHandler.perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.type(MatcherError2.error4), ActionHandler.perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.code(404), .perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.code(400), .perform(action: correctHandlerExecuted)),
(ErrorMatcher.type(MatcherError1.error1), ActionHandler.perform(action: unexpectedHandlerExecuted))
]

let matcher = ErrorMatcher.code(400)
let searchError = NSError(domain: "damain", code: 400, userInfo: ["data": "value"])

// Find the validator for this error
let actionHandlers = matchers.actions(for: searchError)
guard actionHandlers.count == 1,
let actionHandler = actionHandlers.first,
case ActionHandler.perform(action: let handler) = actionHandler else {
XCTFail("Couldn't find action handler for error code 400")
return
}

// Validate if the correct handler is executed
handler(searchError, nil)

XCTAssertTrue(matcher.catches(searchError))
}

func testItCanMatchErrorDomains() throws {

let matchers: [(ErrorMatcher, ActionHandler)] = [
(ErrorMatcher.type(MatcherError1.error2), ActionHandler.perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.type(MatcherError2.error4), ActionHandler.perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.domain("remote"), .perform(action: correctHandlerExecuted)),
(ErrorMatcher.code(400), .perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.type(MatcherError1.error1), ActionHandler.perform(action: unexpectedHandlerExecuted))
]

let matcher = ErrorMatcher.domain("remote")
let searchError = NSError(domain: "remote", code: 401, userInfo: ["data": "value"])

// Find the validator for this error
let actionHandlers = matchers.actions(for: searchError)
guard actionHandlers.count == 1,
let actionHandler = actionHandlers.first,
case ActionHandler.perform(action: let handler) = actionHandler else {
XCTFail("Couldn't find action handler for error code 400")
return
}

// Validate if the correct handler is executed
handler(searchError, nil)

XCTAssertTrue(matcher.catches(searchError))
}

func testItCanMatchWithBlock() throws {

let matchers: [(ErrorMatcher, ActionHandler)] = [
(ErrorMatcher.type(MatcherError1.error2), ActionHandler.perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.code(404), .perform(action: unexpectedHandlerExecuted)),
(ErrorMatcher.match({ error in
guard let testError = error as? MatcherError1, testError == .error3 else { return true }
return true
}), ActionHandler.perform(action: correctHandlerExecuted))
]

let matcher: ErrorMatcher = .isError3
let searchError = MatcherError1.error3

// Find the validator for this error
let actionHandlers = matchers.actions(for: searchError)
guard actionHandlers.count == 1,
let actionHandler = actionHandlers.first,
case ActionHandler.perform(action: let handler) = actionHandler else {
XCTFail("Couldn't find action handler for MatcherError1.error3")
return
}

// Validate if the correct handler is executed
handler(searchError, nil)

XCTAssertTrue(matcher.catches(searchError))
}

func testItCanMatchCompleteErrorSuites() throws {

let matchers: [(ErrorMatcher, ActionHandler)] = [
(ErrorMatcher.match({ $0 is MatcherError2 }), ActionHandler.perform(action: correctHandlerExecuted))
]

let matcher: ErrorMatcher = .isError2
let searchError = MatcherError2.error4

// Find the validator for this error
let actionHandlers = matchers.actions(for: searchError)
guard actionHandlers.count == 1,
let actionHandler = actionHandlers.first,
case ActionHandler.perform(action: let handler) = actionHandler else {
XCTFail("Couldn't find action handler for MatcherError2")
return
}

// Validate if the correct handler is executed
handler(searchError, nil)
XCTAssertTrue(matcher.catches(searchError))
}
}

extension ErrorMatcherTests {

func correctHandlerExecuted(for error: Error, onCompleted: OnErrorHandled) { }

func unexpectedHandlerExecuted(for error: Error, onCompleted: OnErrorHandled) {
XCTFail("Unexpected Handler Executed")
private extension ErrorMatcher {
static var isError2: ErrorMatcher {
return .init(matcher: { $0 is ErrorMatcherTests.MatcherError2 })
}

static var isError3: ErrorMatcher {
return .init(matcher: { error in
guard let testError = error as? ErrorMatcherTests.MatcherError1, testError == .error3 else { return true }
return true
})
}
}

fileprivate extension ErrorMatcherTests {

private enum MatcherError1: Error {
enum MatcherError1: Error {
case error1
case error2
case error3
}

private enum MatcherError2: Error {
enum MatcherError2: Error {
case error4
}
}
Expand Down
7 changes: 3 additions & 4 deletions Tests/SwiftErrorHandlerTests/ResultErrorHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,16 @@ class ResultErrorHandlerTests: XCTestCase {
}
}

extension ResultErrorHandlerTests {
private enum HandlerError1: Error {
private extension ResultErrorHandlerTests {
enum HandlerError1: Error {
case error1
case error2
case error3
}

private enum HandlerError2: Error {
enum HandlerError2: Error {
case error4
}

}

extension ResultErrorHandlerTests {
Expand Down

0 comments on commit 388dd3b

Please sign in to comment.