-
Notifications
You must be signed in to change notification settings - Fork 9
/
PersonaList+Reducer.swift
158 lines (136 loc) · 4.42 KB
/
PersonaList+Reducer.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import ComposableArchitecture
import Sargon
import SwiftUI
// MARK: - PersonaList
struct PersonaList: Sendable, FeatureReducer {
@Dependency(\.authorizedDappsClient) var authorizedDappsClient
@Dependency(\.personasClient) var personasClient
@Dependency(\.errorQueue) var errorQueue
struct State: Sendable, Hashable {
var personas: IdentifiedArrayOf<PersonaFeature.State>
let strategy: ReloadingStrategy
var problems: [SecurityProblem] = []
init(
personas: IdentifiedArrayOf<PersonaFeature.State> = [],
strategy: ReloadingStrategy = .all
) {
self.personas = personas
self.strategy = strategy
}
init(dApp: AuthorizedDappDetailed) {
let personas = dApp.detailedAuthorizedPersonas.map { PersonaFeature.State(persona: $0, problems: []) }.asIdentified()
self.init(personas: personas, strategy: .dApp(dApp.dAppDefinitionAddress))
}
enum ReloadingStrategy: Sendable, Hashable {
case all
case ids(OrderedSet<Persona.ID>)
case dApp(AuthorizedDapp.ID)
}
}
enum ViewAction: Sendable, Equatable {
case task
case createNewPersonaButtonTapped
}
@CasePathable
enum ChildAction: Sendable, Equatable {
case persona(id: Persona.ID, action: PersonaFeature.Action)
}
enum DelegateAction: Sendable, Equatable {
case createNewPersona
case openDetails(Persona)
case openSecurityCenter
}
enum InternalAction: Sendable, Equatable {
case setPersonas([Persona])
case setSecurityProblems([SecurityProblem])
}
@Dependency(\.securityCenterClient) var securityCenterClient
init() {}
var body: some ReducerOf<Self> {
Reduce(core)
.forEach(\.personas, action: /Action.child .. ChildAction.persona) {
PersonaFeature()
}
}
func reduce(into state: inout State, viewAction: ViewAction) -> Effect<Action> {
switch viewAction {
case .task:
personasEffect(state: state)
.merge(with: securityProblemsEffect())
case .createNewPersonaButtonTapped:
.send(.delegate(.createNewPersona))
}
}
enum UpdatePersonaError: Error {
case personasMissingFromClient(OrderedSet<Persona.ID>)
}
func reduce(into state: inout State, internalAction: InternalAction) -> Effect<Action> {
switch internalAction {
case let .setPersonas(personas):
state.personas = personas
.map { PersonaFeature.State(persona: $0, problems: state.problems) }
.asIdentified()
return .none
case let .setSecurityProblems(problems):
state.problems = problems
state.personas.mutateAll { persona in
persona.securityProblemsConfig.update(problems: problems)
}
return .none
}
}
func reduce(into state: inout State, childAction: ChildAction) -> Effect<Action> {
switch childAction {
case let .persona(id, action: .delegate(delegateAction)):
switch delegateAction {
case .openDetails:
.run { send in
let persona = try await personasClient.getPersona(id: id)
await send(.delegate(.openDetails(persona)))
} catch: { error, _ in
errorQueue.schedule(error)
}
case .openSecurityCenter:
.send(.delegate(.openSecurityCenter))
}
case .persona:
.none
}
}
private func personasEffect(state: State) -> Effect<Action> {
.run { [strategy = state.strategy] send in
for try await personas in await personasClient.personas() {
guard !Task.isCancelled else { return }
let ids = try await personaIDs(strategy) ?? OrderedSet(validating: personas.ids)
let result = ids.compactMap { personas[id: $0] }
guard result.count == ids.count else {
throw UpdatePersonaError.personasMissingFromClient(ids.subtracting(result.map(\.id)))
}
await send(.internal(.setPersonas(result)))
}
} catch: { error, _ in
loggerGlobal.error("Failed to update personas from client, error: \(error)")
errorQueue.schedule(error)
}
}
private func securityProblemsEffect() -> Effect<Action> {
.run { send in
for try await problems in await securityCenterClient.problems() {
guard !Task.isCancelled else { return }
await send(.internal(.setSecurityProblems(problems)))
}
}
}
/// Returns the ids of personas to include under the given strategy. nil means that all ids should be included
private func personaIDs(_ strategy: State.ReloadingStrategy) async throws -> OrderedSet<Persona.ID>? {
switch strategy {
case .all:
return nil
case let .ids(ids):
return ids
case let .dApp(dAppID):
guard let dApp = try? await authorizedDappsClient.getDetailedDapp(dAppID) else { return [] }
return OrderedSet(dApp.detailedAuthorizedPersonas.map(\.id))
}
}
}