Created
October 26, 2018 19:37
-
-
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
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
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