@@ -42,7 +42,7 @@ internal class EventRecorderImpl(
4242 fun onProcessEvent (eventAndArgument : EventAndArgument <* >, processingResult : ProcessingResult ) {
4343 val lastEvent = records.lastOrNull()?.eventAndArgument?.event
4444 check(lastEvent !is DestroyEvent ) {
45- " Internal error, ${::onProcessEvent:: name} called after " +
45+ " Internal error, ${::onProcessEvent. name} called after " +
4646 " ${DestroyEvent ::class .simpleName} processing, which is considered as last possible event"
4747 }
4848 if (arguments.skipIgnoredEvents && processingResult == ProcessingResult .IGNORED ) return
@@ -89,12 +89,14 @@ object StrictValidator : RestorationResultValidator {
8989 throw RestorationResultValidationException (
9090 result,
9191 " The ${RestorationResult ::class .simpleName} contains warnings" ,
92+ it.warnings.first(),
9293 )
9394 }
9495 if (it.processingResult.isFailure) {
9596 throw RestorationResultValidationException (
9697 result,
9798 " The ${RestorationResult ::class .simpleName} contains failed processing result" ,
99+ it.processingResult.exceptionOrNull(),
98100 )
99101 }
100102 }
@@ -120,9 +122,15 @@ class RestorationWarningException(
120122
121123/* *
122124 * Processes [RecordedEvents] with purpose of restoring a [StateMachine] to a state configuration as it was before.
125+ * Starts the [StateMachine] if necessary and returns [RestorationResult] allowing to inspect
126+ * how the restoration was processed.
127+ *
128+ * There is no way on library side to decide if some exceptions during event processing are errors or not.
129+ * For instance [StateMachine] may be configured with [throwingIgnoredEventHandler] so some exceptions might
130+ * be expected and are not really errors.
123131 *
124132 * @param muteListeners listeners are not triggered by default,
125- * as I assume that client code reactions were already processed before.
133+ * as we assume that client code reactions were already processed before.
126134 * @param disableStructureHashCodeCheck allows to skip the machine structure check
127135 * to force processing of [RecordedEvents]. Note that running the same event sequence on similar machines but having
128136 * different structureHashCode value, may produce different results more likely.
@@ -132,100 +140,78 @@ suspend fun StateMachine.restoreByRecordedEvents(
132140 muteListeners : Boolean = true,
133141 disableStructureHashCodeCheck : Boolean = false,
134142 validator : RestorationResultValidator = StrictValidator ,
135- ): Unit = coroutineAbstraction.withContext {
136- if (isRunning) {
137- restoreRunningMachineByRecordedEvents(recordedEvents, muteListeners, disableStructureHashCodeCheck, validator)
138- } else {
139- onStarted {
140- restoreRunningMachineByRecordedEvents(
141- recordedEvents,
142- muteListeners,
143- disableStructureHashCodeCheck,
144- validator,
145- )
146- }
147- }
148- }
149-
150- /* *
151- * May be called on started ([StateMachine.isRunning] == true) [StateMachine] only,
152- * and returns [RestorationResult] allowing to inspect how the restoration was processed.
153- *
154- * There is no way on library side to decide if some exceptions during event processing are errors or not.
155- * For instance [StateMachine] may be configured with [throwingIgnoredEventHandler] so some exceptions might
156- * be expected and are not really errors.
157- */
158- suspend fun StateMachine.restoreRunningMachineByRecordedEvents (
159- recordedEvents : RecordedEvents ,
160- muteListeners : Boolean = true,
161- disableStructureHashCodeCheck : Boolean = false,
162- validator : RestorationResultValidator = StrictValidator ,
163143): RestorationResult = coroutineAbstraction.withContext {
164- check(isRunning) {
165- " $this is not running, ${::restoreRunningMachineByRecordedEvents.name} () operation only makes sense on " +
166- " created and started ${StateMachine ::class .simpleName} , please call it after the machine is started"
167- }
168144 checkNotDestroyed()
169- check(! hasProcessedEvents) {
170- " $this has already processed events, ${::restoreRunningMachineByRecordedEvents.name} () operation only makes " +
171- " sense on initially clear ${StateMachine ::class .simpleName} , please call it before " +
172- " processing any other events"
145+ if (isRunning) {
146+ check(! hasProcessedEvents) {
147+ " $this has already processed events, ${::restoreByRecordedEvents.name} () operation only makes " +
148+ " sense on initially clear ${StateMachine ::class .simpleName} , please call it before " +
149+ " processing any other events (or even before start - optionally)"
150+ }
173151 }
174152
175153 if (! disableStructureHashCodeCheck)
176154 check(structureHashCode == recordedEvents.structureHashCode) {
177- " $this structure seems to be different from recorded original one"
155+ " $this structure seems to be different from recorded original one, you can disable this error by the " +
156+ " disableStructureHashCodeCheck argument if you are sure that it is correct"
178157 }
179158
180159 this as InternalStateMachine
181160 val results = mutableListOf<RestoredEventResult >()
182161 val mutationSection = if (muteListeners) openListenersMutationSection() else EmptyListenersMutationSection
183162 mutationSection.use {
184- for (record in recordedEvents.records) {
163+ recordedEvents.records.forEachIndexed iteration@{ index, record ->
185164 val warnings = mutableListOf<RestorationWarningException >()
186165 val (event, argument) = record.eventAndArgument
187- if (event is StartEvent )
188- continue // fixme вызов start мог иметь argument что с ним делать?
189- val processingResult = runCatching { processEvent(event, argument) }
190- val actualResult = processingResult.getOrNull()
191- if (actualResult != null && actualResult != record.processingResult) {
192- if (actualResult == ProcessingResult .PENDING ) {
193- if (pendingEventHandler !is QueuePendingEventHandler )
166+ if (event is StartEvent ) {
167+ if (isRunning) {
168+ if (argument == null ) {
169+ return @iteration // continue
170+ } else {
171+ if (index == 0 )
172+ error(
173+ " The ${StateMachine ::class .simpleName} is already started, but " +
174+ " the ${RecordedEvents ::class .simpleName} contains an argument for " +
175+ " ${StateMachine ::start.name} method. " +
176+ " To restore such machine, " +
177+ " do not start it before calling ${::restoreByRecordedEvents.name} "
178+ )
179+ else
180+ error(" The machine should not be running here. Internal error. Never get here" )
181+ }
182+ } else {
183+ start(argument)
184+ // fixme add RestoredEventResult?
185+ }
186+ } else {
187+ val processingResult = runCatching { processEvent(event, argument) }
188+ val actualResult = processingResult.getOrNull()
189+ if (actualResult != null && actualResult != record.processingResult) {
190+ if (actualResult == ProcessingResult .PENDING ) {
191+ if (pendingEventHandler !is QueuePendingEventHandler )
192+ warnings + = RestorationWarningException (
193+ WarningType .PendingEventMightBeIgnored ,
194+ " Actual result is ${ProcessingResult .PENDING } , " +
195+ " but the ${StateMachine ::class .simpleName} is NOT configured " +
196+ " with ${QueuePendingEventHandler ::class ::simpleName} , which potentially means that " +
197+ " the event {${record.eventAndArgument.event} } might be silently ignored" ,
198+ )
199+ } else {
194200 warnings + = RestorationWarningException (
195- WarningType .PendingEventMightBeIgnored ,
196- " Actual result is ${ProcessingResult .PENDING } , " +
197- " but the ${StateMachine ::class .simpleName} is NOT configured " +
198- " with ${QueuePendingEventHandler ::class ::simpleName} , which potentially means that " +
199- " the event {${record.eventAndArgument.event} } might be silently ignored" ,
201+ WarningType .ProcessingResultNotMatch ,
202+ " Recorded (${record.processingResult} ) and actual ($actualResult ) processing results does not match" ,
200203 )
201- } else {
202- warnings + = RestorationWarningException (
203- WarningType .ProcessingResultNotMatch ,
204- " Recorded (${record.processingResult} ) and actual ($actualResult ) processing results does not match" ,
205- )
204+ }
206205 }
206+ results + = RestoredEventResult (record, processingResult, warnings)
207207 }
208- results + = RestoredEventResult (record, processingResult, warnings)
209208 }
210209 }
211210 RestorationResult (results).also {
212211 validator.validate(it)
213212 }
214213}
215214
216- /* *
217- * Blocking [restoreRunningMachineByRecordedEvents] alternative
218- */
219- fun StateMachine.restoreRunningMachineByRecordedEventsBlocking (
220- recordedEvents : RecordedEvents ,
221- muteListeners : Boolean = true,
222- disableStructureHashCodeCheck : Boolean = false,
223- ): RestorationResult {
224- return coroutineAbstraction.runBlocking {
225- restoreRunningMachineByRecordedEvents(recordedEvents, muteListeners, disableStructureHashCodeCheck)
226- }
227- }
228-
229215/* *
230216 * Blocking [restoreByRecordedEvents] alternative
231217 */
0 commit comments