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 coalesce_misses: bool,
288 on_miss: Vec<BuilderStep>,
289 },
290 CacheInvalidate {
295 repository: Option<String>,
296 key: Option<LanguageExpressionDef>,
297 key_prefix: Option<LanguageExpressionDef>,
298 },
299 CacheClear {
302 repository: Option<String>,
303 },
304 CacheStats {
307 repository: Option<String>,
308 },
309 CachePeekStale {
312 repository: Option<String>,
313 key: LanguageExpressionDef,
314 on_miss: camel_processor::PeekStaleMissPolicy,
315 },
316 DeclarativeDoTry {
318 try_steps: Vec<BuilderStep>,
319 catch: Vec<DoTryCatchClauseBuilder>,
320 finally: Option<DoTryFinallyBuilder>,
321 },
322 Resequence {
325 policy_config: ResequencePolicyConfig,
326 },
327}
328
329pub struct RouteDefinition {
331 pub(crate) from_uri: String,
332 pub(crate) steps: Vec<BuilderStep>,
333 pub(crate) error_handler: Option<ErrorHandlerConfig>,
335 pub(crate) circuit_breaker: Option<CircuitBreakerConfig>,
337 pub(crate) circuit_breaker_fallback: Vec<BuilderStep>,
345 pub(crate) security_policy: Option<SecurityPolicyConfig>,
346 pub(crate) security_authenticator: Option<Arc<dyn TokenAuthenticator>>,
348 pub(crate) provider_registry: Option<Arc<camel_auth::ProviderRegistry>>,
351 pub(crate) security_provider: Option<String>,
355 pub(crate) security_audiences: Option<Vec<String>>,
360 pub(crate) unit_of_work: Option<UnitOfWorkConfig>,
362 pub(crate) concurrency: Option<ConcurrencyModel>,
365 pub(crate) route_id: String,
367 pub(crate) auto_startup: bool,
369 pub(crate) startup_order: i32,
371 pub(crate) source_hash: Option<u64>,
372}
373
374impl RouteDefinition {
375 pub fn new(from_uri: impl Into<String>, steps: Vec<BuilderStep>) -> Self {
377 Self {
378 from_uri: from_uri.into(),
379 steps,
380 error_handler: None,
381 circuit_breaker: None,
382 circuit_breaker_fallback: Vec::new(),
383 security_policy: None,
384 security_authenticator: None,
385 provider_registry: None,
386 security_provider: None,
387 security_audiences: None,
388 unit_of_work: None,
389 concurrency: None,
390 route_id: String::new(), auto_startup: true,
392 startup_order: 1000,
393 source_hash: None,
394 }
395 }
396
397 pub fn from_uri(&self) -> &str {
399 &self.from_uri
400 }
401
402 pub fn steps(&self) -> &[BuilderStep] {
404 &self.steps
405 }
406
407 pub fn circuit_breaker_fallback(&self) -> &[BuilderStep] {
408 &self.circuit_breaker_fallback
409 }
410
411 pub fn map_steps(mut self, f: impl FnOnce(Vec<BuilderStep>) -> Vec<BuilderStep>) -> Self {
416 self.steps = f(self.steps);
417 self
418 }
419
420 pub fn with_error_handler(mut self, config: ErrorHandlerConfig) -> Self {
422 self.error_handler = Some(config);
423 self
424 }
425
426 pub fn error_handler_config(&self) -> Option<&ErrorHandlerConfig> {
428 self.error_handler.as_ref()
429 }
430
431 pub fn with_circuit_breaker(mut self, config: CircuitBreakerConfig) -> Self {
433 self.circuit_breaker = Some(config);
434 self
435 }
436
437 pub fn with_circuit_breaker_fallback(mut self, steps: Vec<BuilderStep>) -> Self {
444 self.circuit_breaker_fallback = steps;
445 self
446 }
447
448 pub fn with_security_policy(mut self, config: SecurityPolicyConfig) -> Self {
450 self.security_policy = Some(config);
451 self
452 }
453
454 pub fn with_security_authenticator(
456 mut self,
457 authenticator: Arc<dyn TokenAuthenticator>,
458 ) -> Self {
459 self.security_authenticator = Some(authenticator);
460 self
461 }
462
463 pub fn with_provider_registry(mut self, registry: Arc<camel_auth::ProviderRegistry>) -> Self {
469 self.provider_registry = Some(registry);
470 self
471 }
472
473 pub fn with_security_provider(mut self, name: impl Into<String>) -> Self {
475 self.security_provider = Some(name.into());
476 self
477 }
478
479 pub fn with_security_audiences(mut self, audiences: Vec<String>) -> Self {
481 self.security_audiences = Some(audiences);
482 self
483 }
484
485 pub fn security_provider(&self) -> Option<&str> {
487 self.security_provider.as_deref()
488 }
489
490 pub fn security_audiences(&self) -> Option<&[String]> {
492 self.security_audiences.as_deref()
493 }
494
495 pub fn with_unit_of_work(mut self, config: UnitOfWorkConfig) -> Self {
497 self.unit_of_work = Some(config);
498 self
499 }
500
501 pub fn unit_of_work_config(&self) -> Option<&UnitOfWorkConfig> {
503 self.unit_of_work.as_ref()
504 }
505
506 pub fn circuit_breaker_config(&self) -> Option<&CircuitBreakerConfig> {
508 self.circuit_breaker.as_ref()
509 }
510
511 pub fn security_policy_config(&self) -> Option<&SecurityPolicyConfig> {
512 self.security_policy.as_ref()
513 }
514
515 pub fn security_authenticator(&self) -> Option<&Arc<dyn TokenAuthenticator>> {
516 self.security_authenticator.as_ref()
517 }
518
519 pub fn concurrency_override(&self) -> Option<&ConcurrencyModel> {
521 self.concurrency.as_ref()
522 }
523
524 pub fn with_concurrency(mut self, model: ConcurrencyModel) -> Self {
526 self.concurrency = Some(model);
527 self
528 }
529
530 pub fn route_id(&self) -> &str {
532 &self.route_id
533 }
534
535 pub fn auto_startup(&self) -> bool {
537 self.auto_startup
538 }
539
540 pub fn startup_order(&self) -> i32 {
542 self.startup_order
543 }
544
545 pub fn with_route_id(mut self, id: impl Into<String>) -> Self {
547 self.route_id = id.into();
548 self
549 }
550
551 pub fn with_auto_startup(mut self, auto: bool) -> Self {
553 self.auto_startup = auto;
554 self
555 }
556
557 pub fn with_startup_order(mut self, order: i32) -> Self {
559 self.startup_order = order;
560 self
561 }
562
563 pub fn with_source_hash(mut self, hash: u64) -> Self {
564 self.source_hash = Some(hash);
565 self
566 }
567
568 pub fn source_hash(&self) -> Option<u64> {
569 self.source_hash
570 }
571
572 pub fn to_info(&self) -> RouteDefinitionInfo {
575 RouteDefinitionInfo {
576 route_id: self.route_id.clone(),
577 auto_startup: self.auto_startup,
578 startup_order: self.startup_order,
579 source_hash: self.source_hash,
580 }
581 }
582}
583
584#[derive(Clone)]
590pub struct RouteDefinitionInfo {
591 route_id: String,
592 auto_startup: bool,
593 startup_order: i32,
594 pub(crate) source_hash: Option<u64>,
595}
596
597impl RouteDefinitionInfo {
598 pub fn route_id(&self) -> &str {
600 &self.route_id
601 }
602
603 pub fn auto_startup(&self) -> bool {
605 self.auto_startup
606 }
607
608 pub fn startup_order(&self) -> i32 {
610 self.startup_order
611 }
612
613 pub fn source_hash(&self) -> Option<u64> {
614 self.source_hash
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 #[test]
627 fn golden_debug_output_all_variants() {
628 use camel_api::declarative::LanguageExpressionDef;
629 use camel_api::loop_eip::LoopMode;
630 use camel_api::recipient_list::RecipientListConfig;
631 use camel_api::splitter::{AggregationStrategy, StreamSplitConfig, StreamSplitFormat};
632 use camel_api::{
633 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, FunctionDefinition,
634 FunctionId, IdentityProcessor, MulticastConfig, OpaqueProcessor, RoutingSlipConfig,
635 Value,
636 };
637 use std::sync::Arc;
638
639 let expr = LanguageExpressionDef {
640 language: "simple".into(),
641 source: "${body}".into(),
642 };
643
644 assert_eq!(format!("{:?}", BuilderStep::Stop), "Stop");
647 assert_eq!(
648 format!(
649 "{:?}",
650 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor)))
651 ),
652 "Processor(BoxProcessor(...))"
653 );
654 assert_eq!(
655 format!("{:?}", BuilderStep::To("mock:out".into())),
656 "To(\"mock:out\")"
657 );
658
659 assert_eq!(
662 format!(
663 "{:?}",
664 BuilderStep::Log {
665 level: camel_processor::LogLevel::Info,
666 message: "hello".into(),
667 }
668 ),
669 "Log { level: Info, message: \"hello\" }"
670 );
671
672 assert_eq!(
673 format!(
674 "{:?}",
675 BuilderStep::DeclarativeSetHeader {
676 key: "k".into(),
677 value: ValueSourceDef::Literal(Value::String("v".into())),
678 }
679 ),
680 "DeclarativeSetHeader { key: \"k\", value: Literal(String(\"v\")) }"
681 );
682
683 assert_eq!(
684 format!(
685 "{:?}",
686 BuilderStep::DeclarativeSetHeaderIfAbsent {
687 key: "k".into(),
688 value: ValueSourceDef::Literal(Value::String("v".into())),
689 }
690 ),
691 "DeclarativeSetHeaderIfAbsent { key: \"k\", value: Literal(String(\"v\")) }"
692 );
693
694 assert_eq!(
695 format!(
696 "{:?}",
697 BuilderStep::DeclarativeSetBody {
698 value: ValueSourceDef::Literal(Value::String("v".into())),
699 }
700 ),
701 "DeclarativeSetBody { value: Literal(String(\"v\")) }"
702 );
703
704 assert_eq!(
705 format!(
706 "{:?}",
707 BuilderStep::DeclarativeSetProperty {
708 key: "prop".into(),
709 value_source: ValueSourceDef::Literal(Value::String("v".into())),
710 }
711 ),
712 "DeclarativeSetProperty { key: \"prop\", value_source: Literal(String(\"v\")) }"
713 );
714
715 assert_eq!(
716 format!(
717 "{:?}",
718 BuilderStep::DeclarativeScript {
719 expression: expr.clone(),
720 }
721 ),
722 "DeclarativeScript { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
723 );
724
725 let func_def = FunctionDefinition {
727 id: FunctionId("test-id".into()),
728 runtime: "my_runtime".into(),
729 source: "${body}".into(),
730 timeout_ms: 5000,
731 route_id: None,
732 step_index: None,
733 };
734 assert_eq!(
735 format!(
736 "{:?}",
737 BuilderStep::DeclarativeFunction {
738 definition: func_def,
739 }
740 ),
741 "DeclarativeFunction { definition: FunctionDefinition { id: FunctionId(\"test-id\"), runtime: \"my_runtime\", source: \"${body}\", timeout_ms: 5000, route_id: None, step_index: None } }"
742 );
743
744 assert_eq!(
745 format!(
746 "{:?}",
747 BuilderStep::WireTap {
748 uri: "mock:tap".into(),
749 }
750 ),
751 "WireTap { uri: \"mock:tap\" }"
752 );
753
754 assert_eq!(
755 format!(
756 "{:?}",
757 BuilderStep::DeclarativeLog {
758 level: camel_processor::LogLevel::Info,
759 message: ValueSourceDef::Expression(expr.clone()),
760 }
761 ),
762 "DeclarativeLog { level: Info, message: Expression(LanguageExpressionDef { language: \"simple\", source: \"${body}\" }) }"
763 );
764
765 assert_eq!(
766 format!(
767 "{:?}",
768 BuilderStep::Bean {
769 name: "myBean".into(),
770 method: "process".into(),
771 }
772 ),
773 "Bean { name: \"myBean\", method: \"process\" }"
774 );
775
776 assert_eq!(
777 format!(
778 "{:?}",
779 BuilderStep::Script {
780 language: "js".into(),
781 script: "body".into(),
782 }
783 ),
784 "Script { language: \"js\", script: \"body\" }"
785 );
786
787 assert_eq!(
788 format!(
789 "{:?}",
790 BuilderStep::Aggregate {
791 config: camel_api::AggregatorConfig::correlate_by("id")
792 .complete_when_size(1)
793 .build()
794 .unwrap(),
795 }
796 ),
797 "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 } }"
798 );
799
800 assert_eq!(
801 format!(
802 "{:?}",
803 BuilderStep::DynamicRouter {
804 config: DynamicRouterConfig::new(Arc::new(|_: &Exchange| Some(
805 "mock:dr".into()
806 ))),
807 }
808 ),
809 "DynamicRouter { config: DynamicRouterConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000, timeout: Some(60s) } }"
810 );
811
812 assert_eq!(
813 format!(
814 "{:?}",
815 BuilderStep::RoutingSlip {
816 config: RoutingSlipConfig::new(Arc::new(|_: &Exchange| Some("mock:rs".into()))),
817 }
818 ),
819 "RoutingSlip { config: RoutingSlipConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false } }"
820 );
821
822 assert_eq!(
823 format!(
824 "{:?}",
825 BuilderStep::RecipientList {
826 config: RecipientListConfig::new(Arc::new(|_: &Exchange| String::new())),
827 }
828 ),
829 "RecipientList { config: RecipientListConfig { delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, max_recipients: 1000 } }"
830 );
831
832 assert_eq!(
833 format!(
834 "{:?}",
835 BuilderStep::Enrich {
836 uri: "mock:enrich".into(),
837 strategy: Some("agg".into()),
838 timeout_ms: Some(1000),
839 }
840 ),
841 "Enrich { uri: \"mock:enrich\", strategy: Some(\"agg\"), timeout_ms: Some(1000) }"
842 );
843
844 assert_eq!(
845 format!(
846 "{:?}",
847 BuilderStep::PollEnrich {
848 uri: "mock:poll".into(),
849 strategy: None,
850 timeout_ms: None,
851 }
852 ),
853 "PollEnrich { uri: \"mock:poll\", strategy: None, timeout_ms: None }"
854 );
855
856 assert_eq!(
857 format!(
858 "{:?}",
859 BuilderStep::Validate {
860 predicate: expr.clone(),
861 }
862 ),
863 "Validate { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
864 );
865
866 assert_eq!(
867 format!("{:?}", BuilderStep::Sampling { period: 100 }),
868 "Sampling { period: 100 }"
869 );
870
871 assert_eq!(
872 format!(
873 "{:?}",
874 BuilderStep::Resequence {
875 policy_config: Default::default(),
876 }
877 ),
878 "Resequence { policy_config: ResequencePolicyConfig { mode: Batch { correlation: \"header.id\", sort: \"header.id\", completion: SizeOrTimeout(100, 30000) } } }"
879 );
880
881 assert_eq!(
883 format!(
884 "{:?}",
885 BuilderStep::DeclarativeFilter {
886 predicate: expr.clone(),
887 steps: vec![BuilderStep::Stop],
888 }
889 ),
890 "DeclarativeFilter { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }"
891 );
892
893 assert_eq!(
894 format!(
895 "{:?}",
896 BuilderStep::DeclarativeSplit {
897 expression: expr.clone(),
898 aggregation: AggregationStrategy::Original,
899 parallel: false,
900 parallel_limit: Some(2),
901 stop_on_exception: true,
902 steps: vec![BuilderStep::Stop],
903 }
904 ),
905 "DeclarativeSplit { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, aggregation: Original, parallel: false, parallel_limit: Some(2), stop_on_exception: true, steps: [Stop] }"
906 );
907
908 assert_eq!(
909 format!(
910 "{:?}",
911 BuilderStep::Split {
912 config: camel_api::splitter::SplitterConfig::new(
913 camel_api::splitter::split_body_lines()
914 ),
915 steps: vec![BuilderStep::Stop],
916 }
917 ),
918 "Split { config: SplitterConfig { expression: \"<split-expression>\", aggregation: LastWins, parallel: false, parallel_limit: None, stop_on_exception: true, max_fragments: 100000 }, steps: [Stop] }"
919 );
920
921 assert_eq!(
922 format!(
923 "{:?}",
924 BuilderStep::Filter {
925 predicate: FilterPredicate::new(|_: &Exchange| true),
926 steps: vec![BuilderStep::Stop],
927 }
928 ),
929 "Filter { predicate: FilterPredicate(..), steps: [Stop] }"
930 );
931
932 assert_eq!(
933 format!(
934 "{:?}",
935 BuilderStep::Throttle {
936 config: camel_api::ThrottlerConfig::new(
937 10,
938 std::time::Duration::from_millis(10)
939 ),
940 steps: vec![BuilderStep::Stop],
941 }
942 ),
943 "Throttle { config: ThrottlerConfig { max_requests: 10, period: 10ms, strategy: Delay }, steps: [Stop] }"
944 );
945
946 assert_eq!(
947 format!(
948 "{:?}",
949 BuilderStep::LoadBalance {
950 config: camel_api::LoadBalancerConfig::round_robin(),
951 steps: vec![BuilderStep::To("mock:l1".into())],
952 }
953 ),
954 "LoadBalance { config: LoadBalancerConfig { strategy: RoundRobin }, steps: [To(\"mock:l1\")] }"
955 );
956
957 assert_eq!(
958 format!(
959 "{:?}",
960 BuilderStep::Delay {
961 config: camel_api::DelayConfig::new(500),
962 }
963 ),
964 "Delay { config: DelayConfig { delay_ms: 500, dynamic_header: None, max_delay_ms: 3600000 } }"
965 );
966
967 assert_eq!(
971 format!(
972 "{:?}",
973 BuilderStep::Choice {
974 whens: vec![WhenStep {
975 predicate: FilterPredicate::new(|_: &Exchange| true),
976 steps: vec![BuilderStep::To("mock:a".into())],
977 }],
978 otherwise: None,
979 }
980 ),
981 "Choice { whens: [WhenStep { predicate: FilterPredicate(..), steps: [To(\"mock:a\")] }], otherwise: None }"
982 );
983
984 assert_eq!(
985 format!(
986 "{:?}",
987 BuilderStep::DeclarativeChoice {
988 whens: vec![DeclarativeWhenStep {
989 predicate: expr.clone(),
990 steps: vec![BuilderStep::Stop],
991 }],
992 otherwise: Some(vec![BuilderStep::Stop]),
993 }
994 ),
995 "DeclarativeChoice { whens: [DeclarativeWhenStep { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }], otherwise: Some([Stop]) }"
996 );
997
998 assert_eq!(
1000 format!(
1001 "{:?}",
1002 BuilderStep::Multicast {
1003 steps: vec![BuilderStep::To("direct:a".into())],
1004 config: MulticastConfig::new(),
1005 }
1006 ),
1007 "Multicast { steps: [To(\"direct:a\")], config: MulticastConfig { parallel: false, parallel_limit: None, stop_on_exception: false, timeout: None, aggregation: LastWins } }"
1008 );
1009
1010 assert_eq!(
1012 format!(
1013 "{:?}",
1014 BuilderStep::DeclarativeDynamicRouter {
1015 expression: expr.clone(),
1016 uri_delimiter: ",".into(),
1017 cache_size: 1000,
1018 ignore_invalid_endpoints: false,
1019 max_iterations: 1000,
1020 }
1021 ),
1022 "DeclarativeDynamicRouter { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000 }"
1023 );
1024
1025 assert_eq!(
1026 format!(
1027 "{:?}",
1028 BuilderStep::DeclarativeRoutingSlip {
1029 expression: expr.clone(),
1030 uri_delimiter: ",".into(),
1031 cache_size: 1000,
1032 ignore_invalid_endpoints: false,
1033 }
1034 ),
1035 "DeclarativeRoutingSlip { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false }"
1036 );
1037
1038 assert_eq!(
1040 format!(
1041 "{:?}",
1042 BuilderStep::DeclarativeRecipientList {
1043 expression: expr.clone(),
1044 delimiter: ",".into(),
1045 parallel: false,
1046 parallel_limit: None,
1047 stop_on_exception: false,
1048 aggregation: "original".into(),
1049 }
1050 ),
1051 "DeclarativeRecipientList { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, aggregation: \"original\" }"
1052 );
1053
1054 assert_eq!(
1056 format!(
1057 "{:?}",
1058 BuilderStep::Loop {
1059 config: camel_api::loop_eip::LoopConfig::new(LoopMode::Count(3)),
1060 steps: vec![],
1061 }
1062 ),
1063 "Loop { config: LoopConfig { mode: Count(3), max_iterations: 10000 }, steps: [] }"
1064 );
1065
1066 assert_eq!(
1067 format!(
1068 "{:?}",
1069 BuilderStep::DeclarativeLoop {
1070 count: Some(5),
1071 while_predicate: None,
1072 steps: vec![],
1073 max_iterations: Some(100),
1074 }
1075 ),
1076 "DeclarativeLoop { count: Some(5), while_predicate: None, steps: [], max_iterations: Some(100) }"
1077 );
1078
1079 assert_eq!(
1081 format!(
1082 "{:?}",
1083 BuilderStep::ClaimCheck {
1084 repository: "myRepo".into(),
1085 operation: "checkout".into(),
1086 key: expr.clone(),
1087 filter: None,
1088 }
1089 ),
1090 "ClaimCheck { repository: \"myRepo\", operation: \"checkout\", key: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, filter: None }"
1091 );
1092
1093 assert_eq!(
1095 format!(
1096 "{:?}",
1097 BuilderStep::Sort {
1098 expression: expr.clone(),
1099 reverse: false,
1100 }
1101 ),
1102 "Sort { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, reverse: false }"
1103 );
1104
1105 assert_eq!(
1107 format!(
1108 "{:?}",
1109 BuilderStep::IdempotentConsumer {
1110 repository: "myRepo".into(),
1111 expression: expr.clone(),
1112 steps: vec![],
1113 eager: true,
1114 remove_on_failure: false,
1115 }
1116 ),
1117 "IdempotentConsumer { repository: \"myRepo\", expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [], eager: true, remove_on_failure: false }"
1118 );
1119
1120 assert_eq!(
1122 format!(
1123 "{:?}",
1124 BuilderStep::DeclarativeDoTry {
1125 try_steps: vec![BuilderStep::Stop],
1126 catch: vec![],
1127 finally: None,
1128 }
1129 ),
1130 "DeclarativeDoTry { try_steps: [Stop], catch: [], finally: None }"
1131 );
1132
1133 assert_eq!(
1135 format!(
1136 "{:?}",
1137 BuilderStep::DeclarativeStreamSplit {
1138 stream_config: StreamSplitConfig {
1139 format: StreamSplitFormat::Ndjson,
1140 max_record_bytes: 1024 * 1024,
1141 batch_size: 1,
1142 chunk_size: None,
1143 include_origin: true,
1144 },
1145 aggregation: AggregationStrategy::Original,
1146 stop_on_exception: true,
1147 steps: vec![BuilderStep::Stop],
1148 }
1149 ),
1150 "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] }"
1151 );
1152 }
1153
1154 #[test]
1155 fn test_builder_step_multicast_variant() {
1156 use camel_api::MulticastConfig;
1157
1158 let step = BuilderStep::Multicast {
1159 steps: vec![BuilderStep::To("direct:a".into())],
1160 config: MulticastConfig::new(),
1161 };
1162
1163 assert!(matches!(step, BuilderStep::Multicast { .. }));
1164 }
1165
1166 #[test]
1167 fn test_route_definition_defaults() {
1168 let def = RouteDefinition::new("direct:test", vec![]).with_route_id("test-route");
1169 assert_eq!(def.route_id(), "test-route");
1170 assert!(def.auto_startup());
1171 assert_eq!(def.startup_order(), 1000);
1172 }
1173
1174 #[test]
1175 fn test_route_definition_builders() {
1176 let def = RouteDefinition::new("direct:test", vec![])
1177 .with_route_id("my-route")
1178 .with_auto_startup(false)
1179 .with_startup_order(50);
1180 assert_eq!(def.route_id(), "my-route");
1181 assert!(!def.auto_startup());
1182 assert_eq!(def.startup_order(), 50);
1183 }
1184
1185 #[test]
1186 fn test_route_definition_accessors_cover_core_fields() {
1187 let def = RouteDefinition::new("direct:in", vec![BuilderStep::To("mock:out".into())])
1188 .with_route_id("accessor-route");
1189
1190 assert_eq!(def.from_uri(), "direct:in");
1191 assert_eq!(def.steps().len(), 1);
1192 assert!(matches!(def.steps()[0], BuilderStep::To(_)));
1193 }
1194
1195 #[test]
1196 fn test_route_definition_error_handler_circuit_breaker_and_concurrency_accessors() {
1197 use camel_api::circuit_breaker::CircuitBreakerConfig;
1198 use camel_api::error_handler::ErrorHandlerConfig;
1199 use camel_component_api::ConcurrencyModel;
1200
1201 let def = RouteDefinition::new("direct:test", vec![])
1202 .with_route_id("eh-route")
1203 .with_error_handler(ErrorHandlerConfig::dead_letter_channel("log:dlc"))
1204 .with_circuit_breaker(CircuitBreakerConfig::new())
1205 .with_concurrency(ConcurrencyModel::Concurrent { max: Some(4) });
1206
1207 let eh = def
1208 .error_handler_config()
1209 .expect("error handler should be set");
1210 assert_eq!(eh.dlc_uri.as_deref(), Some("log:dlc"));
1211 assert!(def.circuit_breaker_config().is_some());
1212 assert!(matches!(
1213 def.concurrency_override(),
1214 Some(ConcurrencyModel::Concurrent { max: Some(4) })
1215 ));
1216 }
1217
1218 #[test]
1219 fn test_builder_step_debug_covers_many_variants() {
1220 use camel_api::splitter::{AggregationStrategy, SplitterConfig, split_body_lines};
1221 use camel_api::{
1222 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, IdentityProcessor,
1223 OpaqueProcessor, RoutingSlipConfig, Value,
1224 };
1225 use std::sync::Arc;
1226
1227 let expr = LanguageExpressionDef {
1228 language: "simple".into(),
1229 source: "${body}".into(),
1230 };
1231
1232 let steps = vec![
1233 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor))),
1234 BuilderStep::To("mock:out".into()),
1235 BuilderStep::Stop,
1236 BuilderStep::Log {
1237 level: camel_processor::LogLevel::Info,
1238 message: "hello".into(),
1239 },
1240 BuilderStep::DeclarativeSetHeader {
1241 key: "k".into(),
1242 value: ValueSourceDef::Literal(Value::String("v".into())),
1243 },
1244 BuilderStep::DeclarativeSetBody {
1245 value: ValueSourceDef::Expression(expr.clone()),
1246 },
1247 BuilderStep::DeclarativeFilter {
1248 predicate: expr.clone(),
1249 steps: vec![BuilderStep::Stop],
1250 },
1251 BuilderStep::DeclarativeChoice {
1252 whens: vec![DeclarativeWhenStep {
1253 predicate: expr.clone(),
1254 steps: vec![BuilderStep::Stop],
1255 }],
1256 otherwise: Some(vec![BuilderStep::Stop]),
1257 },
1258 BuilderStep::DeclarativeScript {
1259 expression: expr.clone(),
1260 },
1261 BuilderStep::DeclarativeSplit {
1262 expression: expr.clone(),
1263 aggregation: AggregationStrategy::Original,
1264 parallel: false,
1265 parallel_limit: Some(2),
1266 stop_on_exception: true,
1267 steps: vec![BuilderStep::Stop],
1268 },
1269 BuilderStep::Split {
1270 config: SplitterConfig::new(split_body_lines()),
1271 steps: vec![BuilderStep::Stop],
1272 },
1273 BuilderStep::Aggregate {
1274 config: camel_api::AggregatorConfig::correlate_by("id")
1275 .complete_when_size(1)
1276 .build()
1277 .unwrap(),
1278 },
1279 BuilderStep::Filter {
1280 predicate: FilterPredicate::new(|_: &Exchange| true),
1281 steps: vec![BuilderStep::Stop],
1282 },
1283 BuilderStep::WireTap {
1284 uri: "mock:tap".into(),
1285 },
1286 BuilderStep::DeclarativeLog {
1287 level: camel_processor::LogLevel::Info,
1288 message: ValueSourceDef::Expression(expr.clone()),
1289 },
1290 BuilderStep::Bean {
1291 name: "bean".into(),
1292 method: "call".into(),
1293 },
1294 BuilderStep::Script {
1295 language: "rhai".into(),
1296 script: "body".into(),
1297 },
1298 BuilderStep::Throttle {
1299 config: camel_api::ThrottlerConfig::new(10, std::time::Duration::from_millis(10)),
1300 steps: vec![BuilderStep::Stop],
1301 },
1302 BuilderStep::LoadBalance {
1303 config: camel_api::LoadBalancerConfig::round_robin(),
1304 steps: vec![BuilderStep::To("mock:l1".into())],
1305 },
1306 BuilderStep::DynamicRouter {
1307 config: DynamicRouterConfig::new(Arc::new(|_| Some("mock:dr".into()))),
1308 },
1309 BuilderStep::RoutingSlip {
1310 config: RoutingSlipConfig::new(Arc::new(|_| Some("mock:rs".into()))),
1311 },
1312 ];
1313
1314 for step in steps {
1315 let dbg = format!("{step:?}");
1316 assert!(!dbg.is_empty());
1317 }
1318 }
1319
1320 #[test]
1321 fn test_route_definition_to_info_preserves_metadata() {
1322 let info = RouteDefinition::new("direct:test", vec![])
1323 .with_route_id("meta-route")
1324 .with_auto_startup(false)
1325 .with_startup_order(7)
1326 .to_info();
1327
1328 assert_eq!(info.route_id(), "meta-route");
1329 assert!(!info.auto_startup());
1330 assert_eq!(info.startup_order(), 7);
1331 }
1332
1333 #[test]
1334 fn test_choice_builder_step_debug() {
1335 use camel_api::FilterPredicate;
1336
1337 fn always_true(_: &camel_api::Exchange) -> bool {
1338 true
1339 }
1340
1341 let step = BuilderStep::Choice {
1342 whens: vec![WhenStep {
1343 predicate: FilterPredicate::new(always_true),
1344 steps: vec![BuilderStep::To("mock:a".into())],
1345 }],
1346 otherwise: None,
1347 };
1348 let debug = format!("{step:?}");
1349 assert!(debug.contains("Choice"));
1350 }
1351
1352 #[test]
1353 fn test_route_definition_unit_of_work() {
1354 use camel_api::UnitOfWorkConfig;
1355 let config = UnitOfWorkConfig {
1356 on_complete: Some("log:complete".into()),
1357 on_failure: Some("log:failed".into()),
1358 };
1359 let def = RouteDefinition::new("direct:test", vec![])
1360 .with_route_id("uow-test")
1361 .with_unit_of_work(config.clone());
1362 assert_eq!(
1363 def.unit_of_work_config().unwrap().on_complete.as_deref(),
1364 Some("log:complete")
1365 );
1366 assert_eq!(
1367 def.unit_of_work_config().unwrap().on_failure.as_deref(),
1368 Some("log:failed")
1369 );
1370
1371 let def_no_uow = RouteDefinition::new("direct:test", vec![]).with_route_id("no-uow");
1372 assert!(def_no_uow.unit_of_work_config().is_none());
1373 }
1374
1375 #[test]
1376 fn test_route_definition_security_policy_accessor() {
1377 use async_trait::async_trait;
1378 use camel_api::CamelError;
1379 use camel_api::Exchange;
1380 use camel_api::security_policy::{
1381 AuthContext, AuthorizationDecision, Principal, SecurityPolicy, SecurityPolicyConfig,
1382 };
1383
1384 struct StubPolicy;
1385 #[async_trait]
1386 impl SecurityPolicy for StubPolicy {
1387 async fn evaluate(
1388 &self,
1389 _exchange: &mut Exchange,
1390 _auth: &AuthContext<'_>,
1391 ) -> Result<AuthorizationDecision, CamelError> {
1392 Ok(AuthorizationDecision::Granted {
1393 principal: Principal {
1394 subject: "test".into(),
1395 issuer: "test".into(),
1396 audience: vec![],
1397 scopes: vec![],
1398 roles: vec![],
1399 claims: serde_json::Value::Null,
1400 },
1401 })
1402 }
1403 }
1404
1405 let def_no_sp = RouteDefinition::new("direct:test", vec![]).with_route_id("no-sp");
1406 assert!(def_no_sp.security_policy_config().is_none());
1407
1408 let def = RouteDefinition::new("direct:test", vec![])
1409 .with_route_id("sp-test")
1410 .with_security_policy(SecurityPolicyConfig::new(StubPolicy));
1411 assert!(def.security_policy_config().is_some());
1412 }
1413
1414 #[test]
1415 fn test_route_definition_security_authenticator_accessor() {
1416 use camel_api::security_policy::Principal;
1417
1418 struct TestAuth;
1419 #[async_trait::async_trait]
1420 impl TokenAuthenticator for TestAuth {
1421 async fn authenticate_bearer(
1422 &self,
1423 _token: &str,
1424 ) -> Result<Principal, camel_api::CamelError> {
1425 Ok(Principal {
1426 subject: "test".into(),
1427 issuer: "test".into(),
1428 audience: vec![],
1429 scopes: vec![],
1430 roles: vec![],
1431 claims: serde_json::Value::Null,
1432 })
1433 }
1434 }
1435
1436 let def_no_auth = RouteDefinition::new("direct:test".to_string(), vec![]);
1437 assert!(def_no_auth.security_authenticator().is_none());
1438
1439 let auth = Arc::new(TestAuth);
1440 let def = RouteDefinition::new("direct:test".to_string(), vec![])
1441 .with_security_authenticator(auth);
1442 assert!(def.security_authenticator().is_some());
1443 }
1444
1445 #[test]
1446 fn test_map_steps_swaps_steps_and_preserves_other_fields() {
1447 let original = RouteDefinition::new(
1448 "direct:test".to_string(),
1449 vec![BuilderStep::To("mock:a".into()), BuilderStep::Stop],
1450 )
1451 .with_route_id("my-route");
1452
1453 let mapped = original.map_steps(|steps| {
1454 let mut out = Vec::with_capacity(steps.len() + 1);
1455 out.push(BuilderStep::To("mock:prefix".into()));
1456 out.extend(steps);
1457 out
1458 });
1459
1460 assert_eq!(mapped.steps().len(), 3);
1462 assert!(matches!(mapped.steps()[0], BuilderStep::To(ref s) if s == "mock:prefix"));
1463 assert!(matches!(mapped.steps()[1], BuilderStep::To(ref s) if s == "mock:a"));
1464 assert_eq!(mapped.route_id(), "my-route");
1466 }
1467
1468 #[test]
1469 fn circuit_breaker_fallback_accessor_returns_steps() {
1470 let def = RouteDefinition::new("direct:start", vec![])
1471 .with_circuit_breaker_fallback(vec![BuilderStep::To("mock:out".into())]);
1472 assert_eq!(def.circuit_breaker_fallback().len(), 1);
1473 }
1474}