This commit is contained in:
2024-05-30 22:54:58 -04:00
parent bd0f90894f
commit dac5d932dd
5 changed files with 238 additions and 52 deletions

View File

@@ -4,38 +4,179 @@ import OSLog
extension Effect {
/// An effect that throws a runtime warning and optionally logs an error message.
///
/// This effect uses `XCTFail` to throw a runtime warning and will also log the message
/// if a logger is supplied.
///
/// ## Example
///
///```swift
/// @Reducer
/// struct MyFeature {
/// ...
/// enum Action {
/// case receive(TaskResult<Int>)
/// ...
/// }
///
/// @Dependency(\.logger) var logger
///
/// var body: some ReducerOf<Self> {
/// Reduce { state, action in
/// switch action {
/// case .receive(.failure(_)):
/// return .fail("Failed retreiving number fact.", log: { logger.debug("\($0)") })
/// ...
///
/// }
/// }
/// }
/// }
///```
///
/// - Parameters:
/// - message: The message for the failure reason.
/// - logger: An optional logger to use to log the message.
public static func fail(
_ message: String,
log: (@Sendable (String) -> Void)? = nil
) -> Self {
XCTFail("\(message)")
log?(message)
return .none
}
/// An effect that throws a runtime warning and optionally logs an error message.
///
/// This effect uses `XCTFail` to throw a runtime warning and will also log the message
/// if a logger is supplied.
///
/// ## Example
///
///```swift
/// @Reducer
/// struct MyFeature {
/// ...
/// enum Action {
/// case receive(TaskResult<Int>)
/// ...
/// }
///
/// @Dependency(\.logger) var logger
///
/// var body: some ReducerOf<Self> {
/// Reduce { state, action in
/// switch action {
/// case .receive(.failure(_)):
/// return .fail("Failed retreiving number fact.", logger: logger)
/// ...
///
/// }
/// }
/// }
/// }
///```
///
/// - Parameters:
/// - message: The message for the failure reason.
/// - logger: An optional logger to use to log the message.
@available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
public static func fail(
_ message: String,
logger: Logger? = nil
) -> Self {
XCTFail("\(message)")
logger?.error("\(message)")
return .none
.fail(message, log: { logger?.error("\($0)") })
}
/// An effect that throws a runtime warning and optionally logs an error message.
///
///
/// This effect uses `XCTFail` to throw a runtime warning and will also log the message
/// if a logger is supplied.
///
/// ## Example
///
///```swift
/// @Reducer
/// struct MyFeature {
/// ...
/// enum Action {
/// case receive(TaskResult<Int>)
/// ...
/// }
///
/// @Dependency(\.logger) var logger
///
/// var body: some ReducerOf<Self> {
/// Reduce { state, action in
/// switch action {
/// case let .receive(.failure(error)):
/// return .fail(error: error, logger: logger)
/// ...
///
/// }
/// }
/// }
/// }
///```
///
/// - Parameters:
/// - prefix: The prefix for the error message for the failure.
/// - error: The error for the failure..
/// - logger: A logger to use to log the message.
@available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
public static func fail(
prefix: String = "Failed error:",
error: any Error,
logger: Logger
) -> Self {
return .fail(prefix: prefix, error: error, log: { logger.error("\($0)") })
.fail(prefix: prefix, error: error, log: { logger.error("\($0)") })
}
/// An effect that throws a runtime warning and optionally logs an error message.
/// An effect that throws a runtime warning and optionally logs an error message.
///
///
/// This effect uses `XCTFail` to throw a runtime warning and will also log the message
/// if a logger is supplied.
///
/// ## Example
///
///```swift
/// @Reducer
/// struct MyFeature {
/// ...
/// enum Action {
/// case receive(TaskResult<Int>)
/// ...
/// }
///
/// @Dependency(\.logger) var logger
///
/// var body: some ReducerOf<Self> {
/// Reduce { state, action in
/// switch action {
/// case .receive(.failure(_)):
/// return .fail("Failed retreiving number fact.", log: { logger.debug("\($0)") })
/// ...
///
/// }
/// }
/// }
/// }
///```
///
/// - Parameters:
/// - prefix: The prefix for the error message for the failure.
/// - error: The error for the failure..
/// - log: A log handler to use to log the message.
public static func fail(
prefix: String = "Failed error:",
error: any Error,
log: ((String) -> Void)? = nil
log: (@Sendable (String) -> Void)? = nil
) -> Self {
let message = "\(prefix) \(error.localizedDescription)"
XCTFail("\(message)")
log?("\(message)")
return .none
return .fail("\(prefix) \(error.localizedDescription)", log: log)
}
}

View File

