1use std::sync::Arc;
5
6use camel_api::UnitOfWorkConfig;
7use camel_api::circuit_breaker::CircuitBreakerConfig;
8use camel_api::error_handler::ErrorHandlerConfig;
9use camel_api::loop_eip::LoopConfig;
10use camel_api::security_policy::SecurityPolicyConfig;
11use camel_api::{
12 AggregatorConfig, FilterPredicate, MulticastConfig, OpaqueProcessor, ResequencePolicyConfig,
13 SplitterConfig,
14};
15use camel_auth::TokenAuthenticator;
16use camel_component_api::ConcurrencyModel;
17
18pub struct WhenStep {
20 pub predicate: FilterPredicate,
21 pub steps: Vec<BuilderStep>,
22}
23
24impl std::fmt::Debug for WhenStep {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.debug_struct("WhenStep")
27 .field("predicate", &self.predicate)
28 .field("steps", &self.steps)
29 .finish()
30 }
31}
32
33pub use camel_api::declarative::{LanguageExpressionDef, ValueSourceDef};
34
35#[derive(Debug)]
37pub struct DeclarativeWhenStep {
38 pub predicate: LanguageExpressionDef,
39 pub steps: Vec<BuilderStep>,
40}
41
42#[derive(Debug)]
44pub struct DoTryCatchClauseBuilder {
45 pub exception: Option<Vec<String>>,
46 pub when: Option<LanguageExpressionDef>,
47 pub on_when: Option<LanguageExpressionDef>,
48 pub disposition: camel_api::error_handler::ExceptionDisposition,
49 pub steps: Vec<BuilderStep>,
50}
51
52#[derive(Debug)]
54pub struct DoTryFinallyBuilder {
55 pub on_when: Option<LanguageExpressionDef>,
56 pub steps: Vec<BuilderStep>,
57}
58
59#[derive(Debug)]
61pub enum BuilderStep {
62 Processor(OpaqueProcessor),
64 To(String),
66 Stop,
68 Log {
70 level: camel_processor::LogLevel,
71 message: String,
72 },
73 DeclarativeSetHeader {
75 key: String,
76 value: ValueSourceDef,
77 },
78 DeclarativeSetHeaderIfAbsent {
80 key: String,
81 value: ValueSourceDef,
82 },
83 DeclarativeSetProperty {
84 key: String,
85 value_source: ValueSourceDef,
86 },
87 DeclarativeSetBody {
89 value: ValueSourceDef,
90 },
91 DeclarativeFilter {
93 predicate: LanguageExpressionDef,
94 steps: Vec<BuilderStep>,
95 },
96 DeclarativeChoice {
98 whens: Vec<DeclarativeWhenStep>,
99 otherwise: Option<Vec<BuilderStep>>,
100 },
101 DeclarativeScript {
103 expression: LanguageExpressionDef,
104 },
105 DeclarativeFunction {
106 definition: camel_api::FunctionDefinition,
107 },
108 DeclarativeSplit {
110 expression: LanguageExpressionDef,
111 aggregation: camel_api::splitter::AggregationStrategy,
112 parallel: bool,
113 parallel_limit: Option<usize>,
114 stop_on_exception: bool,
115 steps: Vec<BuilderStep>,
116 },
117 DeclarativeStreamSplit {
119 stream_config: camel_api::StreamSplitConfig,
120 aggregation: camel_api::splitter::AggregationStrategy,
121 stop_on_exception: bool,
122 steps: Vec<BuilderStep>,
123 },
124 DeclarativeDynamicRouter {
125 expression: LanguageExpressionDef,
126 uri_delimiter: String,
127 cache_size: i32,
128 ignore_invalid_endpoints: bool,
129 max_iterations: usize,
130 },
131 DeclarativeRoutingSlip {
132 expression: LanguageExpressionDef,
133 uri_delimiter: String,
134 cache_size: i32,
135 ignore_invalid_endpoints: bool,
136 },
137 Split {
139 config: SplitterConfig,
140 steps: Vec<BuilderStep>,
141 },
142 Aggregate {
144 config: AggregatorConfig,
145 },
146 Filter {
148 predicate: FilterPredicate,
149 steps: Vec<BuilderStep>,
150 },
151 Choice {
154 whens: Vec<WhenStep>,
155 otherwise: Option<Vec<BuilderStep>>,
156 },
157 WireTap {
159 uri: String,
160 },
161 Multicast {
163 steps: Vec<BuilderStep>,
164 config: MulticastConfig,
165 },
166 DeclarativeLog {
168 level: camel_processor::LogLevel,
169 message: ValueSourceDef,
170 },
171 Bean {
173 name: String,
174 method: String,
175 },
176 Script {
179 language: String,
180 script: String,
181 },
182 Throttle {
184 config: camel_api::ThrottlerConfig,
185 steps: Vec<BuilderStep>,
186 },
187 LoadBalance {
189 config: camel_api::LoadBalancerConfig,
190 steps: Vec<BuilderStep>,
191 },
192 DynamicRouter {
194 config: camel_api::DynamicRouterConfig,
195 },
196 RoutingSlip {
197 config: camel_api::RoutingSlipConfig,
198 },
199 RecipientList {
200 config: camel_api::recipient_list::RecipientListConfig,
201 },
202 DeclarativeRecipientList {
203 expression: LanguageExpressionDef,
204 delimiter: String,
205 parallel: bool,
206 parallel_limit: Option<usize>,
207 stop_on_exception: bool,
208 aggregation: String,
209 },
210 Delay {
211 config: camel_api::DelayConfig,
212 },
213 Loop {
215 config: LoopConfig,
216 steps: Vec<BuilderStep>,
217 },
218 DeclarativeLoop {
220 count: Option<usize>,
221 while_predicate: Option<LanguageExpressionDef>,
222 steps: Vec<BuilderStep>,
223 max_iterations: Option<usize>,
224 },
225 Enrich {
227 uri: String,
228 strategy: Option<String>,
229 timeout_ms: Option<u64>,
230 },
231 PollEnrich {
233 uri: String,
234 strategy: Option<String>,
235 timeout_ms: Option<u64>,
236 },
237 Validate {
240 predicate: LanguageExpressionDef,
241 },
242 ClaimCheck {
246 repository: String,
247 operation: String,
248 key: LanguageExpressionDef,
249 filter: Option<String>,
250 },
251 Sampling {
255 period: usize,
256 },
257 Sort {
260 expression: LanguageExpressionDef,
261 reverse: bool,
262 },
263 IdempotentConsumer {
267 repository: String,
268 expression: LanguageExpressionDef,
269 steps: Vec<BuilderStep>,
270 eager: bool,
271 remove_on_failure: bool,
272 },
273 DeclarativeDoTry {
275 try_steps: Vec<BuilderStep>,
276 catch: Vec<DoTryCatchClauseBuilder>,
277 finally: Option<DoTryFinallyBuilder>,
278 },
279 Resequence {
282 policy_config: ResequencePolicyConfig,
283 },
284}
285
286pub struct RouteDefinition {
288 pub(crate) from_uri: String,
289 pub(crate) steps: Vec<BuilderStep>,
290 pub(crate) error_handler: Option<ErrorHandlerConfig>,
292 pub(crate) circuit_breaker: Option<CircuitBreakerConfig>,
294 pub(crate) security_policy: Option<SecurityPolicyConfig>,
295 pub(crate) security_authenticator: Option<Arc<dyn TokenAuthenticator>>,
297 pub(crate) unit_of_work: Option<UnitOfWorkConfig>,
299 pub(crate) concurrency: Option<ConcurrencyModel>,
302 pub(crate) route_id: String,
304 pub(crate) auto_startup: bool,
306 pub(crate) startup_order: i32,
308 pub(crate) source_hash: Option<u64>,
309}
310
311impl RouteDefinition {
312 pub fn new(from_uri: impl Into<String>, steps: Vec<BuilderStep>) -> Self {
314 Self {
315 from_uri: from_uri.into(),
316 steps,
317 error_handler: None,
318 circuit_breaker: None,
319 security_policy: None,
320 security_authenticator: None,
321 unit_of_work: None,
322 concurrency: None,
323 route_id: String::new(), auto_startup: true,
325 startup_order: 1000,
326 source_hash: None,
327 }
328 }
329
330 pub fn from_uri(&self) -> &str {
332 &self.from_uri
333 }
334
335 pub fn steps(&self) -> &[BuilderStep] {
337 &self.steps
338 }
339
340 pub fn with_error_handler(mut self, config: ErrorHandlerConfig) -> Self {
342 self.error_handler = Some(config);
343 self
344 }
345
346 pub fn error_handler_config(&self) -> Option<&ErrorHandlerConfig> {
348 self.error_handler.as_ref()
349 }
350
351 pub fn with_circuit_breaker(mut self, config: CircuitBreakerConfig) -> Self {
353 self.circuit_breaker = Some(config);
354 self
355 }
356
357 pub fn with_security_policy(mut self, config: SecurityPolicyConfig) -> Self {
359 self.security_policy = Some(config);
360 self
361 }
362
363 pub fn with_security_authenticator(
365 mut self,
366 authenticator: Arc<dyn TokenAuthenticator>,
367 ) -> Self {
368 self.security_authenticator = Some(authenticator);
369 self
370 }
371
372 pub fn with_unit_of_work(mut self, config: UnitOfWorkConfig) -> Self {
374 self.unit_of_work = Some(config);
375 self
376 }
377
378 pub fn unit_of_work_config(&self) -> Option<&UnitOfWorkConfig> {
380 self.unit_of_work.as_ref()
381 }
382
383 pub fn circuit_breaker_config(&self) -> Option<&CircuitBreakerConfig> {
385 self.circuit_breaker.as_ref()
386 }
387
388 pub fn security_policy_config(&self) -> Option<&SecurityPolicyConfig> {
389 self.security_policy.as_ref()
390 }
391
392 pub fn security_authenticator(&self) -> Option<&Arc<dyn TokenAuthenticator>> {
393 self.security_authenticator.as_ref()
394 }
395
396 pub fn concurrency_override(&self) -> Option<&ConcurrencyModel> {
398 self.concurrency.as_ref()
399 }
400
401 pub fn with_concurrency(mut self, model: ConcurrencyModel) -> Self {
403 self.concurrency = Some(model);
404 self
405 }
406
407 pub fn route_id(&self) -> &str {
409 &self.route_id
410 }
411
412 pub fn auto_startup(&self) -> bool {
414 self.auto_startup
415 }
416
417 pub fn startup_order(&self) -> i32 {
419 self.startup_order
420 }
421
422 pub fn with_route_id(mut self, id: impl Into<String>) -> Self {
424 self.route_id = id.into();
425 self
426 }
427
428 pub fn with_auto_startup(mut self, auto: bool) -> Self {
430 self.auto_startup = auto;
431 self
432 }
433
434 pub fn with_startup_order(mut self, order: i32) -> Self {
436 self.startup_order = order;
437 self
438 }
439
440 pub fn with_source_hash(mut self, hash: u64) -> Self {
441 self.source_hash = Some(hash);
442 self
443 }
444
445 pub fn source_hash(&self) -> Option<u64> {
446 self.source_hash
447 }
448
449 pub fn to_info(&self) -> RouteDefinitionInfo {
452 RouteDefinitionInfo {
453 route_id: self.route_id.clone(),
454 auto_startup: self.auto_startup,
455 startup_order: self.startup_order,
456 source_hash: self.source_hash,
457 }
458 }
459}
460
461#[derive(Clone)]
467pub struct RouteDefinitionInfo {
468 route_id: String,
469 auto_startup: bool,
470 startup_order: i32,
471 pub(crate) source_hash: Option<u64>,
472}
473
474impl RouteDefinitionInfo {
475 pub fn route_id(&self) -> &str {
477 &self.route_id
478 }
479
480 pub fn auto_startup(&self) -> bool {
482 self.auto_startup
483 }
484
485 pub fn startup_order(&self) -> i32 {
487 self.startup_order
488 }
489
490 pub fn source_hash(&self) -> Option<u64> {
491 self.source_hash
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
504 fn golden_debug_output_all_variants() {
505 use camel_api::declarative::LanguageExpressionDef;
506 use camel_api::loop_eip::LoopMode;
507 use camel_api::recipient_list::RecipientListConfig;
508 use camel_api::splitter::{AggregationStrategy, StreamSplitConfig, StreamSplitFormat};
509 use camel_api::{
510 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, FunctionDefinition,
511 FunctionId, IdentityProcessor, MulticastConfig, OpaqueProcessor, RoutingSlipConfig,
512 Value,
513 };
514 use std::sync::Arc;
515
516 let expr = LanguageExpressionDef {
517 language: "simple".into(),
518 source: "${body}".into(),
519 };
520
521 assert_eq!(format!("{:?}", BuilderStep::Stop), "Stop");
524 assert_eq!(
525 format!(
526 "{:?}",
527 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor)))
528 ),
529 "Processor(BoxProcessor(...))"
530 );
531 assert_eq!(
532 format!("{:?}", BuilderStep::To("mock:out".into())),
533 "To(\"mock:out\")"
534 );
535
536 assert_eq!(
539 format!(
540 "{:?}",
541 BuilderStep::Log {
542 level: camel_processor::LogLevel::Info,
543 message: "hello".into(),
544 }
545 ),
546 "Log { level: Info, message: \"hello\" }"
547 );
548
549 assert_eq!(
550 format!(
551 "{:?}",
552 BuilderStep::DeclarativeSetHeader {
553 key: "k".into(),
554 value: ValueSourceDef::Literal(Value::String("v".into())),
555 }
556 ),
557 "DeclarativeSetHeader { key: \"k\", value: Literal(String(\"v\")) }"
558 );
559
560 assert_eq!(
561 format!(
562 "{:?}",
563 BuilderStep::DeclarativeSetHeaderIfAbsent {
564 key: "k".into(),
565 value: ValueSourceDef::Literal(Value::String("v".into())),
566 }
567 ),
568 "DeclarativeSetHeaderIfAbsent { key: \"k\", value: Literal(String(\"v\")) }"
569 );
570
571 assert_eq!(
572 format!(
573 "{:?}",
574 BuilderStep::DeclarativeSetBody {
575 value: ValueSourceDef::Literal(Value::String("v".into())),
576 }
577 ),
578 "DeclarativeSetBody { value: Literal(String(\"v\")) }"
579 );
580
581 assert_eq!(
582 format!(
583 "{:?}",
584 BuilderStep::DeclarativeSetProperty {
585 key: "prop".into(),
586 value_source: ValueSourceDef::Literal(Value::String("v".into())),
587 }
588 ),
589 "DeclarativeSetProperty { key: \"prop\", value_source: Literal(String(\"v\")) }"
590 );
591
592 assert_eq!(
593 format!(
594 "{:?}",
595 BuilderStep::DeclarativeScript {
596 expression: expr.clone(),
597 }
598 ),
599 "DeclarativeScript { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
600 );
601
602 let func_def = FunctionDefinition {
604 id: FunctionId("test-id".into()),
605 runtime: "my_runtime".into(),
606 source: "${body}".into(),
607 timeout_ms: 5000,
608 route_id: None,
609 step_index: None,
610 };
611 assert_eq!(
612 format!(
613 "{:?}",
614 BuilderStep::DeclarativeFunction {
615 definition: func_def,
616 }
617 ),
618 "DeclarativeFunction { definition: FunctionDefinition { id: FunctionId(\"test-id\"), runtime: \"my_runtime\", source: \"${body}\", timeout_ms: 5000, route_id: None, step_index: None } }"
619 );
620
621 assert_eq!(
622 format!(
623 "{:?}",
624 BuilderStep::WireTap {
625 uri: "mock:tap".into(),
626 }
627 ),
628 "WireTap { uri: \"mock:tap\" }"
629 );
630
631 assert_eq!(
632 format!(
633 "{:?}",
634 BuilderStep::DeclarativeLog {
635 level: camel_processor::LogLevel::Info,
636 message: ValueSourceDef::Expression(expr.clone()),
637 }
638 ),
639 "DeclarativeLog { level: Info, message: Expression(LanguageExpressionDef { language: \"simple\", source: \"${body}\" }) }"
640 );
641
642 assert_eq!(
643 format!(
644 "{:?}",
645 BuilderStep::Bean {
646 name: "myBean".into(),
647 method: "process".into(),
648 }
649 ),
650 "Bean { name: \"myBean\", method: \"process\" }"
651 );
652
653 assert_eq!(
654 format!(
655 "{:?}",
656 BuilderStep::Script {
657 language: "js".into(),
658 script: "body".into(),
659 }
660 ),
661 "Script { language: \"js\", script: \"body\" }"
662 );
663
664 assert_eq!(
665 format!(
666 "{:?}",
667 BuilderStep::Aggregate {
668 config: camel_api::AggregatorConfig::correlate_by("id")
669 .complete_when_size(1)
670 .build()
671 .unwrap(),
672 }
673 ),
674 "Aggregate { config: AggregatorConfig { header_name: \"id\", completion: Single(Size(1)), correlation: HeaderName(\"id\"), strategy: CollectAll, max_buckets: Some(10000), bucket_ttl: Some(300s), force_completion_on_stop: false, discard_on_timeout: false, max_timeout_tasks: 1024 } }"
675 );
676
677 assert_eq!(
678 format!(
679 "{:?}",
680 BuilderStep::DynamicRouter {
681 config: DynamicRouterConfig::new(Arc::new(|_: &Exchange| Some(
682 "mock:dr".into()
683 ))),
684 }
685 ),
686 "DynamicRouter { config: DynamicRouterConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000, timeout: Some(60s) } }"
687 );
688
689 assert_eq!(
690 format!(
691 "{:?}",
692 BuilderStep::RoutingSlip {
693 config: RoutingSlipConfig::new(Arc::new(|_: &Exchange| Some("mock:rs".into()))),
694 }
695 ),
696 "RoutingSlip { config: RoutingSlipConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false } }"
697 );
698
699 assert_eq!(
700 format!(
701 "{:?}",
702 BuilderStep::RecipientList {
703 config: RecipientListConfig::new(Arc::new(|_: &Exchange| String::new())),
704 }
705 ),
706 "RecipientList { config: RecipientListConfig { delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, max_recipients: 1000 } }"
707 );
708
709 assert_eq!(
710 format!(
711 "{:?}",
712 BuilderStep::Enrich {
713 uri: "mock:enrich".into(),
714 strategy: Some("agg".into()),
715 timeout_ms: Some(1000),
716 }
717 ),
718 "Enrich { uri: \"mock:enrich\", strategy: Some(\"agg\"), timeout_ms: Some(1000) }"
719 );
720
721 assert_eq!(
722 format!(
723 "{:?}",
724 BuilderStep::PollEnrich {
725 uri: "mock:poll".into(),
726 strategy: None,
727 timeout_ms: None,
728 }
729 ),
730 "PollEnrich { uri: \"mock:poll\", strategy: None, timeout_ms: None }"
731 );
732
733 assert_eq!(
734 format!(
735 "{:?}",
736 BuilderStep::Validate {
737 predicate: expr.clone(),
738 }
739 ),
740 "Validate { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
741 );
742
743 assert_eq!(
744 format!("{:?}", BuilderStep::Sampling { period: 100 }),
745 "Sampling { period: 100 }"
746 );
747
748 assert_eq!(
749 format!(
750 "{:?}",
751 BuilderStep::Resequence {
752 policy_config: Default::default(),
753 }
754 ),
755 "Resequence { policy_config: ResequencePolicyConfig { mode: Batch { correlation: \"header.id\", sort: \"header.id\", completion: SizeOrTimeout(100, 30000) } } }"
756 );
757
758 assert_eq!(
760 format!(
761 "{:?}",
762 BuilderStep::DeclarativeFilter {
763 predicate: expr.clone(),
764 steps: vec![BuilderStep::Stop],
765 }
766 ),
767 "DeclarativeFilter { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }"
768 );
769
770 assert_eq!(
771 format!(
772 "{:?}",
773 BuilderStep::DeclarativeSplit {
774 expression: expr.clone(),
775 aggregation: AggregationStrategy::Original,
776 parallel: false,
777 parallel_limit: Some(2),
778 stop_on_exception: true,
779 steps: vec![BuilderStep::Stop],
780 }
781 ),
782 "DeclarativeSplit { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, aggregation: Original, parallel: false, parallel_limit: Some(2), stop_on_exception: true, steps: [Stop] }"
783 );
784
785 assert_eq!(
786 format!(
787 "{:?}",
788 BuilderStep::Split {
789 config: camel_api::splitter::SplitterConfig::new(
790 camel_api::splitter::split_body_lines()
791 ),
792 steps: vec![BuilderStep::Stop],
793 }
794 ),
795 "Split { config: SplitterConfig { expression: \"<split-expression>\", aggregation: LastWins, parallel: false, parallel_limit: None, stop_on_exception: true, max_fragments: 100000 }, steps: [Stop] }"
796 );
797
798 assert_eq!(
799 format!(
800 "{:?}",
801 BuilderStep::Filter {
802 predicate: FilterPredicate::new(|_: &Exchange| true),
803 steps: vec![BuilderStep::Stop],
804 }
805 ),
806 "Filter { predicate: FilterPredicate(..), steps: [Stop] }"
807 );
808
809 assert_eq!(
810 format!(
811 "{:?}",
812 BuilderStep::Throttle {
813 config: camel_api::ThrottlerConfig::new(
814 10,
815 std::time::Duration::from_millis(10)
816 ),
817 steps: vec![BuilderStep::Stop],
818 }
819 ),
820 "Throttle { config: ThrottlerConfig { max_requests: 10, period: 10ms, strategy: Delay }, steps: [Stop] }"
821 );
822
823 assert_eq!(
824 format!(
825 "{:?}",
826 BuilderStep::LoadBalance {
827 config: camel_api::LoadBalancerConfig::round_robin(),
828 steps: vec![BuilderStep::To("mock:l1".into())],
829 }
830 ),
831 "LoadBalance { config: LoadBalancerConfig { strategy: RoundRobin }, steps: [To(\"mock:l1\")] }"
832 );
833
834 assert_eq!(
835 format!(
836 "{:?}",
837 BuilderStep::Delay {
838 config: camel_api::DelayConfig::new(500),
839 }
840 ),
841 "Delay { config: DelayConfig { delay_ms: 500, dynamic_header: None, max_delay_ms: 3600000 } }"
842 );
843
844 assert_eq!(
848 format!(
849 "{:?}",
850 BuilderStep::Choice {
851 whens: vec![WhenStep {
852 predicate: FilterPredicate::new(|_: &Exchange| true),
853 steps: vec![BuilderStep::To("mock:a".into())],
854 }],
855 otherwise: None,
856 }
857 ),
858 "Choice { whens: [WhenStep { predicate: FilterPredicate(..), steps: [To(\"mock:a\")] }], otherwise: None }"
859 );
860
861 assert_eq!(
862 format!(
863 "{:?}",
864 BuilderStep::DeclarativeChoice {
865 whens: vec![DeclarativeWhenStep {
866 predicate: expr.clone(),
867 steps: vec![BuilderStep::Stop],
868 }],
869 otherwise: Some(vec![BuilderStep::Stop]),
870 }
871 ),
872 "DeclarativeChoice { whens: [DeclarativeWhenStep { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }], otherwise: Some([Stop]) }"
873 );
874
875 assert_eq!(
877 format!(
878 "{:?}",
879 BuilderStep::Multicast {
880 steps: vec![BuilderStep::To("direct:a".into())],
881 config: MulticastConfig::new(),
882 }
883 ),
884 "Multicast { steps: [To(\"direct:a\")], config: MulticastConfig { parallel: false, parallel_limit: None, stop_on_exception: false, timeout: None, aggregation: LastWins } }"
885 );
886
887 assert_eq!(
889 format!(
890 "{:?}",
891 BuilderStep::DeclarativeDynamicRouter {
892 expression: expr.clone(),
893 uri_delimiter: ",".into(),
894 cache_size: 1000,
895 ignore_invalid_endpoints: false,
896 max_iterations: 1000,
897 }
898 ),
899 "DeclarativeDynamicRouter { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000 }"
900 );
901
902 assert_eq!(
903 format!(
904 "{:?}",
905 BuilderStep::DeclarativeRoutingSlip {
906 expression: expr.clone(),
907 uri_delimiter: ",".into(),
908 cache_size: 1000,
909 ignore_invalid_endpoints: false,
910 }
911 ),
912 "DeclarativeRoutingSlip { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false }"
913 );
914
915 assert_eq!(
917 format!(
918 "{:?}",
919 BuilderStep::DeclarativeRecipientList {
920 expression: expr.clone(),
921 delimiter: ",".into(),
922 parallel: false,
923 parallel_limit: None,
924 stop_on_exception: false,
925 aggregation: "original".into(),
926 }
927 ),
928 "DeclarativeRecipientList { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, aggregation: \"original\" }"
929 );
930
931 assert_eq!(
933 format!(
934 "{:?}",
935 BuilderStep::Loop {
936 config: camel_api::loop_eip::LoopConfig::new(LoopMode::Count(3)),
937 steps: vec![],
938 }
939 ),
940 "Loop { config: LoopConfig { mode: Count(3), max_iterations: 10000 }, steps: [] }"
941 );
942
943 assert_eq!(
944 format!(
945 "{:?}",
946 BuilderStep::DeclarativeLoop {
947 count: Some(5),
948 while_predicate: None,
949 steps: vec![],
950 max_iterations: Some(100),
951 }
952 ),
953 "DeclarativeLoop { count: Some(5), while_predicate: None, steps: [], max_iterations: Some(100) }"
954 );
955
956 assert_eq!(
958 format!(
959 "{:?}",
960 BuilderStep::ClaimCheck {
961 repository: "myRepo".into(),
962 operation: "checkout".into(),
963 key: expr.clone(),
964 filter: None,
965 }
966 ),
967 "ClaimCheck { repository: \"myRepo\", operation: \"checkout\", key: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, filter: None }"
968 );
969
970 assert_eq!(
972 format!(
973 "{:?}",
974 BuilderStep::Sort {
975 expression: expr.clone(),
976 reverse: false,
977 }
978 ),
979 "Sort { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, reverse: false }"
980 );
981
982 assert_eq!(
984 format!(
985 "{:?}",
986 BuilderStep::IdempotentConsumer {
987 repository: "myRepo".into(),
988 expression: expr.clone(),
989 steps: vec![],
990 eager: true,
991 remove_on_failure: false,
992 }
993 ),
994 "IdempotentConsumer { repository: \"myRepo\", expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [], eager: true, remove_on_failure: false }"
995 );
996
997 assert_eq!(
999 format!(
1000 "{:?}",
1001 BuilderStep::DeclarativeDoTry {
1002 try_steps: vec![BuilderStep::Stop],
1003 catch: vec![],
1004 finally: None,
1005 }
1006 ),
1007 "DeclarativeDoTry { try_steps: [Stop], catch: [], finally: None }"
1008 );
1009
1010 assert_eq!(
1012 format!(
1013 "{:?}",
1014 BuilderStep::DeclarativeStreamSplit {
1015 stream_config: StreamSplitConfig {
1016 format: StreamSplitFormat::Ndjson,
1017 max_record_bytes: 1024 * 1024,
1018 batch_size: 1,
1019 chunk_size: None,
1020 include_origin: true,
1021 },
1022 aggregation: AggregationStrategy::Original,
1023 stop_on_exception: true,
1024 steps: vec![BuilderStep::Stop],
1025 }
1026 ),
1027 "DeclarativeStreamSplit { stream_config: StreamSplitConfig { format: Ndjson, max_record_bytes: 1048576, batch_size: 1, chunk_size: None, include_origin: true }, aggregation: Original, stop_on_exception: true, steps: [Stop] }"
1028 );
1029 }
1030
1031 #[test]
1032 fn test_builder_step_multicast_variant() {
1033 use camel_api::MulticastConfig;
1034
1035 let step = BuilderStep::Multicast {
1036 steps: vec![BuilderStep::To("direct:a".into())],
1037 config: MulticastConfig::new(),
1038 };
1039
1040 assert!(matches!(step, BuilderStep::Multicast { .. }));
1041 }
1042
1043 #[test]
1044 fn test_route_definition_defaults() {
1045 let def = RouteDefinition::new("direct:test", vec![]).with_route_id("test-route");
1046 assert_eq!(def.route_id(), "test-route");
1047 assert!(def.auto_startup());
1048 assert_eq!(def.startup_order(), 1000);
1049 }
1050
1051 #[test]
1052 fn test_route_definition_builders() {
1053 let def = RouteDefinition::new("direct:test", vec![])
1054 .with_route_id("my-route")
1055 .with_auto_startup(false)
1056 .with_startup_order(50);
1057 assert_eq!(def.route_id(), "my-route");
1058 assert!(!def.auto_startup());
1059 assert_eq!(def.startup_order(), 50);
1060 }
1061
1062 #[test]
1063 fn test_route_definition_accessors_cover_core_fields() {
1064 let def = RouteDefinition::new("direct:in", vec![BuilderStep::To("mock:out".into())])
1065 .with_route_id("accessor-route");
1066
1067 assert_eq!(def.from_uri(), "direct:in");
1068 assert_eq!(def.steps().len(), 1);
1069 assert!(matches!(def.steps()[0], BuilderStep::To(_)));
1070 }
1071
1072 #[test]
1073 fn test_route_definition_error_handler_circuit_breaker_and_concurrency_accessors() {
1074 use camel_api::circuit_breaker::CircuitBreakerConfig;
1075 use camel_api::error_handler::ErrorHandlerConfig;
1076 use camel_component_api::ConcurrencyModel;
1077
1078 let def = RouteDefinition::new("direct:test", vec![])
1079 .with_route_id("eh-route")
1080 .with_error_handler(ErrorHandlerConfig::dead_letter_channel("log:dlc"))
1081 .with_circuit_breaker(CircuitBreakerConfig::new())
1082 .with_concurrency(ConcurrencyModel::Concurrent { max: Some(4) });
1083
1084 let eh = def
1085 .error_handler_config()
1086 .expect("error handler should be set");
1087 assert_eq!(eh.dlc_uri.as_deref(), Some("log:dlc"));
1088 assert!(def.circuit_breaker_config().is_some());
1089 assert!(matches!(
1090 def.concurrency_override(),
1091 Some(ConcurrencyModel::Concurrent { max: Some(4) })
1092 ));
1093 }
1094
1095 #[test]
1096 fn test_builder_step_debug_covers_many_variants() {
1097 use camel_api::splitter::{AggregationStrategy, SplitterConfig, split_body_lines};
1098 use camel_api::{
1099 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, IdentityProcessor,
1100 OpaqueProcessor, RoutingSlipConfig, Value,
1101 };
1102 use std::sync::Arc;
1103
1104 let expr = LanguageExpressionDef {
1105 language: "simple".into(),
1106 source: "${body}".into(),
1107 };
1108
1109 let steps = vec![
1110 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor))),
1111 BuilderStep::To("mock:out".into()),
1112 BuilderStep::Stop,
1113 BuilderStep::Log {
1114 level: camel_processor::LogLevel::Info,
1115 message: "hello".into(),
1116 },
1117 BuilderStep::DeclarativeSetHeader {
1118 key: "k".into(),
1119 value: ValueSourceDef::Literal(Value::String("v".into())),
1120 },
1121 BuilderStep::DeclarativeSetBody {
1122 value: ValueSourceDef::Expression(expr.clone()),
1123 },
1124 BuilderStep::DeclarativeFilter {
1125 predicate: expr.clone(),
1126 steps: vec![BuilderStep::Stop],
1127 },
1128 BuilderStep::DeclarativeChoice {
1129 whens: vec![DeclarativeWhenStep {
1130 predicate: expr.clone(),
1131 steps: vec![BuilderStep::Stop],
1132 }],
1133 otherwise: Some(vec![BuilderStep::Stop]),
1134 },
1135 BuilderStep::DeclarativeScript {
1136 expression: expr.clone(),
1137 },
1138 BuilderStep::DeclarativeSplit {
1139 expression: expr.clone(),
1140 aggregation: AggregationStrategy::Original,
1141 parallel: false,
1142 parallel_limit: Some(2),
1143 stop_on_exception: true,
1144 steps: vec![BuilderStep::Stop],
1145 },
1146 BuilderStep::Split {
1147 config: SplitterConfig::new(split_body_lines()),
1148 steps: vec![BuilderStep::Stop],
1149 },
1150 BuilderStep::Aggregate {
1151 config: camel_api::AggregatorConfig::correlate_by("id")
1152 .complete_when_size(1)
1153 .build()
1154 .unwrap(),
1155 },
1156 BuilderStep::Filter {
1157 predicate: FilterPredicate::new(|_: &Exchange| true),
1158 steps: vec![BuilderStep::Stop],
1159 },
1160 BuilderStep::WireTap {
1161 uri: "mock:tap".into(),
1162 },
1163 BuilderStep::DeclarativeLog {
1164 level: camel_processor::LogLevel::Info,
1165 message: ValueSourceDef::Expression(expr.clone()),
1166 },
1167 BuilderStep::Bean {
1168 name: "bean".into(),
1169 method: "call".into(),
1170 },
1171 BuilderStep::Script {
1172 language: "rhai".into(),
1173 script: "body".into(),
1174 },
1175 BuilderStep::Throttle {
1176 config: camel_api::ThrottlerConfig::new(10, std::time::Duration::from_millis(10)),
1177 steps: vec![BuilderStep::Stop],
1178 },
1179 BuilderStep::LoadBalance {
1180 config: camel_api::LoadBalancerConfig::round_robin(),
1181 steps: vec![BuilderStep::To("mock:l1".into())],
1182 },
1183 BuilderStep::DynamicRouter {
1184 config: DynamicRouterConfig::new(Arc::new(|_| Some("mock:dr".into()))),
1185 },
1186 BuilderStep::RoutingSlip {
1187 config: RoutingSlipConfig::new(Arc::new(|_| Some("mock:rs".into()))),
1188 },
1189 ];
1190
1191 for step in steps {
1192 let dbg = format!("{step:?}");
1193 assert!(!dbg.is_empty());
1194 }
1195 }
1196
1197 #[test]
1198 fn test_route_definition_to_info_preserves_metadata() {
1199 let info = RouteDefinition::new("direct:test", vec![])
1200 .with_route_id("meta-route")
1201 .with_auto_startup(false)
1202 .with_startup_order(7)
1203 .to_info();
1204
1205 assert_eq!(info.route_id(), "meta-route");
1206 assert!(!info.auto_startup());
1207 assert_eq!(info.startup_order(), 7);
1208 }
1209
1210 #[test]
1211 fn test_choice_builder_step_debug() {
1212 use camel_api::FilterPredicate;
1213
1214 fn always_true(_: &camel_api::Exchange) -> bool {
1215 true
1216 }
1217
1218 let step = BuilderStep::Choice {
1219 whens: vec![WhenStep {
1220 predicate: FilterPredicate::new(always_true),
1221 steps: vec![BuilderStep::To("mock:a".into())],
1222 }],
1223 otherwise: None,
1224 };
1225 let debug = format!("{step:?}");
1226 assert!(debug.contains("Choice"));
1227 }
1228
1229 #[test]
1230 fn test_route_definition_unit_of_work() {
1231 use camel_api::UnitOfWorkConfig;
1232 let config = UnitOfWorkConfig {
1233 on_complete: Some("log:complete".into()),
1234 on_failure: Some("log:failed".into()),
1235 };
1236 let def = RouteDefinition::new("direct:test", vec![])
1237 .with_route_id("uow-test")
1238 .with_unit_of_work(config.clone());
1239 assert_eq!(
1240 def.unit_of_work_config().unwrap().on_complete.as_deref(),
1241 Some("log:complete")
1242 );
1243 assert_eq!(
1244 def.unit_of_work_config().unwrap().on_failure.as_deref(),
1245 Some("log:failed")
1246 );
1247
1248 let def_no_uow = RouteDefinition::new("direct:test", vec![]).with_route_id("no-uow");
1249 assert!(def_no_uow.unit_of_work_config().is_none());
1250 }
1251
1252 #[test]
1253 fn test_route_definition_security_policy_accessor() {
1254 use async_trait::async_trait;
1255 use camel_api::CamelError;
1256 use camel_api::Exchange;
1257 use camel_api::security_policy::{
1258 AuthorizationDecision, Principal, SecurityPolicy, SecurityPolicyConfig,
1259 };
1260
1261 struct StubPolicy;
1262 #[async_trait]
1263 impl SecurityPolicy for StubPolicy {
1264 async fn evaluate(
1265 &self,
1266 _exchange: &mut Exchange,
1267 ) -> Result<AuthorizationDecision, CamelError> {
1268 Ok(AuthorizationDecision::Granted {
1269 principal: Principal {
1270 subject: "test".into(),
1271 issuer: "test".into(),
1272 audience: vec![],
1273 scopes: vec![],
1274 roles: vec![],
1275 claims: serde_json::Value::Null,
1276 },
1277 })
1278 }
1279 }
1280
1281 let def_no_sp = RouteDefinition::new("direct:test", vec![]).with_route_id("no-sp");
1282 assert!(def_no_sp.security_policy_config().is_none());
1283
1284 let def = RouteDefinition::new("direct:test", vec![])
1285 .with_route_id("sp-test")
1286 .with_security_policy(SecurityPolicyConfig::new(StubPolicy));
1287 assert!(def.security_policy_config().is_some());
1288 }
1289
1290 #[test]
1291 fn test_route_definition_security_authenticator_accessor() {
1292 use camel_api::security_policy::Principal;
1293
1294 struct TestAuth;
1295 #[async_trait::async_trait]
1296 impl TokenAuthenticator for TestAuth {
1297 async fn authenticate_bearer(
1298 &self,
1299 _token: &str,
1300 ) -> Result<Principal, camel_api::CamelError> {
1301 Ok(Principal {
1302 subject: "test".into(),
1303 issuer: "test".into(),
1304 audience: vec![],
1305 scopes: vec![],
1306 roles: vec![],
1307 claims: serde_json::Value::Null,
1308 })
1309 }
1310 }
1311
1312 let def_no_auth = RouteDefinition::new("direct:test".to_string(), vec![]);
1313 assert!(def_no_auth.security_authenticator().is_none());
1314
1315 let auth = Arc::new(TestAuth);
1316 let def = RouteDefinition::new("direct:test".to_string(), vec![])
1317 .with_security_authenticator(auth);
1318 assert!(def.security_authenticator().is_some());
1319 }
1320}