Skip to content

Instantly share code, notes, and snippets.

@jonhull
Created October 26, 2018 19:37
Show Gist options
  • Save jonhull/6166037b4dd8f5d684132c9dc0bcaf0a to your computer and use it in GitHub Desktop.
Save jonhull/6166037b4dd8f5d684132c9dc0bcaf0a to your computer and use it in GitHub Desktop.
This is a Result type which I would like to see picked up by the Swift Standard Library. It allows custom error types, but also has easy interop with native Swift Error Handling
import Foundation
///A Result type which indicates the success or failure of a task. Has a generic Value, and uses a Swift.Error for the failure case. Specialization of SpecificResult.
public typealias Result<T> = SpecificResult<T,Swift.Error>
///A Result type which indicates the success or failure of a task using an enum to hold the resulting (generic) value in the case of success or (generic) error in the case of failure.
public enum SpecificResult<Value,Error> {
///The task succeeded and returned this value
case success(Value)
///The task failed with this error
case error(Error)
///Initialize with the given value
/// - Parameters:
/// - value: The value to initialize the Result with
public init(_ value:Value) {
self = .success(value)
}
///Initialize with a given error
/// - Parameters:
/// - error: The error to initialize the Result with
public init(error:Error) {
self = .error(error)
}
///Initialize with a given value, or if that value is nil, initialize with an error instead
/// - Parameters:
/// - value: An optional value to initialize the Result with
/// - nilError: An autoclosure returning an Error. Called when value is nil.
public init(_ value:Value?, nilError:@autoclosure ()->Error) {
guard let value = value else {
self = .error(nilError())
return
}
self = .success(value)
}
///Returns the value of the Result, or nil if the Result has an Error
/// - Return: the result if available or nil
public var optionalValue:Value? {
switch self {
case .success(let value): return value
case .error(_): return nil
}
}
///Returns the error stored in the Result, or nil if the Result has no Error
/// - Returns: The error of the result if available or nil
public var optionalError:Error? {
switch self {
case .error(let e): return e
case .success(_): return nil
}
}
}
//MARK: - Mapping
extension SpecificResult {
///Maps the value of the Result to a new type. If the Result has an error, then the error is simply propogated.
/// - Parameter transform: A mapping closure. `transform` accepts a value of type Value, and returns a value of a different type.
/// - Returns: A SpecificResult containing the transformed value or the original error.
public func mapValue<U>(_ transform: (Value)->U) -> SpecificResult<U,Error> {
switch self {
case .success(let value): return SpecificResult<U,Error>(transform(value))
case .error(let e): return SpecificResult<U,Error>(error: e)
}
}
///Maps the value of the Result to a new Result and then flattens it. If the Result has an error, then the error is simply propogated.
/// - Parameter transform: A mapping closure. `transform` accepts a value of type Value, and returns a SpecificResult of a different type.
/// - Returns: A SpecificResult created from the transformed value or the original error.
public func flatMapValue<U>(_ transform: (Value)->SpecificResult<U,Error>) -> SpecificResult<U,Error> {
switch self {
case .success(let value): return transform(value)
case .error(let e): return SpecificResult<U,Error>(error: e)
}
}
///Maps the error of the Result to a new type. If the Result has an value, then the value is simply propogated.
/// - Parameter transform: A mapping closure. `transform` accepts an error of type Error, and returns an Error of a (potentially) different type.
/// - Returns: A SpecificResult containing the transformed error or the original value.
public func mapError<E>(_ transform: (Error)->E) -> SpecificResult<Value,E> {
switch self {
case .success(let value): return SpecificResult<Value,E>(value)
case .error(let e): return SpecificResult<Value,E>(error: transform(e))
}
}
///Maps the error of the Result to a new Result. If the Result has an value, then the value is simply propogated.
/// - Parameter transform: A mapping closure. `transform` accepts an error of type Error, and returns a SpecificResult.
/// - Returns: A SpecificResult containing the Result created by the transform or the original value.
public func flatMapError<E>(_ transform: (Error)->SpecificResult<Value,E>) -> SpecificResult<Value,E> {
switch self {
case .success(let value): return SpecificResult<Value,E>(value)
case .error(let e): return transform(e)
}
}
///Maps either the value or the error of the Result to a new type of value or error respectively.
/// - Parameters:
/// - transform: A mapping closure which takes a Value and transforms it into a value of a different type.
/// - errorTransform: A mapping closure which takes an Error and transforms it into an Error of a different type.
/// - Returns: A SpecificResult containing the transformed Value or Error.
public func bimap<U,E>(success transform: (Value)->U, error errorTransform: (Error)->E) -> SpecificResult<U,E> {
switch self {
case .success(let value): return SpecificResult<U,E>(transform(value))
case .error(let e): return SpecificResult<U,E>(error: errorTransform(e))
}
}
///Maps either the value or the error of the Result to a SpecificResult.
/// - Parameters:
/// - transform: A mapping closure which takes a Value and transforms it into a SpecificResult of a different type.
/// - errorTransform: A mapping closure which takes an Error and transforms it into a SpecificResult of a different type.
/// - Returns: A SpecificResult containing the transformed Value or Error.
public func flatBimap<U,E>(success transform: (Value)->SpecificResult<U,E>, error errorTransform: (Error)->SpecificResult<U,E>) -> SpecificResult<U,E> {
switch self {
case .success(let value): return transform(value)
case .error(let e): return errorTransform(e)
}
}
}
//MARK: - Convienience Handling
extension Result {
///Returns true if the Result contains a value
public var hasValue:Bool {
switch self {
case .success(_): return true
default: return false
}
}
///Returns true if the Result contains an error
public var hasError:Bool {
switch self {
case .error(_): return true
default: return false
}
}
///Returns true if the Result contains an Error of the given type
/// - Parameter ofType: The type of error to check for.
public func hasError<E>(ofType type:E) -> Bool {
switch self {
case .error(let error) where error is E: return true
default: return false
}
}
///Calls a handler closure only in the case where the Result contains a value. Returns itself to allow chaining.
/// - Parameter handler: A closure to run when Result contains a value
@discardableResult
public func onSuccess(_ handler: (Value)->()) -> SpecificResult<Value,Error> {
switch self {
case .success(let value): handler(value)
case .error(_): break;
}
return self
}
///Calls a handler closure only in the case where the Result contains an error. Returns itself to allow chaining.
/// - Parameter handler: A closure to run when Result contains an error
@discardableResult
public func onError(_ handler: (Error)->()) -> SpecificResult<Value,Error> {
switch self {
case .error(let error): handler(error)
case .success(_): break;
}
return self
}
///Calls a handler closure only in the case where the Result contains an error of the given type. Returns itself to allow chaining.
/// - Parameter ofType: The type of error to check for.
/// - Parameter handler: A closure to run when Result contains an error of the given type.
@discardableResult
public func onError<E>(ofType type: E, handler: (E)->()) -> SpecificResult<Value,Error> {
switch self {
case .error(let error as E): handler(error)
case .error(_): break; //Error of another type
case .success(_): break;
}
return self
}
}
//MARK: - Recovery
extension SpecificResult {
///Returns the value of the Result, or if there is an error, recovers an acceptable value using the provided closure.
/// - Parameter recoveringError: A closure which takes an error and returns a Value
/// - Returns: A Value.
public func value(recoveringError: (Error)->Value) -> Value {
switch self {
case .success(let value): return value
case .error(let error): return recoveringError(error)
}
}
///Replaces an error with the provided fallback Result. If the current Result contains a value, then that value is simply propogated.
/// - Parameter fallback: A SpecificResult used to replace an error condition.
/// - Returns: A SpecificResult with any error replaced by the fallback Result.
public func recover(_ fallback:SpecificResult<Value,Error>) -> SpecificResult<Value,Error> {
switch self {
case .success(_): return self
case .error(_): return fallback
}
}
///Replaces an error with a value provided by the given closure.
/// - Parameter fallback: A closure taking an error and returning a Value.
/// - Returns: a SpecificResult guaranteed to contain a value.
public func recover(_ fallback:(Error)->Value) -> SpecificResult<Value,Error> {
switch self {
case .success(_): return self
case .error(let error): return SpecificResult(fallback(error))
}
}
}
//MARK: - Normalization
extension SpecificResult where Error:Swift.Error {
///Returns a SpecificResult<Value,Swift.Error>. Used to allow easy interoperability when Error types conform to Swift.Error
public func standardResult() -> Result<Value> {
switch self {
case .success(let value): return Result<Value>(value)
case .error(let error): return Result<Value>(error: error)
}
}
}
//MARK: - Throw Interop
extension SpecificResult where Error == Swift.Error {
///Initialize using a throwing function or closure. Errors thrown by the given closure are converted into a Result.error case. Used to convert a throwing function or closure into a Result returning one.
///
/// This function can be used in conjunction with `value()throws` to convert back and forth between native Swift error handling and Results.
/// It can be used as follows to convert a throwing function into one which returns a Result:
/// -
/// let myResult = Result {try myThrowingFunction()}
/// - Parameter throwing: A throwing closure which returns a Value.
public init(_ throwing: ()throws->Value ) {
do {
self = try .success(throwing())
}catch let e {
self = .error(e)
}
}
///Used to chain a series of throwing functions and obtain a Result at the end. Errors throws by the given closure are converted into a Result.error case. The transform is only called if the current Result contains a value, otherwise it just propogates the error
/// - Parameter transform: A throwing closure which takes a value and returns a new value of the same or different type.
/// - Returns: A Result of the type returned by the closure.
/// - This can be used to chain a series of throwing functions operating on a result like so:
/// -
/// let myResult = Result{try throwingFN()}.then{try anotherFN($0)}
public func then<U>(_ transform:(Value)throws->U) -> Result<U> {
do {
switch self {
case .success(let value): return try Result(transform(value))
case .error(let e): return Result<U>(error: e)
}
}catch let e {
return Result<U>(error: e)
}
}
///Maps either a value or error to a new value using throwing closures. If a closure throws, it is converted to a .error case.
/// - Parameters:
/// - transform: A throwing closure which maps the current value to a new value or throws a new error
/// - errorTransform: A throwing closure which maps a current error to a new value or throws a new error
public func flatBimap<U>(success transform: (Value)throws->U, error errorTransform: (Error)throws->U) -> Result<U> {
do {
switch self {
case .success(let value): return Result<U>(try transform(value))
case .error(let e): return Result<U>(try errorTransform(e))
}
}catch let e {
return Result<U>(error: e)
}
}
}
//Throw Interop Cont.
extension SpecificResult where Error:Swift.Error {
///Returns the value of the Result or throws it's error. Used to convert a result back into native Swift error handling (i.e. throwing errors)
///
/// - This function can be used in conjunction with the init taking a throwing closure to convert back and forth between native Swift error handling and Results. It can be used as follows to return to native error handling:
/// -
/// let value = try myResult.value()
/// - Returns: The value contained in the Result
/// - Throws: The error contained in the Result
public func value()throws -> Value {
switch self {
case .success(let value): return value
case .error(let error): throw error
}
}
///Throws a Swift Error if the result contains one.
public func throwError()throws {
switch self {
case .error(let error): throw error
case .success(_): break;
}
}
///Replaces an error with the value returned by the given closure.
///
///If the closure throws, the error is converted into an .error case. If the thrown error does not match the generic Error type, then the throw is ignored (aka. the recovery failed) and the original error is propogated.
/// - Parameter transform: A throwing closure taking an error and returning the value to replace it with.
public func recover(_ transform:(Error)throws->Value) -> SpecificResult<Value,Error> {
do{
switch self {
case .success(_): return self
case .error(let e): return try SpecificResult(transform(e))
}
}catch let e as Error {
return SpecificResult(error: e)
}catch{
return self
}
}
///Replaces an error with the value returned by the given closure.
///
///If the closure throws, the error is converted into an .error case. If the thrown error does not match the generic Error type, then the throw is ignored (aka. the recovery failed) and the original error is propogated.
/// - Parameter fallbackValue: A throwing autoclosure which returns the value to replace any error with.
public func recover(_ fallbackValue:@autoclosure ()throws->Value) -> SpecificResult<Value,Error> {
switch self {
case .success(_): return self
case .error(_):
do {
return SpecificResult(try fallbackValue())
}catch let e as Error {
return SpecificResult(error: e)
}catch {
return self
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment