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

Add type-erased AnyError for generic contexts #198

Merged
merged 6 commits into from
Nov 29, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion Result/Result.swift
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,26 @@ public enum Result<T, Error: Swift.Error>: ResultProtocol, CustomStringConvertib

// MARK: - Derive result from failable closure

public func materialize<T>(_ f: () throws -> T) -> Result<T, AnyError> {
return materialize(try f())
}

public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, AnyError> {
do {
return .success(try f())
} catch let error as AnyError {
return .failure(error)
} catch {
return .failure(AnyError(error))
}
}

@available(*, deprecated, message: "Use the overload which returns `Result<T, AnyError>` instead")
public func materialize<T>(_ f: () throws -> T) -> Result<T, NSError> {
return materialize(try f())
}

@available(*, deprecated, message: "Use the overload which returns `Result<T, AnyError>` instead")
public func materialize<T>(_ f: @autoclosure () throws -> T) -> Result<T, NSError> {
do {
return .success(try f())
Expand Down Expand Up @@ -160,7 +176,7 @@ extension NSError: ErrorProtocolConvertible {
}
}

// MARK: -
// MARK: - Errors

/// An “error” that is impossible to construct.
///
Expand All @@ -169,6 +185,33 @@ extension NSError: ErrorProtocolConvertible {
/// contains an `Int`eger and is guaranteed never to be a `failure`.
public enum NoError: Swift.Error { }

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

public init(_ error: Swift.Error) {
self.error = error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to check if error is AnyError?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 1a12328.

}

public static func error(from error: Error) -> AnyError {
if let anyError = error as? AnyError {
return anyError
}
return AnyError(error)
}

public static func ==(lhs: AnyError, rhs: AnyError) -> Bool {
return lhs.error._code == rhs.error._code
&& lhs.error._domain == rhs.error._domain
Copy link
Contributor

@norio-nomura norio-nomura Nov 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_domain should be compared before _code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I withdraw my comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick question: Are _domain and _code allowed to use?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are _domain and _code allowed to use?

I'm not confident. It should be okay to remove Equatable conformance (in that case use them for testing purpose only).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not confident. It should be okay to remove Equatable conformance (in that case use them for testing purpose only).

+1. I also think removing Equatable is safer to ship :shipit:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 3cafe3e.

}

public var description: String {
return String(describing: error)
}
}

// MARK: - migration support
extension Result {
@available(*, unavailable, renamed: "success")
Expand Down
74 changes: 28 additions & 46 deletions Tests/ResultTests/ResultTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,71 +38,54 @@ final class ResultTests: XCTestCase {
// MARK: Try - Catch

func testTryCatchProducesSuccesses() {
let result: Result<String, NSError> = Result(try tryIsSuccess("success"))
let result: Result<String, AnyError> = Result(try tryIsSuccess("success"))
XCTAssert(result == success)
}

func testTryCatchProducesFailures() {
#if os(Linux)
/// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4.
print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.")
#else
let result: Result<String, NSError> = Result(try tryIsSuccess(nil))
XCTAssert(result.error == error)
#endif
let result: Result<String, AnyError> = Result(try tryIsSuccess(nil))
XCTAssert(result.error == error)
}

func testTryCatchWithFunctionProducesSuccesses() {
let function = { try tryIsSuccess("success") }

let result: Result<String, NSError> = Result(attempt: function)
let result: Result<String, AnyError> = Result(attempt: function)
XCTAssert(result == success)
}

func testTryCatchWithFunctionCatchProducesFailures() {
#if os(Linux)
/// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4.
print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.")
#else
let function = { try tryIsSuccess(nil) }

let result: Result<String, NSError> = Result(attempt: function)
XCTAssert(result.error == error)
#endif
let function = { try tryIsSuccess(nil) }

let result: Result<String, AnyError> = Result(attempt: function)
XCTAssert(result.error == error)
}

func testMaterializeProducesSuccesses() {
let result1 = materialize(try tryIsSuccess("success"))
let result1: Result<String, AnyError> = materialize(try tryIsSuccess("success"))
XCTAssert(result1 == success)

let result2: Result<String, NSError> = materialize { try tryIsSuccess("success") }
let result2: Result<String, AnyError> = materialize { try tryIsSuccess("success") }
XCTAssert(result2 == success)
}

func testMaterializeProducesFailures() {
#if os(Linux)
/// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4.
print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.")
#else
let result1 = materialize(try tryIsSuccess(nil))
XCTAssert(result1.error == error)

let result2: Result<String, NSError> = materialize { try tryIsSuccess(nil) }
XCTAssert(result2.error == error)
#endif
let result1: Result<String, AnyError> = materialize(try tryIsSuccess(nil))
XCTAssert(result1.error == error)

let result2: Result<String, AnyError> = materialize { try tryIsSuccess(nil) }
XCTAssert(result2.error == error)
}

// MARK: Recover

func testRecoverProducesLeftForLeftSuccess() {
let left = Result<String, NSError>.success("left")
let left = Result<String, Error>.success("left")
XCTAssertEqual(left.recover("right"), "left")
}

func testRecoverProducesRightForLeftFailure() {
struct Error: Swift.Error {}

let left = Result<String, Error>.failure(Error())
let left = Result<String, Error>.failure(Error.a)
XCTAssertEqual(left.recover("right"), "right")
}

Expand Down Expand Up @@ -169,13 +152,8 @@ final class ResultTests: XCTestCase {
}

func testTryMapProducesFailure() {
#if os(Linux)
/// FIXME: skipped on Linux because of crash with swift-3.0-PREVIEW-4.
print("Test Case `\(#function)` skipped on Linux because of crash with swift-3.0-PREVIEW-4.")
#else
let result = Result<String, NSError>.success("fail").tryMap(tryIsSuccess)
XCTAssert(result == failure)
#endif
let result = Result<String, AnyError>.success("fail").tryMap(tryIsSuccess)
XCTAssert(result == failure)
}

// MARK: Operators
Expand All @@ -202,11 +180,15 @@ final class ResultTests: XCTestCase {

// MARK: - Fixtures

let success = Result<String, NSError>.success("success")
let error = NSError(domain: "com.antitypical.Result", code: 1, userInfo: nil)
let error2 = NSError(domain: "com.antitypical.Result", code: 2, userInfo: nil)
let failure = Result<String, NSError>.failure(error)
let failure2 = Result<String, NSError>.failure(error2)
private enum Error: Swift.Error {
case a, b
}

let success = Result<String, AnyError>.success("success")
let error = AnyError(Error.a)
let error2 = AnyError(Error.b)
let failure = Result<String, AnyError>.failure(error)
let failure2 = Result<String, AnyError>.failure(error2)


// MARK: - Helpers
Expand Down