@@ -104,7 +104,24 @@ defmodule Reactor do
104
104
"""
105
105
@ type concurrency_key_option :: { :concurrency_key , reference ( ) }
106
106
107
- @ type options ::
107
+ @ typedoc """
108
+ When this option is set the Reactor will return a copy of the completed Reactor
109
+ struct for potential future undo.
110
+ """
111
+ @ type fully_reversible_option :: { :fully_reversible? , boolean }
112
+
113
+ @ type run_options ::
114
+ Enumerable . t (
115
+ max_concurrency_option
116
+ | timeout_option
117
+ | max_iterations_option
118
+ | halt_timeout_option
119
+ | async_option
120
+ | concurrency_key_option
121
+ | fully_reversible_option
122
+ )
123
+
124
+ @ type undo_options ::
108
125
Enumerable . t (
109
126
max_concurrency_option
110
127
| timeout_option
@@ -135,7 +152,7 @@ defmodule Reactor do
135
152
@ spec is_reactor ( any ) :: Macro . t ( )
136
153
defguard is_reactor ( reactor ) when is_struct ( reactor , __MODULE__ )
137
154
138
- @ option_schema [
155
+ @ run_schema [
139
156
max_concurrency: [
140
157
type: :pos_integer ,
141
158
required: false ,
@@ -168,6 +185,12 @@ defmodule Reactor do
168
185
type: :any ,
169
186
required: false ,
170
187
doc: "A unique identifier for the Reactor run"
188
+ ] ,
189
+ fully_reversible?: [
190
+ type: :boolean ,
191
+ required: false ,
192
+ default: false ,
193
+ doc: "Return the completed reactor as well as the result for possible later reversal"
171
194
]
172
195
]
173
196
@@ -185,10 +208,10 @@ defmodule Reactor do
185
208
186
209
## Options
187
210
188
- #{ Spark.Options . docs ( @ option_schema ) }
211
+ #{ Spark.Options . docs ( @ run_schema ) }
189
212
"""
190
- @ doc spark_opts: [ { 4 , @ option_schema } ]
191
- @ spec run ( t | module , inputs , context_arg , options ) :: { :ok , any } | { :error , any } | { :halted , t }
213
+ @ spec run ( t | module , inputs , context_arg , run_options ) ::
214
+ { :ok , any } | { :error , any } | { :halted , t }
192
215
def run ( reactor , inputs \\ % { } , context \\ % { } , options \\ [ ] )
193
216
194
217
def run ( reactor , inputs , context , options ) when is_atom ( reactor ) do
@@ -215,7 +238,7 @@ defmodule Reactor do
215
238
end
216
239
217
240
@ doc "Raising version of `run/4`."
218
- @ spec run! ( t | module , inputs , context_arg , options ) :: any | no_return
241
+ @ spec run! ( t | module , inputs , context_arg , run_options ) :: any | no_return
219
242
def run! ( reactor , inputs \\ % { } , context \\ % { } , options \\ [ ] )
220
243
221
244
def run! ( reactor , inputs , context , options ) do
@@ -224,4 +247,63 @@ defmodule Reactor do
224
247
{ :error , reason } -> raise reason
225
248
end
226
249
end
250
+
251
+ @ undo_options Keyword . drop ( @ run_schema , [ :fully_reversible? ] )
252
+
253
+ @ doc """
254
+ Attempt to undo a previously successful Reactor.
255
+
256
+ ## Arguments
257
+
258
+ * `reactor` - The previously successful Reactor struct.
259
+ * `context` - An arbitrary map that will be merged into the Reactor context and passed into each undo.
260
+
261
+ ## Options
262
+
263
+ #{ Spark.Options . docs ( @ undo_options ) }
264
+ """
265
+ @ spec undo ( t , context_arg , undo_options ) :: :ok | { :error , any }
266
+ def undo ( reactor , context , options \\ [ ] )
267
+
268
+ def undo ( reactor , _context , _options ) when not is_struct ( reactor , __MODULE__ ) do
269
+ { :error ,
270
+ ArgumentError . exception (
271
+ message: "`reactor` value `#{ inspect ( reactor ) } ` is not a Reactor struct"
272
+ ) }
273
+ end
274
+
275
+ def undo ( reactor , _context , _options ) when reactor . state != :successful do
276
+ { :error ,
277
+ StateError . exception (
278
+ reactor: reactor ,
279
+ state: reactor . state ,
280
+ expected: ~w[ successful] a
281
+ ) }
282
+ end
283
+
284
+ def undo ( _reactor , context , _options ) when not is_map ( context ) do
285
+ { :error ,
286
+ ArgumentError . exception (
287
+ message: "`context` value `#{ inspect ( context ) } ` is not valid context - must be a map"
288
+ ) }
289
+ end
290
+
291
+ def undo ( _reactor , _context , options ) when not is_list ( options ) do
292
+ { :error ,
293
+ ArgumentError . exception (
294
+ message: "`options` value `#{ inspect ( options ) } ` is not a keyword list"
295
+ ) }
296
+ end
297
+
298
+ def undo ( reactor , context , options ) do
299
+ Reactor.Executor . undo ( reactor , context , options )
300
+ end
301
+
302
+ @ doc "A raising version of `undo/2`"
303
+ @ spec undo! ( t , context_arg , undo_options ) :: :ok | no_return
304
+ def undo! ( reactor , context , options ) do
305
+ with { :error , reason } <- undo ( reactor , context , options ) do
306
+ raise reason
307
+ end
308
+ end
227
309
end
0 commit comments