Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kremizask committed Sep 6, 2017
0 parents commit d7c3992
Show file tree
Hide file tree
Showing 94 changed files with 13,850 additions and 0 deletions.
68 changes: 68 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
.build/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.

Carthage/Checkouts

Carthage/Build

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
1 change: 1 addition & 0 deletions .swift-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0
1 change: 1 addition & 0 deletions Cartfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github "Alamofire/Alamofire" ~> 4.1
1 change: 1 addition & 0 deletions Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github "Alamofire/Alamofire" "4.5.0"
33 changes: 33 additions & 0 deletions ErrorHandler.podspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Pod::Spec.new do |s|
s.name = "ErrorHandler"
s.version = "0.8.0"
s.summary = "Error handling library for Swift"
s.description = <<-DESC
> Elegant and flexible error handling for Swift
ErrorHandler enables expressing complex error handling logic with a few lines of code using a memorable fluent API.
DESC
s.homepage = "https://github.com/Workable/swift-error-handler"
s.license = { :type => "MIT", :file => "LICENSE" }
s.authors = { "Kostas Kremizas" => "kremizask@gmail.com",
"Eleni Papanikolopoulou" => "eleni.papanikolopoulou@gmail.com" }
s.social_media_url = ""
s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.10'
s.watchos.deployment_target = '2.0'
s.tvos.deployment_target = '9.0'
s.source = { :git => "https://github.com/Workable/swift-error-handler.git", :tag => s.version.to_s }

s.default_subspec = "Core"

s.subspec "Core" do |ss|
ss.source_files = "ErrorHandler/Classes/Core/**/*"
ss.framework = "Foundation"
end

s.subspec "Alamofire" do |ss|
ss.source_files = "ErrorHandler/Classes/Alamofire/**/*"
ss.dependency "Alamofire", "~> 4.1"
ss.dependency "ErrorHandler/Core"
end
end
Empty file added ErrorHandler/Assets/.gitkeep
Empty file.
Empty file added ErrorHandler/Classes/.gitkeep
Empty file.
33 changes: 33 additions & 0 deletions ErrorHandler/Classes/Alamofire/AFErrorStatusCodeMatcher.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// ErrorHandler-AFExtensions.swift
// Workable SA
//
// Created by Kostas Kremizas on {TODAY}.
// Copyright © 2017 Workable SA. All rights reserved.
//

import Foundation
import Alamofire
#if !COCOAPODS
import ErrorHandler
#endif

public class AFErrorStatusCodeMatcher: ErrorMatcher {

private let validRange: Range<Int>

public init(_ range: Range<Int>) {
self.validRange = range
}

public init(statusCode: Int) {
self.validRange = statusCode..<statusCode + 1
}

public func matches(_ error: Error) -> Bool {
guard let error = error as? AFError else { return false }
guard case .responseValidationFailed(reason: let validationFailureReason) = error else { return false }
guard case .unacceptableStatusCode(code: let statusCode) = validationFailureReason else { return false }
return validRange ~= statusCode
}
}
25 changes: 25 additions & 0 deletions ErrorHandler/Classes/Alamofire/ErrorHandler+AFExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// ErrorHandler+AFExtensions.swift
// ErrorHandler+AFExtensions
//
// Created by Kostas Kremizas on 30/08/2017.
// Copyright © 2017 Workable SA. All rights reserved.
//

import Foundation
#if !COCOAPODS
import ErrorHandler
#endif

public extension ErrorHandler {

public func onAFError(withStatus statusCode: Int, do action: @escaping ErrorAction) -> ErrorHandler {
let matcher = AFErrorStatusCodeMatcher(statusCode: statusCode)
return self.on(matcher, do: action)
}

public func onAFError(withStatus range: Range<Int>, do action: @escaping ErrorAction) -> ErrorHandler {
let matcher = AFErrorStatusCodeMatcher(range)
return self.on(matcher, do: action)
}
}
225 changes: 225 additions & 0 deletions ErrorHandler/Classes/Core/ErrorHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
//
// ErrorHandlerClass.swift
// workable
//
// Created by Kostas Kremizas on 09/05/16.
// Copyright © 2016 Workable. All rights reserved.
//

import Foundation

public enum MatchingPolicy {
case continueMatching
case stopMatching
}

public typealias ErrorAction = (Error) -> MatchingPolicy

public typealias Tag = String

