Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw other errors within init(attempt:) and tryMap #254

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
28 changes: 20 additions & 8 deletions Result/AnyError.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import Foundation

/// Protocols used to define a wrapper for arbitrary `Error`s.
public protocol ErrorInitializing: Swift.Error {
init(_ error: Swift.Error)
}
public protocol ErrorConvertible {
var error : Swift.Error { get }
}

/// A type-erased error which wraps an arbitrary error instance. This should be
/// useful for generic contexts.
public struct AnyError: Swift.Error {
public struct AnyError: Swift.Error, ErrorInitializing, ErrorConvertible {
/// The underlying error.
public let error: Swift.Error

public init(_ error: Swift.Error) {
if let anyError = error as? AnyError {
self = anyError
} else {
self.error = error
self.error = (error as? ErrorConvertible)?.error ?? error
}
}
}

extension AnyError: ErrorConvertible {
public static func error(from error: Error) -> AnyError {
return AnyError(error)
}
}

extension AnyError: CustomStringConvertible {
public var description: String {
return String(describing: error)
Expand All @@ -44,3 +46,13 @@ extension AnyError: LocalizedError {
return (error as? LocalizedError)?.recoverySuggestion
}
}

public protocol NSErrorInitializing : ErrorInitializing {}

extension NSErrorInitializing {
public init(_ error: Swift.Error) {
self = error as! Self
}
}

extension NSError : NSErrorInitializing {}
74 changes: 43 additions & 31 deletions Result/Result.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,26 @@ public enum Result<Value, Error: Swift.Error>: ResultProtocol, CustomStringConve
// MARK: Constructors

/// Constructs a success wrapping a `value`.
public init(value: Value) {
public init(value: Value, errorType: Error.Type) {
self = .success(value)
}
public init(value: Value) {
self.init(value: value, errorType: Error.self)
}

/// Constructs a failure wrapping an `error`.
public init(error: Error) {
public init(error: Error, valueType: Value.Type) {
self = .failure(error)
}
public init(error: Error) {
self.init(error: error, valueType: Value.self)
}

/// Constructs a result from an `Optional`, failing with `Error` if `nil`.
public init(_ value: Value?, failWith: @autoclosure () -> Error) {
self = value.map(Result.success) ?? .failure(failWith())
}

/// Constructs a result from a function that uses `throw`, failing with `Error` if throws.
public init(_ f: @autoclosure () throws -> Value) {
self.init(attempt: f)
}

/// Constructs a result from a function that uses `throw`, failing with `Error` if throws.
public init(attempt f: () throws -> Value) {
do {
self = .success(try f())
} catch var error {
if Error.self == AnyError.self {
error = AnyError(error)
}
self = .failure(error as! Error)
}
}

// MARK: Deconstruction

/// Returns the value from `success` Results or `throw`s the error.
Expand All @@ -47,6 +36,9 @@ public enum Result<Value, Error: Swift.Error>: ResultProtocol, CustomStringConve
case let .success(value):
return value
case let .failure(error):
if let wrapper = error as? ErrorConvertible {
throw wrapper.error
}
throw error
}
}
Expand Down Expand Up @@ -115,6 +107,26 @@ public enum Result<Value, Error: Swift.Error>: ResultProtocol, CustomStringConve
}
}

extension Result where Error: ErrorInitializing {
/// Constructs a result from an expression that uses `throw`, failing with `Error` if throws.
public init(_ f: @autoclosure () throws -> Value) {
self.init(attempt: f)
}

/// Constructs a result from a closure that uses `throw`, failing with `Error` if throws.
public init(attempt f: () throws -> Value) {
do {
self = .success(try f())
} catch {
if let wrappedError = error as? Error {
self = .failure(wrappedError)
} else {
self = .failure(Error.init(error))
}
}
}
}

