Skip to content

Commit e7c6786

Browse files
committed
Add success with no results state transition object
This state transition object handles cases where the transition either succeeds with a final result that ends the session, or hits a static condition and stays in the same state. An example of this type of state transition would be the sender's process_res on the get request typestate.
1 parent 9f403f1 commit e7c6786

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed

payjoin/src/persist.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,56 @@ impl<V: Value> Persister<V> for NoopPersister {
6060
fn load(&self, token: Self::Token) -> Result<V, Self::Error> { Ok(token.0) }
6161
}
6262

63+
/// Handles cases where the transition either succeeds with a final result that ends the session, or hits a static condition and stays in the same state.
64+
/// State transition may also be a fatal error or transient error.
65+
pub struct MaybeSuccessTransitionWithNoResults<Event, SuccessValue, CurrentState, Err>(
66+
Result<AcceptOptionalTransition<Event, SuccessValue, CurrentState>, Rejection<Event, Err>>,
67+
);
68+
69+
impl<Event, SuccessValue, CurrentState, Err>
70+
MaybeSuccessTransitionWithNoResults<Event, SuccessValue, CurrentState, Err>
71+
{
72+
#[allow(dead_code)]
73+
#[inline]
74+
pub(crate) fn fatal(event: Event, error: Err) -> Self {
75+
MaybeSuccessTransitionWithNoResults(Err(Rejection::fatal(event, error)))
76+
}
77+
78+
#[allow(dead_code)]
79+
#[inline]
80+
pub(crate) fn transient(error: Err) -> Self {
81+
MaybeSuccessTransitionWithNoResults(Err(Rejection::transient(error)))
82+
}
83+
84+
#[allow(dead_code)]
85+
#[inline]
86+
pub(crate) fn no_results(current_state: CurrentState) -> Self {
87+
MaybeSuccessTransitionWithNoResults(Ok(AcceptOptionalTransition::NoResults(current_state)))
88+
}
89+
90+
#[allow(dead_code)]
91+
#[inline]
92+
pub(crate) fn success(success_value: SuccessValue, event: Event) -> Self {
93+
MaybeSuccessTransitionWithNoResults(Ok(AcceptOptionalTransition::Success(AcceptNextState(
94+
event,
95+
success_value,
96+
))))
97+
}
98+
99+
pub fn save<P>(
100+
self,
101+
persister: &P,
102+
) -> Result<
103+
OptionalTransitionOutcome<SuccessValue, CurrentState>,
104+
PersistedError<Err, P::InternalStorageError>,
105+
>
106+
where
107+
P: SessionPersister<SessionEvent = Event>,
108+
Err: std::error::Error,
109+
{
110+
persister.save_maybe_no_results_success_transition(self)
111+
}
112+
}
63113
/// A transition that can result in a state transition, fatal error, transient error, or successfully have no results.
64114
pub struct MaybeFatalTransitionWithNoResults<Event, NextState, CurrentState, Err>(
65115
Result<AcceptOptionalTransition<Event, NextState, CurrentState>, Rejection<Event, Err>>,
@@ -465,6 +515,42 @@ trait InternalSessionPersister: SessionPersister {
465515
}
466516
}
467517

