Forked from nicklockwood/Pattern for Swift Errors.swift
Created
August 16, 2017 15:36
-
-
Save rlaguilar/7d8d5ec83c88d55cef6149de02eb99e9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Swift's untyped errors are a goddam PiTA. Here's the pattern I use to try to work around this. | |
// The goal is basically to try to guarantee that every throwing function in the app throws an | |
// ApplicationError instead of some unknown error type. We can't actually enforce this statically | |
// But by following this convention we can simplify error handling | |
enum ApplicationError: Error, CustomStringConvertible { | |
// These are application-specific errors that may need special treatment | |
case specificError1 | |
case specificError2(SomeType) | |
... | |
// These are generic cases for errors that don't need special treatment | |
case message(String) | |
case generic(Error) | |
// Always handy to be able to print your errors | |
var description: String { | |
switch self { | |
case .specificError1, .specificError2: | |
// Application-specific | |
case let message(message): | |
return message | |
case let generic(error): | |
if let error = error as? CustomStringConvertible { | |
return error.description | |
} | |
// Always returns something, but not always something useful | |
return (error as NSError).localizedDescription | |
} | |
} | |
// Convenience constructor to save writing `ApplicationError.message(...)` all the time | |
init(_ message: String) { | |
self = .message(message) | |
} | |
// Convenience constructor for converting any unknown error to an ApplicationError | |
// this is useful when receiving errors where we're not sure what type they are, | |
// which is more common that not given Swift's lack of Error type annotations | |
init(_ error: Error) { | |
if let error = error as? ApplicationError { | |
self = error | |
} else { | |
self = .generic(error) | |
} | |
} | |
// By wrapping a call with this function, you can convert any thrown error to an ApplicationError | |
// usage 1: `let result = try ApplicationError.wrap(someFunctionWithNoArguments)` | |
// usage 2: `let result = try ApplicationError.wrap { try someFunction(with: arguments) }` | |
static func wrap<T>(_ closure: () throws -> T) throws -> T { | |
do { | |
return try closure() | |
} catch { | |
throw self.init(error) | |
} | |
} | |
// Like `wrap` above, but instead of calling the function and wrapping the error immediately, | |
// this returns a new function that throws an ApplicationError instead of the original error | |
// usage: let appErrorFn = ApplicationError.wrap(someUntypedErrorFn) | |
static func wrap<T>(_ closure: @escaping () throws -> T) -> () throws -> T { | |
return { try wrap(closure) } | |
} | |
// This function is basically an alternative version of try? that logs (or performs some other | |
// application-specific action) instead of failing silently. This is useful if you need to call | |
// a throwing function inside a function that doesn't throw, such as a delegate method | |
static func attempt<T>(_ closure: () throws -> T) -> T? { | |
do { | |
return try closure() | |
} catch { | |
let error = ApplicationError(error) | |
print(error.description) // Could do something more sophisticated, like store error in a global | |
return nil | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment