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