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