1use serde::{Deserialize, Serialize};
8
9use crate::ids::ExecutionPolicyId;
10use crate::limits::DESCRIPTOR_V1;
11use crate::optimization::OptimizationPolicy;
12use crate::schema;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
15#[serde(deny_unknown_fields)]
16pub struct ExecutionPolicy {
17 pub schema: String,
18 pub max_attempts_per_decision: u64,
21 pub cost: CostPolicy,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub optimization: Option<OptimizationPolicy>,
26 pub steps: Vec<StepExecution>,
29}
30
31crate::document::document!(
32 ExecutionPolicy,
33 schema::EXECUTION,
34 DESCRIPTOR_V1,
35 ExecutionPolicyId
36);
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
41#[serde(rename_all = "snake_case", deny_unknown_fields)]
42pub enum CostPolicy {
43 Unlimited,
45 Hard { max_units: u64 },
48 Estimated { max_units: u64 },
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
54#[serde(deny_unknown_fields)]
55pub struct StepExecution {
56 pub step: String,
57 pub retry: RetryPolicy,
58 pub attempt_timeout: AttemptTimeout,
59 pub fallback: FallbackPolicy,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
64#[serde(deny_unknown_fields)]
65pub struct RetryPolicy {
66 pub max_attempts: u32,
68 pub on: Vec<FailureClass>,
71 pub delay: Delay,
72}
73
74impl RetryPolicy {
75 pub fn none() -> Self {
77 RetryPolicy {
78 max_attempts: 1,
79 on: vec![],
80 delay: Delay::None,
81 }
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
87#[serde(rename_all = "snake_case", deny_unknown_fields)]
88pub enum Delay {
89 None,
90 Fixed {
91 ms: u64,
92 },
93 Exponential {
95 initial_ms: u64,
96 max_ms: u64,
97 },
98}
99
100impl Delay {
101 pub fn before_retry(self, n: u32) -> u64 {
103 match self {
104 Delay::None => 0,
105 Delay::Fixed { ms } => ms,
106 Delay::Exponential { initial_ms, max_ms } => {
107 let shift = n.saturating_sub(1).min(63);
108 initial_ms.saturating_mul(1u64 << shift).min(max_ms)
109 }
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
115#[serde(rename_all = "snake_case", deny_unknown_fields)]
116pub enum AttemptTimeout {
117 None,
118 Ms(u64),
119}
120
121#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
124#[serde(deny_unknown_fields)]
125pub struct FallbackPolicy {
126 pub backends: Vec<String>,
129 pub on: Vec<FailureClass>,
131}
132
133impl FallbackPolicy {
134 pub fn none() -> Self {
135 FallbackPolicy {
136 backends: vec![],
137 on: vec![],
138 }
139 }
140}
141
142#[derive(
146 Debug,
147 Clone,
148 Copy,
149 PartialEq,
150 Eq,
151 PartialOrd,
152 Ord,
153 Hash,
154 Serialize,
155 Deserialize,
156 schemars::JsonSchema,
157)]
158#[serde(rename_all = "snake_case")]
159pub enum FailureClass {
160 InvalidOutput,
162 Transient,
164 Overloaded,
166 Permanent,
168 TimedOut,
170 AdapterFault,
172}
173
174impl FailureClass {
175 pub fn retryable(self) -> bool {
177 matches!(
178 self,
179 FailureClass::Transient | FailureClass::Overloaded | FailureClass::TimedOut
180 )
181 }
182
183 pub fn name(self) -> &'static str {
184 match self {
185 FailureClass::InvalidOutput => "invalid_output",
186 FailureClass::Transient => "transient",
187 FailureClass::Overloaded => "overloaded",
188 FailureClass::Permanent => "permanent",
189 FailureClass::TimedOut => "timed_out",
190 FailureClass::AdapterFault => "adapter_fault",
191 }
192 }
193}
194
195pub const MAX_ATTEMPTS_PER_TARGET: u32 = 8;
197pub const MAX_FALLBACKS: usize = 4;
198pub const MAX_DELAY_MS: u64 = 60_000;