Skip to content

Commit c4af258

Browse files
committed
Initial commit
0 parents  commit c4af258

File tree

94 files changed

+13849
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+13849
-0
lines changed

.gitignore

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Xcode
2+
#
3+
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4+
5+
## Build generated
6+
build/
7+
DerivedData/
8+
9+
## Various settings
10+
*.pbxuser
11+
!default.pbxuser
12+
*.mode1v3
13+
!default.mode1v3
14+
*.mode2v3
15+
!default.mode2v3
16+
*.perspectivev3
17+
!default.perspectivev3
18+
xcuserdata/
19+
20+
## Other
21+
*.moved-aside
22+
*.xccheckout
23+
*.xcscmblueprint
24+
25+
## Obj-C/Swift specific
26+
*.hmap
27+
*.ipa
28+
*.dSYM.zip
29+
*.dSYM
30+
31+
## Playgrounds
32+
timeline.xctimeline
33+
playground.xcworkspace
34+
35+
# Swift Package Manager
36+
#
37+
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
38+
# Packages/
39+
# Package.pins
40+
.build/
41+
42+
# CocoaPods
43+
#
44+
# We recommend against adding the Pods directory to your .gitignore. However
45+
# you should judge for yourself, the pros and cons are mentioned at:
46+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
47+
#
48+
# Pods/
49+
50+
# Carthage
51+
#
52+
# Add this line if you want to avoid checking in source code from Carthage dependencies.
53+
54+
Carthage/Checkouts
55+
56+
Carthage/Build
57+
58+
# fastlane
59+
#
60+
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
61+
# screenshots whenever they are needed.
62+
# For more information about the recommended setup visit:
63+
# https://docs.fastlane.tools/best-practices/source-control/#source-control
64+
65+
fastlane/report.xml
66+
fastlane/Preview.html
67+
fastlane/screenshots
68+
fastlane/test_output

.swift-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.0

Cartfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github "Alamofire/Alamofire" ~> 4.1

Cartfile.resolved

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github "Alamofire/Alamofire" "4.5.0"

ErrorHandler.podspec

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Pod::Spec.new do |s|
2+
s.name = "ErrorHandler"
3+
s.version = "0.8.0"
4+
s.summary = "Error handling library for Swift"
5+
s.description = <<-DESC
6+
> Elegant and flexible error handling for Swift
7+
8+
ErrorHandler enables expressing complex error handling logic with a few lines of code using a memorable fluent API.
9+
DESC
10+
s.homepage = "https://github.com/Workable/swift-error-handler"
11+
s.license = { :type => "MIT", :file => "LICENSE" }
12+
s.authors = { "Kostas Kremizas" => "kremizask@gmail.com",
13+
"Eleni Papanikolopoulou" => "eleni.papanikolopoulou@gmail.com" }
14+
s.ios.deployment_target = '8.0'
15+
s.osx.deployment_target = '10.10'
16+
s.watchos.deployment_target = '2.0'
17+
s.tvos.deployment_target = '9.0'
18+
s.source = { :git => "https://github.com/Workable/swift-error-handler.git", :tag => s.version.to_s }
19+
20+
s.default_subspec = "Core"
21+
22+
s.subspec "Core" do |ss|
23+
ss.source_files = "ErrorHandler/Classes/Core/**/*"
24+
ss.framework = "Foundation"
25+
end
26+
27+
s.subspec "Alamofire" do |ss|
28+
ss.source_files = "ErrorHandler/Classes/Alamofire/**/*"
29+
ss.dependency "Alamofire", "~> 4.1"
30+
ss.dependency "ErrorHandler/Core"
31+
end
32+
end

ErrorHandler/Assets/.gitkeep

Whitespace-only changes.