extension Result where Error == AnyError {
/// Constructs a result from an expression that uses `throw`, failing with `AnyError` if throws.
public init(_ f: @autoclosure () throws -> Value) {
Expand All @@ -131,6 +143,18 @@ extension Result where Error == AnyError {
}
}

extension Result where Error == NoError {
/// Constructs a success wrapping a `value`.
public init(value: Value) {
self = .success(value)
}

/// Constructs a result from an expression that does not use `throw` and should never fail.
public init(_ f: @autoclosure () -> Value) {
self = .success(f())
}
}

// MARK: - Derive result from failable closure

@available(*, deprecated, renamed: "Result.init(attempt:)")
Expand All @@ -143,18 +167,6 @@ public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, AnyErr
return Result(f)
}

// MARK: - ErrorConvertible conformance

extension NSError: ErrorConvertible {
public static func error(from error: Swift.Error) -> Self {
func cast<T: NSError>(_ error: Swift.Error) -> T {
return error as! T
}

return cast(error)
}
}

// MARK: - migration support

@available(*, unavailable, message: "Use the overload which returns `Result<T, AnyError>` instead")
Expand Down
24 changes: 13 additions & 11 deletions Result/ResultProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ public extension Result {
case let .failure(error): return transform(error)
}
}

/// Returns the result of re-wrapping `Failure`’s errors in a different `Error` wrapper, or re-wrapping `Success`es’ values.
public func mapError<Error2: ErrorInitializing>(to errorType: Error2.Type = Error2.self) -> Result<Value, Error2> {
return mapError{ Error2(($0 as? ErrorConvertible)?.error ?? $0) }
}

/// Returns a new Result by mapping `Success`es’ values using `success`, and by mapping `Failure`'s values using `failure`.
public func bimap<U, Error2>(success: (Value) -> U, failure: (Error) -> Error2) -> Result<U, Error2> {
Expand Down Expand Up @@ -87,12 +92,7 @@ public extension Result {
}
}

/// Protocol used to constrain `tryMap` to `Result`s with compatible `Error`s.
public protocol ErrorConvertible: Swift.Error {
static func error(from error: Swift.Error) -> Self
}

public extension Result where Error: ErrorConvertible {
public extension Result where Error: ErrorInitializing {

/// Returns the result of applying `transform` to `Success`es’ values, or wrapping thrown errors.
public func tryMap<U>(_ transform: (Value) throws -> U) -> Result<U, Error> {
Expand All @@ -101,9 +101,10 @@ public extension Result where Error: ErrorConvertible {
return .success(try transform(value))
}
catch {
let convertedError = Error.error(from: error)
// Revisit this in a future version of Swift. https://twitter.com/jckarter/status/672931114944696321
return .failure(convertedError)
guard let wrappedError = error as? Error else {
return .failure(Error.init(error))
}
return .failure(wrappedError)
}
}
}
Expand Down Expand Up @@ -142,5 +143,6 @@ extension Result {

// MARK: - migration support

@available(*, unavailable, renamed: "ErrorConvertible")
public protocol ErrorProtocolConvertible: ErrorConvertible {}
@available(*, unavailable, message: "This has been removed. Use `ErrorInitializing` instead.")
public protocol ErrorProtocolConvertible {}

11 changes: 10 additions & 1 deletion Tests/ResultTests/AnyErrorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import Result

final class AnyErrorTests: XCTestCase {
static var allTests: [(String, (AnyErrorTests) -> () throws -> Void)] {
return [ ("testAnyError", testAnyError) ]
return [
("testAnyError", testAnyError),
("testWrapperError", testWrapperError),
]
}

func testAnyError() {
Expand All @@ -13,4 +16,10 @@ final class AnyErrorTests: XCTestCase {
let anyErrorFromAnyError = AnyError(anyErrorFromError)
XCTAssertTrue(anyErrorFromError == anyErrorFromAnyError)
}
func testWrapperError() {
let error = Error.a
let wrapperErrorFromError = WrapperError(error)
let wrapperErrorFromWrapperError = WrapperError(wrapperErrorFromError)
XCTAssertTrue(wrapperErrorFromError == wrapperErrorFromWrapperError)
}
}
Loading