Skip to main content

execution_policy/
policy.rs

1//! The hero type: a reusable, cheaply-cloneable reliability policy.
2
3use std::sync::Arc;
4
5use crate::attempt::Attempt;
6use crate::builder::ExecutionPolicyBuilder;
7use crate::core::Core;
8use crate::engine::run_pipeline;
9use crate::error::ExecutionError;
10use crate::plan::Plan;
11
12/// A reusable reliability policy. Cheap to clone (shares a compiled `Plan`).
13pub struct ExecutionPolicy<C, T, E> {
14    core: C,
15    plan: Arc<Plan<T, E>>,
16}
17
18impl<C: std::fmt::Debug, T, E> std::fmt::Debug for ExecutionPolicy<C, T, E> {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        f.debug_struct("ExecutionPolicy")
21            .field("core", &self.core)
22            .field("plan", &self.plan)
23            .finish()
24    }
25}
26
27impl<C: Clone, T, E> Clone for ExecutionPolicy<C, T, E> {
28    fn clone(&self) -> Self {
29        Self {
30            core: self.core.clone(),
31            plan: Arc::clone(&self.plan),
32        }
33    }
34}
35
36impl ExecutionPolicy<(), (), ()> {
37    /// Start configuring a policy. The `T`/`E` types are inferred at the first
38    /// `retry(..)` or execute call site.
39    pub fn builder<T, E>() -> ExecutionPolicyBuilder<T, E> {
40        ExecutionPolicyBuilder::new()
41    }
42}
43
44impl<C, T, E> ExecutionPolicy<C, T, E> {
45    pub(crate) fn from_parts(core: C, plan: Arc<Plan<T, E>>) -> Self {
46        Self { core, plan }
47    }
48
49    /// Current circuit-breaker state, or `None` if no breaker is configured.
50    pub fn circuit_state(&self) -> Option<crate::error::BreakerState> {
51        self.plan.breaker.as_ref().map(|b| b.runtime.state())
52    }
53}
54
55impl<C, T, E> ExecutionPolicy<C, T, E>
56where
57    C: Core,
58{
59    /// Run an operation that needs neither application state nor attempt metadata.
60    pub async fn run<F>(&self, mut op: F) -> Result<T, ExecutionError<E>>
61    where
62        F: AsyncFnMut() -> Result<T, E>,
63    {
64        run_pipeline(
65            &self.core,
66            &self.plan,
67            async move |_attempt: Attempt<'_>| op().await,
68        )
69        .await
70    }
71
72    /// Run an operation that wants attempt metadata.
73    pub async fn execute<F>(&self, op: F) -> Result<T, ExecutionError<E>>
74    where
75        F: AsyncFnMut(Attempt<'_>) -> Result<T, E>,
76    {
77        run_pipeline(&self.core, &self.plan, op).await
78    }
79
80    /// Run an operation with injected application state.
81    pub async fn run_with<S, F>(&self, state: &S, mut op: F) -> Result<T, ExecutionError<E>>
82    where
83        S: Sync + ?Sized,
84        F: AsyncFnMut(&S) -> Result<T, E>,
85    {
86        run_pipeline(
87            &self.core,
88            &self.plan,
89            async move |_attempt: Attempt<'_>| op(state).await,
90        )
91        .await
92    }
93
94    /// Run an operation with injected state and attempt metadata.
95    pub async fn execute_with<S, F>(&self, state: &S, mut op: F) -> Result<T, ExecutionError<E>>
96    where
97        S: Sync + ?Sized,
98        F: AsyncFnMut(&S, Attempt<'_>) -> Result<T, E>,
99    {
100        run_pipeline(&self.core, &self.plan, async move |attempt: Attempt<'_>| {
101            op(state, attempt).await
102        })
103        .await
104    }
105}