1use crate::RouteBuilder;
17use camel_api::error_handler::ExceptionDisposition;
18use camel_api::{BoxProcessor, CamelError, FilterPredicate, OpaqueProcessor};
19use camel_core::route::BuilderStep;
20use camel_processor::{CatchClause, CatchMatcher, DoTryService};
21
22pub struct DoTryBuilder {
26 parent: RouteBuilder,
27 try_steps: Vec<BoxProcessor>,
28 catch_clauses: Vec<CatchClause>,
29 finally_steps: Vec<BoxProcessor>,
30 finally_on_when: Option<FilterPredicate>,
31 finally_set: bool,
32}
33
34pub struct DoCatchBuilder {
36 parent: DoTryBuilder,
37 matcher: CatchMatcher,
38 on_when: Option<FilterPredicate>,
39 steps: Vec<BoxProcessor>,
40 disposition: ExceptionDisposition,
41}
42
43pub struct DoFinallyBuilder {
45 parent: DoTryBuilder,
46 steps: Vec<BoxProcessor>,
47 on_when: Option<FilterPredicate>,
48}
49
50impl RouteBuilder {
51 pub fn do_try(self) -> DoTryBuilder {
53 DoTryBuilder {
54 parent: self,
55 try_steps: Vec::new(),
56 catch_clauses: Vec::new(),
57 finally_steps: Vec::new(),
58 finally_on_when: None,
59 finally_set: false,
60 }
61 }
62}
63
64impl DoTryBuilder {
65 pub fn process(mut self, processor: BoxProcessor) -> Self {
67 self.try_steps.push(processor);
68 self
69 }
70
71 pub fn do_catch_exception(self, variants: &[&str]) -> DoCatchBuilder {
75 DoCatchBuilder {
76 parent: self,
77 matcher: CatchMatcher::ByVariant(variants.iter().map(|s| (*s).to_string()).collect()),
78 on_when: None,
79 steps: Vec::new(),
80 disposition: ExceptionDisposition::Handled,
81 }
82 }
83
84 pub fn do_catch_when(self, predicate: FilterPredicate) -> DoCatchBuilder {
86 DoCatchBuilder {
87 parent: self,
88 matcher: CatchMatcher::Predicate(predicate),
89 on_when: None,
90 steps: Vec::new(),
91 disposition: ExceptionDisposition::Handled,
92 }
93 }
94
95 pub fn do_catch_all(self) -> DoCatchBuilder {
97 self.do_catch_exception(&["*"])
98 }
99
100 pub fn do_finally(self) -> Result<DoFinallyBuilder, CamelError> {
107 if self.finally_set {
108 return Err(CamelError::RouteError(
109 "do_finally can only be called once per do_try scope".into(),
110 ));
111 }
112 Ok(DoFinallyBuilder {
113 parent: self,
114 steps: Vec::new(),
115 on_when: None,
116 })
117 }
118
119 pub fn end_do_try(self) -> RouteBuilder {
121 let do_try = DoTryService {
122 try_steps: self.try_steps,
123 catch_clauses: self.catch_clauses,
124 finally_steps: self.finally_steps,
125 finally_on_when: self.finally_on_when,
126 };
127 let mut parent = self.parent;
128 parent
129 .steps
130 .push(BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(
131 do_try,
132 ))));
133 parent
134 }
135}
136
137impl DoCatchBuilder {
138 pub fn process(mut self, processor: BoxProcessor) -> Self {
140 self.steps.push(processor);
141 self
142 }
143
144 pub fn on_when(mut self, predicate: FilterPredicate) -> Self {
146 self.on_when = Some(predicate);
147 self
148 }
149
150 pub fn handled(mut self) -> Self {
176 self.disposition = ExceptionDisposition::Handled;
177 self
178 }
179
180 pub fn propagate(mut self) -> Self {
188 self.disposition = ExceptionDisposition::Propagate;
189 self
190 }
191
192 pub fn end_do_catch(self) -> DoTryBuilder {
194 let mut parent = self.parent;
195 parent.catch_clauses.push(CatchClause {
196 matcher: self.matcher,
197 on_when: self.on_when,
198 steps: self.steps,
199 disposition: self.disposition,
200 });
201 parent
202 }
203}
204
205impl DoFinallyBuilder {
206 pub fn process(mut self, processor: BoxProcessor) -> Self {
208 self.steps.push(processor);
209 self
210 }
211
212 pub fn on_when(mut self, predicate: FilterPredicate) -> Self {
214 self.on_when = Some(predicate);
215 self
216 }
217
218 pub fn end_do_finally(self) -> DoTryBuilder {
220 let mut parent = self.parent;
221 parent.finally_set = true;
222 parent.finally_on_when = self.on_when;
223 parent.finally_steps = self.steps;
224 parent
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use crate::RouteBuilder;
231 use camel_api::error_handler::ExceptionDisposition;
232 use camel_api::{BoxProcessor, BoxProcessorExt, CamelError};
233 use camel_core::route::BuilderStep;
234
235 fn passthrough() -> BoxProcessor {
236 BoxProcessor::from_fn(move |ex| Box::pin(async move { Ok(ex) }))
237 }
238
239 #[test]
240 fn do_try_builder_assembles_correct_shape() {
241 let route = RouteBuilder::from("direct:start")
242 .route_id("do-try-shape")
243 .do_try()
244 .process(passthrough())
245 .do_catch_exception(&["ProcessorError"])
246 .handled()
247 .process(passthrough())
248 .end_do_catch()
249 .do_finally()
250 .unwrap()
251 .process(passthrough())
252 .end_do_finally()
253 .end_do_try();
254
255 let config = route.build().unwrap();
256 assert_eq!(
257 config.steps().len(),
258 1,
259 "expected exactly one step (the DoTryService)"
260 );
261 assert!(
262 matches!(config.steps().first(), Some(BuilderStep::Processor(_))),
263 "the single step must be a Processor variant (the DoTryService)"
264 );
265 }
266
267 #[test]
268 fn do_try_builder_disposition_sugar_methods() {
269 let catch = RouteBuilder::from("direct:a")
271 .route_id("do-try-sugar-a")
272 .do_try()
273 .process(passthrough())
274 .do_catch_exception(&["Io"])
275 .handled();
276 assert_eq!(catch.disposition, ExceptionDisposition::Handled);
277 let _ = catch.end_do_catch().end_do_try().build().unwrap();
278
279 let catch = RouteBuilder::from("direct:b")
281 .route_id("do-try-sugar-b")
282 .do_try()
283 .process(passthrough())
284 .do_catch_exception(&["Io"])
285 .propagate();
286 assert_eq!(catch.disposition, ExceptionDisposition::Propagate);
287 let _ = catch.end_do_catch().end_do_try().build().unwrap();
288 }
289
290 #[test]
291 fn do_finally_called_twice_returns_err() {
292 let result = RouteBuilder::from("direct:start")
293 .route_id("do-try-double-finally")
294 .do_try()
295 .process(passthrough())
296 .do_finally()
297 .unwrap()
298 .process(passthrough())
299 .end_do_finally()
300 .do_finally();
301 match result {
302 Err(CamelError::RouteError(msg)) => {
303 assert!(
304 msg.contains("do_finally can only be called once"),
305 "unexpected message: {msg}"
306 );
307 }
308 _ => panic!("expected Err(RouteError)"),
309 }
310 }
311}