518+
/// Persists the outcome of a state transition that may result in one of the following:
519+
/// - A successful state transition, in which case the success value is returned and the session is closed.
520+
/// - No state change (stasis), where the current state is retained and nothing is persisted.
521+
/// - A transient error, which does not affect persistent storage and is returned to the caller.
522+
/// - A fatal error, which is persisted and returned to the caller.
523+
fn save_maybe_no_results_success_transition<SuccessValue, CurrentState, Err>(
524+
&self,
525+
state_transition: MaybeSuccessTransitionWithNoResults<
526+
Self::SessionEvent,
527+
SuccessValue,
528+
CurrentState,
529+
Err,
530+
>,
531+
) -> Result<
532+
OptionalTransitionOutcome<SuccessValue, CurrentState>,
533+
PersistedError<Err, Self::InternalStorageError>,
534+
>
535+
where
536+
Err: std::error::Error,
537+
{
538+
match state_transition.0 {
539+
Ok(AcceptOptionalTransition::Success(AcceptNextState(event, success_value))) => {
540+
self.save_event(&event).map_err(InternalPersistedError::Storage)?;
541+
self.close().map_err(InternalPersistedError::Storage)?;
542+
Ok(OptionalTransitionOutcome::Progress(success_value))
543+
}
544+
Ok(AcceptOptionalTransition::NoResults(current_state)) =>
545+
Ok(OptionalTransitionOutcome::Stasis(current_state)),
546+
Err(Rejection::Fatal(fatal_rejection)) => {
547+
self.handle_fatal_reject(&fatal_rejection)?;
548+
Err(InternalPersistedError::Fatal(fatal_rejection.1).into())
549+
}
550+
Err(Rejection::Transient(RejectTransient(err))) =>
551+
Err(InternalPersistedError::Transient(err).into()),
552+
}
553+
}
468554
/// Save a transition that can result in:
469555
/// - A successful state transition
470556
/// - No state change (no results)
@@ -925,6 +1011,84 @@ mod tests {
9251011
}
9261012
}
9271013

1014+
#[test]
1015+
fn test_maybe_success_transition_with_no_results() {
1016+
let event = InMemoryTestEvent("foo".to_string());
1017+
let error_event = InMemoryTestEvent("error event".to_string());
1018+
let current_state = "Current state".to_string();
1019+
let success_value = "Success value".to_string();
1020+
let test_cases: Vec<
1021+
TestCase<
1022+
OptionalTransitionOutcome<InMemoryTestState, InMemoryTestState>,
1023+
PersistedError<InMemoryTestError, std::convert::Infallible>,
1024+
>,
1025+
> = vec![
1026+
// Success
1027+
TestCase {
1028+
expected_result: ExpectedResult {
1029+
events: vec![event.clone()],
1030+
is_closed: true,
1031+
error: None,
1032+
success: Some(OptionalTransitionOutcome::Progress(success_value.clone())),
1033+
},
1034+
test: Box::new(move |persister| {
1035+
MaybeSuccessTransitionWithNoResults::success(
1036+
success_value.clone(),
1037+
event.clone(),
1038+
)
1039+
.save(persister)
1040+
}),
1041+
},
1042+
// No results
1043+
TestCase {
1044+
expected_result: ExpectedResult {
1045+
events: vec![],
1046+
is_closed: false,
1047+
error: None,
1048+
success: Some(OptionalTransitionOutcome::Stasis(current_state.clone())),
1049+
},
1050+
test: Box::new(move |persister| {
1051+
MaybeSuccessTransitionWithNoResults::no_results(current_state.clone())
1052+
.save(persister)
1053+
}),
1054+
},
1055+
// Transient error
1056+
TestCase {
1057+
expected_result: ExpectedResult {
1058+
events: vec![],
1059+
is_closed: false,
1060+
error: Some(InternalPersistedError::Transient(InMemoryTestError {}).into()),
1061+
success: None,
1062+
},
1063+
test: Box::new(move |persister| {
1064+
MaybeSuccessTransitionWithNoResults::transient(InMemoryTestError {})
1065+
.save(persister)
1066+
}),
1067+
},
1068+
// Fatal error
1069+
TestCase {
1070+
expected_result: ExpectedResult {
1071+
events: vec![error_event.clone()],
1072+
is_closed: true,
1073+
error: Some(InternalPersistedError::Fatal(InMemoryTestError {}).into()),
1074+
success: None,
1075+
},
1076+
test: Box::new(move |persister| {
1077+
MaybeSuccessTransitionWithNoResults::fatal(
1078+
error_event.clone(),
1079+
InMemoryTestError {},
1080+
)
1081+
.save(persister)
1082+
}),
1083+
},
1084+
];
1085+
1086+
for test in test_cases {
1087+
let persister = InMemoryTestPersister::default();
1088+
do_test(&persister, &test);
1089+
}
1090+
}
1091+
9281092
#[test]
9291093
fn test_maybe_fatal_transition_with_no_results() {
9301094
let event = InMemoryTestEvent("foo".to_string());

0 commit comments

Comments
 (0)