@@ -5,6 +5,7 @@ import ru.nsk.kstatemachine.event.StartEvent
55import ru.nsk.kstatemachine.event.StopEvent
66import ru.nsk.kstatemachine.statemachine.*
77import ru.nsk.kstatemachine.statemachine.StateMachine.EventRecordingArguments
8+ import ru.nsk.kstatemachine.statemachine.StateMachine.PendingEventHandler
89import ru.nsk.kstatemachine.transition.EventAndArgument
910import ru.nsk.kstatemachine.visitors.structureHashCode
1011
@@ -56,7 +57,8 @@ internal class EventRecorderImpl(
5657}
5758
5859data class RestorationResult (
59- val results : List <RestoredEventResult >
60+ val results : List <RestoredEventResult >,
61+ val warnings : List <RestorationWarningException >,
6062)
6163
6264data class RestoredEventResult (
@@ -69,30 +71,35 @@ fun interface RestorationResultValidator {
6971 /* *
7072 * Throws if validation is not passed
7173 */
72- fun validate (result : RestorationResult )
74+ fun validate (result : RestorationResult , recordedEvents : RecordedEvents , machine : StateMachine )
7375}
7476
7577/* *
7678 * Completely skips validation
7779 */
7880object EmptyValidator : RestorationResultValidator {
79- override fun validate (result : RestorationResult ) = Unit
81+ override fun validate (result : RestorationResult , recordedEvents : RecordedEvents , machine : StateMachine ) = Unit
8082}
8183
8284/* *
8385 * Does not allow warnings or failed processing results
8486 */
8587object StrictValidator : RestorationResultValidator {
86- override fun validate (result : RestorationResult ) {
88+ override fun validate (result : RestorationResult , recordedEvents : RecordedEvents , machine : StateMachine ) {
89+ if (result.warnings.isNotEmpty())
90+ throw RestorationResultValidationException (
91+ result,
92+ " The ${RestorationResult ::class .simpleName} contains warnings" ,
93+ result.warnings.first(),
94+ )
8795 result.results.forEach {
8896 if (it.warnings.isNotEmpty()) {
8997 throw RestorationResultValidationException (
9098 result,
9199 " The ${RestorationResult ::class .simpleName} contains warnings" ,
92100 it.warnings.first(),
93101 )
94- }
95- if (it.processingResult.isFailure) {
102+ } else if (it.processingResult.isFailure) {
96103 throw RestorationResultValidationException (
97104 result,
98105 " The ${RestorationResult ::class .simpleName} contains failed processing result" ,
@@ -111,7 +118,7 @@ class RestorationResultValidationException(
111118
112119enum class WarningType {
113120 ProcessingResultNotMatch ,
114- PendingEventMightBeIgnored ,
121+ RecordedAndProcessedEventCountNotMatch ,
115122}
116123
117124class RestorationWarningException (
@@ -158,6 +165,7 @@ suspend fun StateMachine.restoreByRecordedEvents(
158165
159166 this as InternalStateMachine
160167 val results = mutableListOf<RestoredEventResult >()
168+ val commonWarnings = mutableListOf<RestorationWarningException >()
161169 val mutationSection = if (muteListeners) openListenersMutationSection() else EmptyListenersMutationSection
162170 mutationSection.use {
163171 recordedEvents.records.forEachIndexed iteration@{ index, record ->
@@ -166,6 +174,7 @@ suspend fun StateMachine.restoreByRecordedEvents(
166174 if (event is StartEvent ) {
167175 if (isRunning) {
168176 if (argument == null ) {
177+ results + = RestoredEventResult (record, Result .success(ProcessingResult .PROCESSED ), warnings)
169178 return @iteration // continue
170179 } else {
171180 if (index == 0 )
@@ -176,39 +185,36 @@ suspend fun StateMachine.restoreByRecordedEvents(
176185 " To restore such machine, " +
177186 " do not start it before calling ${::restoreByRecordedEvents.name} "
178187 )
179- else
188+ else {
189+ destroy()
180190 error(" The machine should not be running here. Internal error. Never get here" )
191+ }
181192 }
182193 } else {
183194 start(argument)
184- // fixme add RestoredEventResult?
195+ results + = RestoredEventResult (record, Result .success( ProcessingResult . PROCESSED ), warnings)
185196 }
186197 } else {
187198 val processingResult = runCatching { processEvent(event, argument) }
188199 val actualResult = processingResult.getOrNull()
189200 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 {
200- warnings + = RestorationWarningException (
201- WarningType .ProcessingResultNotMatch ,
202- " Recorded (${record.processingResult} ) and actual ($actualResult ) processing results does not match" ,
203- )
204- }
201+ warnings + = RestorationWarningException (
202+ WarningType .ProcessingResultNotMatch ,
203+ " Recorded (${record.processingResult} ) and actual ($actualResult ) processing results does not match" ,
204+ )
205205 }
206206 results + = RestoredEventResult (record, processingResult, warnings)
207207 }
208208 }
209209 }
210- RestorationResult (results).also {
211- validator.validate(it)
210+ if (results.size != recordedEvents.records.size)
211+ commonWarnings + = RestorationWarningException (
212+ WarningType .RecordedAndProcessedEventCountNotMatch ,
213+ " Recorded event count is ${recordedEvents.records.size} but the actual processed event count is " +
214+ " ${results.size} . They should not differ, this should never happen" ,
215+ )
216+ RestorationResult (results, commonWarnings).also {
217+ validator.validate(it, recordedEvents, this )
212218 }
213219}
214220
0 commit comments