ErrorHandler/Classes/.gitkeep

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// ErrorHandler-AFExtensions.swift
3+
// Workable SA
4+
//
5+
// Created by Kostas Kremizas on {TODAY}.
6+
// Copyright © 2017 Workable SA. All rights reserved.
7+
//
8+
9+
import Foundation
10+
import Alamofire
11+
#if !COCOAPODS
12+
import ErrorHandler
13+
#endif
14+
15+
public class AFErrorStatusCodeMatcher: ErrorMatcher {
16+
17+
private let validRange: Range<Int>
18+
19+
public init(_ range: Range<Int>) {
20+
self.validRange = range
21+
}
22+
23+
public init(statusCode: Int) {
24+
self.validRange = statusCode..<statusCode + 1
25+
}
26+
27+
public func matches(_ error: Error) -> Bool {
28+
guard let error = error as? AFError else { return false }
29+
guard case .responseValidationFailed(reason: let validationFailureReason) = error else { return false }
30+
guard case .unacceptableStatusCode(code: let statusCode) = validationFailureReason else { return false }
31+
return validRange ~= statusCode
32+
}
33+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// ErrorHandler+AFExtensions.swift
3+
// ErrorHandler+AFExtensions
4+
//
5+
// Created by Kostas Kremizas on 30/08/2017.
6+
// Copyright © 2017 Workable SA. All rights reserved.
7+
//
8+
9+
import Foundation
10+
#if !COCOAPODS
11+
import ErrorHandler
12+
#endif
13+
14+
public extension ErrorHandler {
15+
16+
public func onAFError(withStatus statusCode: Int, do action: @escaping ErrorAction) -> ErrorHandler {
17+
let matcher = AFErrorStatusCodeMatcher(statusCode: statusCode)
18+
return self.on(matcher, do: action)
19+
}
20+
21+
public func onAFError(withStatus range: Range<Int>, do action: @escaping ErrorAction) -> ErrorHandler {
22+
let matcher = AFErrorStatusCodeMatcher(range)
23+
return self.on(matcher, do: action)
24+
}
25+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
//
2+
// ErrorHandlerClass.swift
3+
// workable
4+
//
5+
// Created by Kostas Kremizas on 09/05/16.
6+
// Copyright © 2016 Workable. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public enum MatchingPolicy {
12+
case continueMatching
13+
case stopMatching
14+
}
15+
16+
public typealias ErrorAction = (Error) -> MatchingPolicy
17+
18+
public typealias Tag = String
19+
20+
/**
21+
An ErrorHandler is responsible for handling an error by executing one or more actions, that are found to match the error.
22+
23+
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.
24+
25+
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:).`
26+
*/
27+
open class ErrorHandler {
28+
fileprivate var errorActions: [(ErrorMatcher, ErrorAction)] = []
29+
fileprivate var onNoMatch: [ErrorAction] = []
30+
fileprivate var alwaysActions: [ErrorAction] = []
31+
32+
fileprivate var tagsDictionary = [Tag: [ErrorMatcher]]()
33+
34+
public init() {
35+
}
36+
37+
/**
38+
Defines an action to be executed if the error handled by the error handler metches the `matches` function.
39+
- Parameters:
40+
- matches: If this closure returns true, the `action` parameter will be executed.
41+
- 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.
42+
- Returns: The updated error handler (self).
43+
- 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.
44+
*/
45+
public func on(matches: @escaping ((Error) -> Bool), do action: @escaping ErrorAction) -> ErrorHandler {
46+
let matcher = ClosureErrorMatcher(matches: matches)
47+
return on(matcher, do: action)
48+
}
49+
50+
/**
51+
Defines an action to be executed if the `matcher` matches the error handled by the error handler.
52+
- Parameters:
53+
- matcher: The matcher that defines if the `action` closure will be called (if it's `matches` function returns true).
54+
- 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.
55+
- Returns: The updated error handler (self).
56+
- 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.
57+
*/
58+
public func on(_ matcher: ErrorMatcher, do action: @escaping ErrorAction) -> ErrorHandler {
59+
errorActions.append((matcher, action))
60+
return self
61+
}
62+
63+
/**
64+
Adds an action to be executed if there is no match for the error being handled.
65+
- Parameters:
66+
- 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.
67+
- Returns: The updated error handler (self).
68+
- 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.
69+
*/
70+
public func onNoMatch(do action: @escaping ErrorAction) -> ErrorHandler {
71+
onNoMatch.append(action)
72+
return self
73+
}
74+
75+
/**
76+
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.
77+
- Parameters:
78+
- 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.
79+
- Returns: The updated error handler (self).
80+
- Note: You can add multiple `always` actions and they will be executed in lifo order until one of them returns `MatchingPolicy.stopMatching`.
81+
*/
82+
public func always(do action: @escaping ErrorAction) -> ErrorHandler {
83+
alwaysActions.append(action)
84+
return self
85+
}
86+
87+
/**
88+
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.
89+
- Parameter error: The error to handle.
90+
*/
91+
public func handle(_ error: Error) {
92+
93+
defer {
94+
for alwaysAction in alwaysActions.reversed() {
95+
let alwaysMatchingPolicy = alwaysAction(error)
96+
if case .stopMatching = alwaysMatchingPolicy {
97+
break
98+
}
99+
}
100+
}
101+
102+
var foundMatch = false
103+
for (matcher, action) in errorActions.reversed() {
104+
if matcher.matches(error) {
105+
foundMatch = true
106+
let policy = action(error)
107+
if case .stopMatching = policy {
108+
return
109+
}
110+
}
111+
}
112+
113+
if foundMatch { return }
114+
115+
for otherwise in onNoMatch.reversed() {
116+
let policy = otherwise(error)
117+
if case .stopMatching = policy {
118+
break
119+
}
120+
}
121+
}
122+
123+
/**
124+
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.
125+
126+
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.
127+
128+
````
129+
ErrorHandler()
130+
.tag(matches: {...}, with: "NetworkError")
131+
.tag(matches: {...}, with: "NetworkError")
132+
.on(tag: "NetworkError", do: {...})
133+
````
134+
An alternative would be to create a new `matcher` or matches function that matches if all the other `matchers` match and use this.
135+
136+
- Parameters:
137+
- matches: the closure that will be given a tag.
138+
- tag: A `String` with which the `matches` closure (and any other tagged the same way) can referred with in the `on(tag:do:)` method.
139+
- Returns: The updated error handler (self).
140+
*/
141+
public func tag(matches: @escaping ((Error) -> Bool), with tag: Tag) -> ErrorHandler {
142+
let matcher = ClosureErrorMatcher(matches: matches)
143+
return self.tag(matcher, with: tag)
144+
}
145+
146+
/**
147+
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.
148+
149+
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.
150+
151+
````
152+
ErrorHandler()
153+
.tag(connectionFailedMatcher, with: "NetworkError")
154+
.tag(timeoutErrorMatcher, with: "NetworkError")
155+
.on(tag: "NetworkError", do: {...})
156+
````
157+
An alternative would be to create a new `matcher` or matches function that matches if all the other `matchers` match and use this.
158+
159+
- Parameters:
160+
- matcher: The closure that will be given a tag.
161+
- tag: A `String` with which the `matches` closure (and any other tagged the same way) can referred with in the `on(tag:do:)` method.
162+
- Returns: The updated error handler (self).
163+
*/
164+
public func tag(_ matcher: ErrorMatcher, with tag: Tag) -> ErrorHandler {
165+
if tagsDictionary[tag] != nil {
166+
tagsDictionary[tag]?.append(matcher)
167+
} else {
168+
tagsDictionary[tag] = [matcher]
169+
}
170+
return self
171+
}
172+
173+
/**
174+
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.
175+
- Returns: The updated error handler (self).
176+
*/
177+
public func on(tag: Tag, do action: @escaping ErrorAction) -> ErrorHandler {
178+
guard let taggedMatchers = tagsDictionary[tag] else { return self }
179+
let matherActionsPairs = taggedMatchers.map({ ($0, action) })
180+
errorActions.append(contentsOf: matherActionsPairs)
181+
return self
182+
}
183+
184+
/**
185+
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.
186+
187+
- Parameters:
188+
- type: The type the error must be in order for the `action` to be called.
189+
- action: The closure that will be executed if the error is of type `T`.
190+
- Returns: The updated error handler (self).
191+
*/
192+
public func onError<T: Error>(ofType type: T.Type, do action: @escaping ErrorAction) -> ErrorHandler {
193+
return on(ErrorTypeMatcher<T>(), do: action)
194+
}
195+
196+
/**
197+
Defines an action to be executed if the error handled by the error handler is equal to the given `Equatable` error.
198+
199+
- Parameters:
200+
- error: An `Equatable` `Error` we want to handle with the `action` closure.
201+
- action: The closure that will be called if the error being handled is equal to the given error (1st parameter).
202+
- Returns: The updated error handler (self).
203+
*/
204+
public func on<E: Error & Equatable>(_ error: E, do action: @escaping ErrorAction) -> ErrorHandler {
205+
return self.on(
206+
matches: { (handledError) -> Bool in
207+
guard let handledError = handledError as? E else { return false }
208+
return handledError == error
209+
}, do: action)
210+
}
211+
212+
public func onNSError(domain: String, code: Int? = nil, do action: @escaping ErrorAction) -> ErrorHandler {
213+
return self.on(NSErrorMatcher(domain: domain, code: code), do: action)
214+
}
215+
}
216+
217+
public func tryWith(_ handler: ErrorHandler, closure: () throws -> Void) {
218+
do {
219+
try closure()
220+
} catch {
221+
handler.handle(error)
222+
}
223+
}
224+
225+

0 commit comments

Comments
 (0)