use crate::RouteBuilder;
use camel_api::error_handler::ExceptionDisposition;
use camel_api::{BoxProcessor, CamelError, FilterPredicate, OpaqueProcessor};
use camel_core::route::BuilderStep;
use camel_processor::{CatchClause, CatchMatcher, DoTryService};
pub struct DoTryBuilder {
parent: RouteBuilder,
try_steps: Vec<BoxProcessor>,
catch_clauses: Vec<CatchClause>,
finally_steps: Vec<BoxProcessor>,
finally_on_when: Option<FilterPredicate>,
finally_set: bool,
}
pub struct DoCatchBuilder {
parent: DoTryBuilder,
matcher: CatchMatcher,
on_when: Option<FilterPredicate>,
steps: Vec<BoxProcessor>,
disposition: ExceptionDisposition,
}
pub struct DoFinallyBuilder {
parent: DoTryBuilder,
steps: Vec<BoxProcessor>,
on_when: Option<FilterPredicate>,
}
impl RouteBuilder {
pub fn do_try(self) -> DoTryBuilder {
DoTryBuilder {
parent: self,
try_steps: Vec::new(),
catch_clauses: Vec::new(),
finally_steps: Vec::new(),
finally_on_when: None,
finally_set: false,
}
}
}
impl DoTryBuilder {
pub fn process(mut self, processor: BoxProcessor) -> Self {
self.try_steps.push(processor);
self
}
pub fn do_catch_exception(self, variants: &[&str]) -> DoCatchBuilder {
DoCatchBuilder {
parent: self,
matcher: CatchMatcher::ByVariant(variants.iter().map(|s| (*s).to_string()).collect()),
on_when: None,
steps: Vec::new(),
disposition: ExceptionDisposition::Handled,
}
}
pub fn do_catch_when(self, predicate: FilterPredicate) -> DoCatchBuilder {
DoCatchBuilder {
parent: self,
matcher: CatchMatcher::Predicate(predicate),
on_when: None,
steps: Vec::new(),
disposition: ExceptionDisposition::Handled,
}
}
pub fn do_catch_all(self) -> DoCatchBuilder {
self.do_catch_exception(&["*"])
}
pub fn do_finally(self) -> Result<DoFinallyBuilder, CamelError> {
if self.finally_set {
return Err(CamelError::RouteError(
"do_finally can only be called once per do_try scope".into(),
));
}
Ok(DoFinallyBuilder {
parent: self,
steps: Vec::new(),
on_when: None,
})
}
pub fn end_do_try(self) -> RouteBuilder {
let do_try = DoTryService {
try_steps: self.try_steps,
catch_clauses: self.catch_clauses,
finally_steps: self.finally_steps,
finally_on_when: self.finally_on_when,
};
let mut parent = self.parent;
parent
.steps
.push(BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(
do_try,
))));
parent
}
}
impl DoCatchBuilder {
pub fn process(mut self, processor: BoxProcessor) -> Self {
self.steps.push(processor);
self
}
pub fn on_when(mut self, predicate: FilterPredicate) -> Self {
self.on_when = Some(predicate);
self
}
pub fn handled(mut self) -> Self {
self.disposition = ExceptionDisposition::Handled;
self
}
pub fn propagate(mut self) -> Self {
self.disposition = ExceptionDisposition::Propagate;
self
}
pub fn end_do_catch(self) -> DoTryBuilder {
let mut parent = self.parent;
parent.catch_clauses.push(CatchClause {
matcher: self.matcher,
on_when: self.on_when,
steps: self.steps,
disposition: self.disposition,
});
parent
}
}
impl DoFinallyBuilder {
pub fn process(mut self, processor: BoxProcessor) -> Self {
self.steps.push(processor);
self
}
pub fn on_when(mut self, predicate: FilterPredicate) -> Self {
self.on_when = Some(predicate);
self
}
pub fn end_do_finally(self) -> DoTryBuilder {
let mut parent = self.parent;
parent.finally_set = true;
parent.finally_on_when = self.on_when;
parent.finally_steps = self.steps;
parent
}
}
#[cfg(test)]
mod tests {
use crate::RouteBuilder;
use camel_api::error_handler::ExceptionDisposition;
use camel_api::{BoxProcessor, BoxProcessorExt, CamelError};
use camel_core::route::BuilderStep;
fn passthrough() -> BoxProcessor {
BoxProcessor::from_fn(move |ex| Box::pin(async move { Ok(ex) }))
}
#[test]
fn do_try_builder_assembles_correct_shape() {
let route = RouteBuilder::from("direct:start")
.route_id("do-try-shape")
.do_try()
.process(passthrough())
.do_catch_exception(&["ProcessorError"])
.handled()
.process(passthrough())
.end_do_catch()
.do_finally()
.unwrap()
.process(passthrough())
.end_do_finally()
.end_do_try();
let config = route.build().unwrap();
assert_eq!(
config.steps().len(),
1,
"expected exactly one step (the DoTryService)"
);
assert!(
matches!(config.steps().first(), Some(BuilderStep::Processor(_))),
"the single step must be a Processor variant (the DoTryService)"
);
}
#[test]
fn do_try_builder_disposition_sugar_methods() {
let catch = RouteBuilder::from("direct:a")
.route_id("do-try-sugar-a")
.do_try()
.process(passthrough())
.do_catch_exception(&["Io"])
.handled();
assert_eq!(catch.disposition, ExceptionDisposition::Handled);
let _ = catch.end_do_catch().end_do_try().build().unwrap();
let catch = RouteBuilder::from("direct:b")
.route_id("do-try-sugar-b")
.do_try()
.process(passthrough())
.do_catch_exception(&["Io"])
.propagate();
assert_eq!(catch.disposition, ExceptionDisposition::Propagate);
let _ = catch.end_do_catch().end_do_try().build().unwrap();
}
#[test]
fn do_finally_called_twice_returns_err() {
let result = RouteBuilder::from("direct:start")
.route_id("do-try-double-finally")
.do_try()
.process(passthrough())
.do_finally()
.unwrap()
.process(passthrough())
.end_do_finally()
.do_finally();
match result {
Err(CamelError::RouteError(msg)) => {
assert!(
msg.contains("do_finally can only be called once"),
"unexpected message: {msg}"
);
}
_ => panic!("expected Err(RouteError)"),
}
}
}