@@ -1,31 +1,13 @@
import ComposableArchitecture
import OSLog
extension Effect {
@usableFromInline
static func receive<T>(
_ casePath: AnyCasePath<Action, TaskResult<T>>,
_ operation: @escaping @Sendable () async throws -> T
static func receive<Input, Result>(
operation: ReceiveOperation<Action, Input, Result>
) -> Self {
.run { send in
await send(casePath.embed(
TaskResult { try await operation() }
))
}
}
@usableFromInline
static func receive<T, V>(
_ casePath: AnyCasePath<Action, TaskResult<V>>,
_ operation: @escaping @Sendable () async throws -> T,
_ transform: @escaping @Sendable (T) -> V
) -> Self {
.run { send in
await send(casePath.embed(
TaskResult { try await operation() }
.map(transform)
))
await operation(send: send)
}
}
@@ -34,7 +16,7 @@ extension Effect {
action toResult: CaseKeyPath<Action, TaskResult<T>>,
operation: @escaping @Sendable () async throws -> T
) -> Self {
.receive(AnyCasePath(toResult), operation)
.receive(operation: .case(AnyCasePath(toResult), operation))
}
@inlinable
@@ -43,7 +25,7 @@ extension Effect {
operation: @escaping @Sendable () async throws -> T,
transform: @escaping @Sendable (T) -> V
) -> Self {
.receive(AnyCasePath(toResult), operation, transform)
.receive(operation: .case(AnyCasePath(toResult), operation, transform))
}
}
@@ -54,7 +36,7 @@ extension Effect where Action: ReceiveAction {
_ operation: @escaping @Sendable () async throws -> T,
transform: @escaping @Sendable (T) -> Action.ReceiveAction
) -> Self {
.receive(AnyCasePath(unsafe: Action.receive), operation, transform)
.receive(operation: .case(AnyCasePath(unsafe: Action.receive), operation, transform))
}
@inlinable
@@ -68,3 +50,51 @@ extension Effect where Action: ReceiveAction {
}
}
@usableFromInline
struct ReceiveOperation<Action, Input, Result> {
@usableFromInline
let embed: @Sendable (TaskResult<Result>) -> Action
@usableFromInline
let operation: @Sendable () async throws -> Input
@usableFromInline
let transform: @Sendable (Input) -> Result
@usableFromInline
func callAsFunction(send: Send<Action>) async {
await send(embed(
TaskResult { try await operation() }
.map(transform)
))
}
@usableFromInline
static func `case`(
_ casePath: AnyCasePath<Action, TaskResult<Result>>,
_ operation: @escaping @Sendable () async throws -> Input,
_ transform: @escaping @Sendable (Input) -> Result
) -> Self {
.init(embed: { casePath.embed($0) }, operation: operation, transform: transform)
}
}
extension ReceiveOperation where Input == Result {
@usableFromInline
init(
embed: @escaping @Sendable (TaskResult<Result>) -> Action,
operation: @escaping @Sendable () async throws -> Input
) {
self.init(embed: embed, operation: operation, transform: { $0 })
}
@usableFromInline
static func `case`(
_ casePath: AnyCasePath<Action, TaskResult<Result>>,
_ operation: @escaping @Sendable () async throws -> Input
) -> Self {
.init(embed: { casePath.embed($0) }, operation: operation)
}
}

View File

@@ -30,3 +30,20 @@ enum SetAction<State, Action, Value> {
}
}
@usableFromInline
struct SetAction2<State, Action, Value> {
let operation: @Sendable (inout State, Value) -> Void
init(_ operation: @escaping @Sendable (inout State, Value) -> Void) {
self.operation = operation
}
static func keyPath(
_ keyPath: WritableKeyPath<State, Value>
) -> Self {
.init({ state, value in
state[keyPath: keyPath] = value
})
}
}

View File

@@ -3,7 +3,7 @@ import Foundation
import OSLog
public enum OnFailAction<State, Action> {
case fail(prefix: String? = nil, log: ((String) -> Void)? = nil)
case fail(prefix: String? = nil, log: (@Sendable (String) -> Void)? = nil)
case operation((inout State, Error) -> Effect<Action>)
@usableFromInline

View File

@@ -188,13 +188,15 @@ public struct _OnReceiveReducer<Parent: Reducer, Value>: Reducer {
action: Parent.Action
) -> Effect<Parent.Action> {
let baseEffects = parent.reduce(into: &state, action: action)
var setEffects = Effect<Action>.none
if let value = receiveAction(action) {
setEffects = setAction(state: &state, value: value)
guard let value = receiveAction(action) else {
return baseEffects
}
return .merge(baseEffects, setEffects)
return .merge(
baseEffects,
setAction(state: &state, value: value)
)
}
}
@@ -208,13 +210,13 @@ public struct _ReceiveOnTriggerReducer<
let parent: Parent
@usableFromInline
let triggerAction: (Parent.Action) -> TriggerAction?
let triggerAction: @Sendable (Parent.Action) -> TriggerAction?
@usableFromInline
let toReceiveAction: (TaskResult<Value>) -> Parent.Action
let toReceiveAction: @Sendable (TaskResult<Value>) -> Parent.Action
@usableFromInline
let resultHandler: () async throws -> Value
let resultHandler: @Sendable () async throws -> Value
@usableFromInline
init(
@@ -242,11 +244,7 @@ public struct _ReceiveOnTriggerReducer<
return .merge(
baseEffects,
.run { send in
await send(toReceiveAction(
TaskResult { try await resultHandler() }
))
}
.receive(operation: .init(embed: toReceiveAction, operation: resultHandler))
)
}
}