cratestack_client_rust/step_up/retry.rs
1//! Retry a call once after a re-proof (cratestack#1244).
2
3use std::future::{Future, IntoFuture};
4
5use super::{StepUpError, StepUpRequired};
6
7/// Run `call` (a future, or anything `.await` takes, such as a generated
8/// RPC method's call); when it fails with a step-up refusal, hand the refusal to
9/// `reprove` and, when that answers `true` (the caller authenticated
10/// again), run `call` **once** more and return what it gives, even a
11/// second step-up refusal. Never loops.
12///
13/// `reprove` answers `false` when the user declined or the re-proof
14/// failed; the first refusal is then returned unchanged. Any other error,
15/// and a success, is returned from the first call as it is.
16///
17/// ```text
18/// let order = retry_after_step_up(
19/// || client.procedures().transfer(&args),
20/// |step_up| async move { session.reauthenticate(step_up.max_age).await.is_ok() },
21/// )
22/// .await?;
23/// ```
24pub async fn retry_after_step_up<T, E, F, Fut, R, RFut>(mut call: F, reprove: R) -> Result<T, E>
25where
26 E: StepUpError,
27 F: FnMut() -> Fut,
28 Fut: IntoFuture<Output = Result<T, E>>,
29 R: FnOnce(StepUpRequired) -> RFut,
30 RFut: Future<Output = bool>,
31{
32 let error = match call().await {
33 Ok(value) => return Ok(value),
34 Err(error) => error,
35 };
36 let Some(step_up) = error.step_up() else {
37 return Err(error);
38 };
39 if !reprove(step_up).await {
40 return Err(error);
41 }
42 call().await
43}