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