Skip to content

Commit

Permalink
Merge pull request #17 from Quick/pendable_error_fallback
Browse files Browse the repository at this point in the history
Provide a default fallback for Pendable when it's a result where Failure == Swift.Error
  • Loading branch information
younata authored May 24, 2024
2 parents 41eddbc + d149dc3 commit 38bcf83
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 18 deletions.
22 changes: 18 additions & 4 deletions Sources/Fakes/Pendable/Pendable.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Foundation

protocol ResolvableWithFallback {
/// A way to type-erase Pendable, specifically just for resolving with the fallback.
///
/// This is meant to be used by ```Spy``` as part of changing the stub in order to quickly resolve pending calls.
public protocol ResolvableWithFallback {
func resolveWithFallback()
}

Expand Down Expand Up @@ -131,8 +134,11 @@ public final class Pendable<Value: Sendable>: @unchecked Sendable, ResolvableWit
}
}

@available(*, deprecated, renamed: "ThrowingPendable")
public typealias ThrowingDynamicPendable<Success, Failure: Error> = Pendable<Result<Success, Failure>>

public typealias ThrowingPendable<Success, Failure: Error> = Pendable<Result<Success, Failure>>

extension Pendable {
/// Gets or throws value for the `Pendable`, possibly waiting until it's resolved.
///
Expand Down Expand Up @@ -162,17 +168,25 @@ extension Pendable {
extension Pendable {
/// Creates a new pending `Pendable` with the given fallback value.
public static func pending(fallback: Value) -> Pendable<Value> {
return Pendable(fallbackValue: fallback)
Pendable(fallbackValue: fallback)
}

/// Creates a new pending `Pendable` with a fallback value of Void.
public static func pending() -> Pendable<Value> where Value == Void {
return Pendable(fallbackValue: ())
Pendable(fallbackValue: ())
}

/// Creates a new pending `Pendable` with a fallback value of nil.
public static func pending<Wrapped>() -> Pendable<Value> where Value == Optional<Wrapped> {
// swiftlint:disable:previous syntactic_sugar
return Pendable(fallbackValue: nil)
Pendable(fallbackValue: nil)
}

/// Creatse a new pending `Pendable` with a fallback value of an error.
public static func pending<Success>() -> Pendable<Value> where Value == Result<Success, Error> {
Pendable(fallbackValue: Result<Success, Error>.failure(PendableDefaultError()))
}
}

/// An error that can be used as a default error for ``Pendable`` when returning a `Result<..., Error>`.
public struct PendableDefaultError: Error, Sendable {}
11 changes: 11 additions & 0 deletions Sources/Fakes/Spy/Spy+AsyncSequence.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extension Spy {
public func record<AS: AsyncSequence>(_ sequence: AS) async where Arguments == Result<AS.Element, Error>, Returning == Void {
do {
for try await value in sequence {
self(.success(value))
}
} catch {
self(.failure(error))
}
}
}
38 changes: 24 additions & 14 deletions Sources/Fakes/Spy/Spy+ThrowingPendable.swift
Original file line number Diff line number Diff line change
@@ -1,70 +1,80 @@
import Foundation

public typealias ThrowingPendableSpy<Arguments, Success, Failure: Error> = Spy<Arguments, ThrowingDynamicPendable<Success, Failure>>
public typealias ThrowingPendableSpy<Arguments, Success, Failure: Error> = Spy<Arguments, ThrowingPendable<Success, Failure>>

extension Spy {
/// Create a throwing pendable Spy that is pre-stubbed to return a pending that will block for a bit before returning success.
public convenience init<Success: Sendable, Failure: Error>(pendingSuccess: Success) where Returning == ThrowingDynamicPendable<Success, Failure> {
public convenience init<Success: Sendable, Failure: Error>(pendingSuccess: Success) where Returning == ThrowingPendable<Success, Failure> {
self.init(.pending(fallback: .success(pendingSuccess)))
}

/// Create a throwing pendable Spy that is pre-stubbed to return a pending that will block for a bit before returning Void.
public convenience init<Failure: Error>() where Returning == ThrowingDynamicPendable<(), Failure> {
public convenience init<Failure: Error>() where Returning == ThrowingPendable<(), Failure> {
self.init(.pending(fallback: .success(())))
}

/// Create a throwing pendable Spy that is pre-stubbed to return a pending that will block for a bit before throwing an error.
public convenience init<Success: Sendable, Failure: Error>(pendingFailure: Failure) where Returning == ThrowingDynamicPendable<Success, Failure> {
public convenience init<Success: Sendable, Failure: Error>(pendingFailure: Failure) where Returning == ThrowingPendable<Success, Failure> {
self.init(.pending(fallback: .failure(pendingFailure)))
}

/// Create a throwing pendable Spy that is pre-stubbed to return a finished & successful value.
public convenience init<Success: Sendable, Failure: Error>(success: Success) where Returning == ThrowingDynamicPendable<Success, Failure> {
public convenience init<Success: Sendable, Failure: Error>(success: Success) where Returning == ThrowingPendable<Success, Failure> {
self.init(.finished(.success(success)))
}

/// Create a throwing pendable Spy that is pre-stubbed to throw the given error.
public convenience init<Success: Sendable, Failure: Error>(failure: Failure) where Returning == ThrowingDynamicPendable<Success, Failure> {
public convenience init<Success: Sendable, Failure: Error>(failure: Failure) where Returning == ThrowingPendable<Success, Failure> {
self.init(.finished(.failure(failure)))
}
}

extension Spy {
/// Resolve the pendable Spy's stub with the success value.
public func resolveStub<Success: Sendable, Failure: Error>(success: Success) where Returning == ThrowingDynamicPendable<Success, Failure> {
public func resolveStub<Success: Sendable, Failure: Error>(success: Success) where Returning == ThrowingPendable<Success, Failure> {
self.resolveStub(with: .success(success))
}

/// Resolve the pendable spy's stub with the given error
public func resolveStub<Success: Sendable, Failure: Error>(failure: Failure) where Returning == ThrowingDynamicPendable<Success, Failure> {
public func resolveStub<Success: Sendable, Failure: Error>(failure: Failure) where Returning == ThrowingPendable<Success, Failure> {
self.resolveStub(with: .failure(failure))
}
}

extension Spy {
/// Update the pendable Spy's stub to be in a pending state.
public func stub<Success: Sendable, Failure: Error>(pendingSuccess: Success) where Returning == ThrowingDynamicPendable<Success, Failure> {
public func stub<Success: Sendable, Failure: Error>(pendingSuccess: Success) where Returning == ThrowingPendable<Success, Failure> {
self.stub(.pending(fallback: .success(pendingSuccess)))
}

/// Update the pendable Spy's stub to be in a pending state.
public func stub<Success: Sendable, Failure: Error>(pendingFailure: Failure) where Returning == ThrowingDynamicPendable<Success, Failure> {
public func stub<Success: Sendable, Failure: Error>(pendingFailure: Failure) where Returning == ThrowingPendable<Success, Failure> {
self.stub(.pending(fallback: .failure(pendingFailure)))
}

/// Update the pendable Spy's stub to be in a pending state with a default failure value.
public func stubPendingFailure<Success: Sendable>() where Returning == ThrowingPendable<Success, Error> {
self.stub(pendingFailure: PendableDefaultError())
}

/// Update the throwing pendable Spy's stub to be successful, with the given value.
///
/// - parameter success: The value to return when `callAsFunction` is called.
public func stub<Success: Sendable, Failure: Error>(success: Success) where Returning == ThrowingDynamicPendable<Success, Failure> {
public func stub<Success: Sendable, Failure: Error>(success: Success) where Returning == ThrowingPendable<Success, Failure> {
self.stub(.finished(.success(success)))
}

/// Update the throwing pendable Spy's stub to throw the given error.
///
/// - parameter failure: The error to throw when `callAsFunction` is called.
public func stub<Success: Sendable, Failure: Error>(failure: Failure) where Returning == ThrowingDynamicPendable<Success, Failure> {
public func stub<Success: Sendable, Failure: Error>(failure: Failure) where Returning == ThrowingPendable<Success, Failure> {
self.stub(.finished(.failure(failure)))
}

/// Update the throwing pendable Spy's stub to throw an error.
public func stubFailure<Success: Sendable>() where Returning == ThrowingPendable<Success, Error> {
self.stub(failure: PendableDefaultError())
}
}

extension Spy {
Expand All @@ -78,7 +88,7 @@ extension Spy {
public func callAsFunction<Success: Sendable, Failure: Error>(
_ arguments: Arguments,
fallbackDelay: TimeInterval = PendableDefaults.delay
) async throws -> Success where Returning == ThrowingDynamicPendable<Success, Failure> {
) async throws -> Success where Returning == ThrowingPendable<Success, Failure> {
return try await call(arguments).call(fallbackDelay: fallbackDelay).get()
}

Expand All @@ -89,7 +99,7 @@ extension Spy {
/// throwing a `PendableInProgressError`. If the `Pendable` is .finished, then this value is ignored.
public func callAsFunction<Success: Sendable, Failure: Error>(
fallbackDelay: TimeInterval = PendableDefaults.delay
) async throws -> Success where Arguments == Void, Returning == ThrowingDynamicPendable<Success, Failure> {
) async throws -> Success where Arguments == Void, Returning == ThrowingPendable<Success, Failure> {
return try await call(()).call(fallbackDelay: fallbackDelay).get()
}
}

0 comments on commit 38bcf83

Please sign in to comment.