/**
An ErrorHandler is responsible for handling an error by executing one or more actions, that are found to match the error.
You can add the rules for matching and the actions that will be executed when a match occurs by using the `on(_:do)` method variants.
You can add actions to be executed when there is no match with the onNoMatch(do:) method and actions that will be executed regardless of whether there is a match or not with `always(do:).`
*/
open class ErrorHandler {
fileprivate var errorActions: [(ErrorMatcher, ErrorAction)] = []
fileprivate var onNoMatch: [ErrorAction] = []
fileprivate var alwaysActions: [ErrorAction] = []

fileprivate var tagsDictionary = [Tag: [ErrorMatcher]]()

public init() {
}

/**
Defines an action to be executed if the error handled by the error handler metches the `matches` function.
- Parameters:
- matches: If this closure returns true, the `action` parameter will be executed.
- action: The closure that will be executed if `matches` returns true. The action returns a `MatchingPolicy` instance. If the action is called and returns `MatchingPolicy.continueMatching`, the error handle continues to execute the actions corresponding to other potential matches for the handled error. If the action is called and returns `MatchingPolicy.stopMatching` no other matching action is executed, except actions defined with the `always(do:)` method.
- Returns: The updated error handler (self).
- Note: When the `handle` method is called the matching functions are called in lifo order. First is checked the `matches` function given by the last `on(matches:do:)` call. The reasoning behind this is that the last `on(matches:do:)` call is the last customization made to the handler by the developer and as such, it's action should have priority if there are multiple matches.
*/
public func on(matches: @escaping ((Error) -> Bool), do action: @escaping ErrorAction) -> ErrorHandler {
let matcher = ClosureErrorMatcher(matches: matches)
return on(matcher, do: action)
}

/**
Defines an action to be executed if the `matcher` matches the error handled by the error handler.
- Parameters:
- matcher: The matcher that defines if the `action` closure will be called (if it's `matches` function returns true).
- action: The closure that will be executed if the `matcher` matches the error. The action returns a `MatchingPolicy` instance. If the action is called and returns `MatchingPolicy.continueMatching`, the error handle continues to execute the actions corresponding to other potential matches for the handled error. If the action is called and returns `MatchingPolicy.stopMatching` no other matching action is executed, except actions defined with the `always(do:)` method.
- Returns: The updated error handler (self).
- Note: When the `handle` method is called the matching functions are called in lifo order. First is checked the `matches` function given by the last `on(_:do:)` call. The reasoning behind this is that the last `on(matches:do:)` call is the last customization made to the handler by the developer and as such, it's action should have priority if there are multiple matches.
*/
public func on(_ matcher: ErrorMatcher, do action: @escaping ErrorAction) -> ErrorHandler {
errorActions.append((matcher, action))
return self
}

/**
Adds an action to be executed if there is no match for the error being handled.
- Parameters:
- action: The closure that will be executed if there is no match. The action returns a `MatchingPolicy` instance. If the action is called and returns `MatchingPolicy.continueMatching`, the error handle continues to execute the previously added `onNoMatch` actions. If the action is called and returns `MatchingPolicy.stopMatching` no other `onNoMatch` action is executed. This way you can override previously defined `onNoMatch` actions.
- Returns: The updated error handler (self).
- Note: You can add multiple `onNoMatch` actions and they will be executed in lifo order until one of them returns MatchingPolicy.stopMatching. The reasoning behind this is that the last `onNoMatch` call is the last customization made to the handler's `onNoMatch` actions and as such, it should have priority.
*/
public func onNoMatch(do action: @escaping ErrorAction) -> ErrorHandler {
onNoMatch.append(action)
return self
}

/**
Adds an action to be executed whether there is a match for the error or not. This action will be called after potential matching actions have been called.
- Parameters:
- action: The closure that will be executed if there is no match. The action returns a `MatchingPolicy` instance. If the action is called and returns `MatchingPolicy.continueMatching`, the error handle continues to execute the previously added `always` actions. If the action is called and returns `MatchingPolicy.stopMatching` no other `always` action is executed. This way you can override previously defined `always` actions.
- Returns: The updated error handler (self).
- Note: You can add multiple `always` actions and they will be executed in lifo order until one of them returns `MatchingPolicy.stopMatching`.
*/
public func always(do action: @escaping ErrorAction) -> ErrorHandler {
alwaysActions.append(action)
return self
}

/**
Looks for actions that match the error (added with `on(matches:do:)`) and executes them. If there are no matching actions, `onNoMatch` actions will be executed. In any case, `always` actions will also be executed.
- Parameter error: The error to handle.
*/
public func handle(_ error: Error) {

defer {
for alwaysAction in alwaysActions.reversed() {
let alwaysMatchingPolicy = alwaysAction(error)
if case .stopMatching = alwaysMatchingPolicy {
break
}
}
}

var foundMatch = false
for (matcher, action) in errorActions.reversed() {
if matcher.matches(error) {
foundMatch = true
let policy = action(error)
if case .stopMatching = policy {
return
}
}
}

if foundMatch { return }

for otherwise in onNoMatch.reversed() {
let policy = otherwise(error)
if case .stopMatching = policy {
break
}
}
}

/**
Adds a tag (of type `String`) to a specific `matches` closure. After this you can use `on(tag:do:)` to link actions to all the `matches` closures that have been assigned this tag. This way you can group many `matches` closures together, refer to them by a memorizeable tag and handle the errors that match any of them by calling the same action.
For example you can tag with "NetworkError" all the `matches` closures that match an error related to the network and handle such errors the same way.
````
ErrorHandler()
.tag(matches: {...}, with: "NetworkError")
.tag(matches: {...}, with: "NetworkError")
.on(tag: "NetworkError", do: {...})
````
An alternative would be to create a new `matcher` or matches function that matches if all the other `matchers` match and use this.
- Parameters:
- matches: the closure that will be given a tag.
- tag: A `String` with which the `matches` closure (and any other tagged the same way) can referred with in the `on(tag:do:)` method.
- Returns: The updated error handler (self).
*/
public func tag(matches: @escaping ((Error) -> Bool), with tag: Tag) -> ErrorHandler {
let matcher = ClosureErrorMatcher(matches: matches)
return self.tag(matcher, with: tag)
}

/**
Adds a tag (of type `String`) to a specific `matcher`. After this you can use `on(tag:do:)` to link actions to all the `matchers` that have been assigned this tag. This way you can group many `matchers` closures together, refer to them by a memorizeable tag and handle the errors that match any of them by calling the same action.
For example you can tag with "NetworkError" all the `matchers` that match an error related to the network and handle such errors the same way.
````
ErrorHandler()
.tag(connectionFailedMatcher, with: "NetworkError")
.tag(timeoutErrorMatcher, with: "NetworkError")
.on(tag: "NetworkError", do: {...})
````
An alternative would be to create a new `matcher` or matches function that matches if all the other `matchers` match and use this.
- Parameters:
- matcher: The closure that will be given a tag.
- tag: A `String` with which the `matches` closure (and any other tagged the same way) can referred with in the `on(tag:do:)` method.
- Returns: The updated error handler (self).
*/
public func tag(_ matcher: ErrorMatcher, with tag: Tag) -> ErrorHandler {
if tagsDictionary[tag] != nil {
tagsDictionary[tag]?.append(matcher)
} else {
tagsDictionary[tag] = [matcher]
}
return self
}

/**
Adds an action to all the `matches` closures that have been assigned this tag. This way you can group many `matches` closures together, refer to them by a memorizeable tag and handle the errors that match any of them by calling the same action.
- Returns: The updated error handler (self).
*/
public func on(tag: Tag, do action: @escaping ErrorAction) -> ErrorHandler {
guard let taggedMatchers = tagsDictionary[tag] else { return self }
let matherActionsPairs = taggedMatchers.map({ ($0, action) })
errorActions.append(contentsOf: matherActionsPairs)
return self
}

/**
Defines an action to be executed if the error is any error of the given type `T`. In essence it is just a convenience method for calling `on(_:do:)` with a matcher that checks the type of the error.
- Parameters:
- type: The type the error must be in order for the `action` to be called.
- action: The closure that will be executed if the error is of type `T`.
- Returns: The updated error handler (self).
*/
public func onError<T: Error>(ofType type: T.Type, do action: @escaping ErrorAction) -> ErrorHandler {
return on(ErrorTypeMatcher<T>(), do: action)
}

/**
Defines an action to be executed if the error handled by the error handler is equal to the given `Equatable` error.
- Parameters:
- error: An `Equatable` `Error` we want to handle with the `action` closure.
- action: The closure that will be called if the error being handled is equal to the given error (1st parameter).
- Returns: The updated error handler (self).
*/
public func on<E: Error & Equatable>(_ error: E, do action: @escaping ErrorAction) -> ErrorHandler {
return self.on(
matches: { (handledError) -> Bool in
guard let handledError = handledError as? E else { return false }
return handledError == error
}, do: action)
}

public func onNSError(domain: String, code: Int? = nil, do action: @escaping ErrorAction) -> ErrorHandler {
return self.on(NSErrorMatcher(domain: domain, code: code), do: action)
}
}

public func tryWith(_ handler: ErrorHandler, closure: () throws -> Void) {
do {
try closure()
} catch {
handler.handle(error)
}
}


Loading

0 comments on commit d7c3992

Please sign in to comment.