From d149dc3b775df28bdb8baf10d70a5a66e20eab3e Mon Sep 17 00:00:00 2001 From: Rachel Brindle Date: Thu, 23 May 2024 22:01:37 -0700 Subject: [PATCH] Provide a default fallback for Pendable when it's a result where Failure == Swift.Error Rename ThrowingDynamicPendable to ThrowingPendable --- Sources/Fakes/Pendable/Pendable.swift | 22 +++++++++--- Sources/Fakes/Spy/Spy+AsyncSequence.swift | 11 ++++++ Sources/Fakes/Spy/Spy+ThrowingPendable.swift | 38 ++++++++++++-------- 3 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 Sources/Fakes/Spy/Spy+AsyncSequence.swift diff --git a/Sources/Fakes/Pendable/Pendable.swift b/Sources/Fakes/Pendable/Pendable.swift index 8981119..10c1145 100644 --- a/Sources/Fakes/Pendable/Pendable.swift +++ b/Sources/Fakes/Pendable/Pendable.swift @@ -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() } @@ -131,8 +134,11 @@ public final class Pendable: @unchecked Sendable, ResolvableWit } } +@available(*, deprecated, renamed: "ThrowingPendable") public typealias ThrowingDynamicPendable = Pendable> +public typealias ThrowingPendable = Pendable> + extension Pendable { /// Gets or throws value for the `Pendable`, possibly waiting until it's resolved. /// @@ -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 { - return Pendable(fallbackValue: fallback) + Pendable(fallbackValue: fallback) } /// Creates a new pending `Pendable` with a fallback value of Void. public static func pending() -> Pendable where Value == Void { - return Pendable(fallbackValue: ()) + Pendable(fallbackValue: ()) } /// Creates a new pending `Pendable` with a fallback value of nil. public static func pending() -> Pendable where Value == Optional { // 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() -> Pendable where Value == Result { + Pendable(fallbackValue: Result.failure(PendableDefaultError())) } } + +/// An error that can be used as a default error for ``Pendable`` when returning a `Result<..., Error>`. +public struct PendableDefaultError: Error, Sendable {} diff --git a/Sources/Fakes/Spy/Spy+AsyncSequence.swift b/Sources/Fakes/Spy/Spy+AsyncSequence.swift new file mode 100644 index 0000000..4eb8545 --- /dev/null +++ b/Sources/Fakes/Spy/Spy+AsyncSequence.swift @@ -0,0 +1,11 @@ +extension Spy { + public func record(_ sequence: AS) async where Arguments == Result, Returning == Void { + do { + for try await value in sequence { + self(.success(value)) + } + } catch { + self(.failure(error)) + } + } +} diff --git a/Sources/Fakes/Spy/Spy+ThrowingPendable.swift b/Sources/Fakes/Spy/Spy+ThrowingPendable.swift index f7c15b1..d9056ab 100644 --- a/Sources/Fakes/Spy/Spy+ThrowingPendable.swift +++ b/Sources/Fakes/Spy/Spy+ThrowingPendable.swift @@ -1,70 +1,80 @@ import Foundation -public typealias ThrowingPendableSpy = Spy> +public typealias ThrowingPendableSpy = Spy> 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(pendingSuccess: Success) where Returning == ThrowingDynamicPendable { + public convenience init(pendingSuccess: Success) where Returning == ThrowingPendable { 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() where Returning == ThrowingDynamicPendable<(), Failure> { + public convenience init() 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(pendingFailure: Failure) where Returning == ThrowingDynamicPendable { + public convenience init(pendingFailure: Failure) where Returning == ThrowingPendable { 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: Success) where Returning == ThrowingDynamicPendable { + public convenience init(success: Success) where Returning == ThrowingPendable { self.init(.finished(.success(success))) } /// Create a throwing pendable Spy that is pre-stubbed to throw the given error. - public convenience init(failure: Failure) where Returning == ThrowingDynamicPendable { + public convenience init(failure: Failure) where Returning == ThrowingPendable { self.init(.finished(.failure(failure))) } } extension Spy { /// Resolve the pendable Spy's stub with the success value. - public func resolveStub(success: Success) where Returning == ThrowingDynamicPendable { + public func resolveStub(success: Success) where Returning == ThrowingPendable { self.resolveStub(with: .success(success)) } /// Resolve the pendable spy's stub with the given error - public func resolveStub(failure: Failure) where Returning == ThrowingDynamicPendable { + public func resolveStub(failure: Failure) where Returning == ThrowingPendable { self.resolveStub(with: .failure(failure)) } } extension Spy { /// Update the pendable Spy's stub to be in a pending state. - public func stub(pendingSuccess: Success) where Returning == ThrowingDynamicPendable { + public func stub(pendingSuccess: Success) where Returning == ThrowingPendable { self.stub(.pending(fallback: .success(pendingSuccess))) } /// Update the pendable Spy's stub to be in a pending state. - public func stub(pendingFailure: Failure) where Returning == ThrowingDynamicPendable { + public func stub(pendingFailure: Failure) where Returning == ThrowingPendable { 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() where Returning == ThrowingPendable { + 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: Success) where Returning == ThrowingDynamicPendable { + public func stub(success: Success) where Returning == ThrowingPendable { 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(failure: Failure) where Returning == ThrowingDynamicPendable { + public func stub(failure: Failure) where Returning == ThrowingPendable { self.stub(.finished(.failure(failure))) } + + /// Update the throwing pendable Spy's stub to throw an error. + public func stubFailure() where Returning == ThrowingPendable { + self.stub(failure: PendableDefaultError()) + } } extension Spy { @@ -78,7 +88,7 @@ extension Spy { public func callAsFunction( _ arguments: Arguments, fallbackDelay: TimeInterval = PendableDefaults.delay - ) async throws -> Success where Returning == ThrowingDynamicPendable { + ) async throws -> Success where Returning == ThrowingPendable { return try await call(arguments).call(fallbackDelay: fallbackDelay).get() } @@ -89,7 +99,7 @@ extension Spy { /// throwing a `PendableInProgressError`. If the `Pendable` is .finished, then this value is ignored. public func callAsFunction( fallbackDelay: TimeInterval = PendableDefaults.delay - ) async throws -> Success where Arguments == Void, Returning == ThrowingDynamicPendable { + ) async throws -> Success where Arguments == Void, Returning == ThrowingPendable { return try await call(()).call(fallbackDelay: fallbackDelay).get() } }