changeset_saga/
builder.rs1use std::fmt::Debug;
2use std::marker::PhantomData;
3
4use crate::erased::{ErasedStep, StepWrapper};
5use crate::saga::Saga;
6use crate::step::SagaStep;
7
8pub struct Empty;
10
11pub struct HasSteps<LastOutput>(PhantomData<LastOutput>);
13
14pub struct SagaBuilder<Input, Output, Ctx, Err, State> {
68 steps: Vec<Box<dyn ErasedStep<Ctx, Err>>>,
69 _phantom: PhantomData<(Input, Output, State)>,
70}
71
72impl<Ctx, Err> SagaBuilder<(), (), Ctx, Err, Empty> {
73 #[must_use]
75 pub fn new() -> Self {
76 Self {
77 steps: Vec::new(),
78 _phantom: PhantomData,
79 }
80 }
81
82 #[must_use]
86 pub fn first_step<S>(
87 self,
88 step: S,
89 ) -> SagaBuilder<S::Input, S::Output, Ctx, Err, HasSteps<S::Output>>
90 where
91 S: SagaStep<Context = Ctx, Error = Err> + 'static,
92 {
93 let mut steps = self.steps;
94 steps.push(Box::new(StepWrapper::new(step)));
95 SagaBuilder {
96 steps,
97 _phantom: PhantomData,
98 }
99 }
100}
101
102impl<Ctx, Err> Default for SagaBuilder<(), (), Ctx, Err, Empty> {
103 fn default() -> Self {
104 Self::new()
105 }
106}
107
108impl<Input, CurrentOutput, Ctx, Err>
109 SagaBuilder<Input, CurrentOutput, Ctx, Err, HasSteps<CurrentOutput>>
110{
111 #[must_use]
115 pub fn then<S>(self, step: S) -> SagaBuilder<Input, S::Output, Ctx, Err, HasSteps<S::Output>>
116 where
117 S: SagaStep<Input = CurrentOutput, Context = Ctx, Error = Err> + 'static,
118 {
119 let mut steps = self.steps;
120 steps.push(Box::new(StepWrapper::new(step)));
121 SagaBuilder {
122 steps,
123 _phantom: PhantomData,
124 }
125 }
126
127 #[must_use]
129 pub fn build(self) -> Saga<Input, CurrentOutput, Ctx, Err>
130 where
131 Input: Clone + Send + 'static,
132 CurrentOutput: Send + 'static,
133 Err: Debug,
134 {
135 Saga::from_steps(self.steps)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 struct TestContext;
144
145 #[derive(Debug, PartialEq)]
146 struct TestError(String);
147
148 struct IntToString;
149
150 impl SagaStep for IntToString {
151 type Input = i32;
152 type Output = String;
153 type Context = TestContext;
154 type Error = TestError;
155
156 fn name(&self) -> &'static str {
157 "int_to_string"
158 }
159
160 fn execute(
161 &self,
162 _ctx: &Self::Context,
163 input: Self::Input,
164 ) -> Result<Self::Output, Self::Error> {
165 Ok(input.to_string())
166 }
167 }
168
169 struct StringToLen;
170
171 impl SagaStep for StringToLen {
172 type Input = String;
173 type Output = usize;
174 type Context = TestContext;
175 type Error = TestError;
176
177 fn name(&self) -> &'static str {
178 "string_to_len"
179 }
180
181 fn execute(
182 &self,
183 _ctx: &Self::Context,
184 input: Self::Input,
185 ) -> Result<Self::Output, Self::Error> {
186 Ok(input.len())
187 }
188 }
189
190 struct DoubleInt;
191
192 impl SagaStep for DoubleInt {
193 type Input = i32;
194 type Output = i32;
195 type Context = TestContext;
196 type Error = TestError;
197
198 fn name(&self) -> &'static str {
199 "double_int"
200 }
201
202 fn execute(
203 &self,
204 _ctx: &Self::Context,
205 input: Self::Input,
206 ) -> Result<Self::Output, Self::Error> {
207 Ok(input * 2)
208 }
209 }
210
211 #[test]
212 fn builder_creates_single_step_saga() {
213 let _saga: Saga<i32, String, TestContext, TestError> =
214 SagaBuilder::new().first_step(IntToString).build();
215 }
216
217 #[test]
218 fn builder_chains_steps_with_matching_types() {
219 let _saga: Saga<i32, usize, TestContext, TestError> = SagaBuilder::new()
220 .first_step(IntToString)
221 .then(StringToLen)
222 .build();
223 }
224
225 #[test]
226 fn builder_allows_multiple_steps_with_same_type() {
227 let _saga: Saga<i32, i32, TestContext, TestError> = SagaBuilder::new()
228 .first_step(DoubleInt)
229 .then(DoubleInt)
230 .then(DoubleInt)
231 .build();
232 }
233}