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 SpanKindHint, 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 trace_item_threshold: Option<usize>,
122 stop_on_exception: bool,
123 steps: Vec<BuilderStep>,
124 },
125 DeclarativeStreamSplit {
127 stream_config: camel_api::StreamSplitConfig,
128 aggregation: camel_api::splitter::AggregationStrategy,
129 stop_on_exception: bool,
130 steps: Vec<BuilderStep>,
131 },
132 DeclarativeDynamicRouter {
133 expression: LanguageExpressionDef,
134 uri_delimiter: String,
135 cache_size: i32,
136 ignore_invalid_endpoints: bool,
137 max_iterations: usize,
138 },
139 DeclarativeRoutingSlip {
140 expression: LanguageExpressionDef,
141 uri_delimiter: String,
142 cache_size: i32,
143 ignore_invalid_endpoints: bool,
144 },
145 Split {
147 config: SplitterConfig,
148 steps: Vec<BuilderStep>,
149 },
150 Aggregate {
152 config: AggregatorConfig,
153 },
154 Filter {
156 predicate: FilterPredicate,
157 steps: Vec<BuilderStep>,
158 },
159 Choice {
162 whens: Vec<WhenStep>,
163 otherwise: Option<Vec<BuilderStep>>,
164 },
165 WireTap {
167 uri: String,
168 },
169 Multicast {
171 steps: Vec<BuilderStep>,
172 config: MulticastConfig,
173 },
174 DeclarativeLog {
176 level: camel_processor::LogLevel,
177 message: ValueSourceDef,
178 },
179 Bean {
181 name: String,
182 method: String,
183 },
184 Script {
187 language: String,
188 script: String,
189 },
190 Throttle {
192 config: camel_api::ThrottlerConfig,
193 steps: Vec<BuilderStep>,
194 },
195 LoadBalance {
197 config: camel_api::LoadBalancerConfig,
198 steps: Vec<BuilderStep>,
199 },
200 DynamicRouter {
202 config: camel_api::DynamicRouterConfig,
203 },
204 RoutingSlip {
205 config: camel_api::RoutingSlipConfig,
206 },
207 RecipientList {
208 config: camel_api::recipient_list::RecipientListConfig,
209 },
210 DeclarativeRecipientList {
211 expression: LanguageExpressionDef,
212 delimiter: String,
213 parallel: bool,
214 parallel_limit: Option<usize>,
215 stop_on_exception: bool,
216 aggregation: String,
217 },
218 Delay {
219 config: camel_api::DelayConfig,
220 },
221 Loop {
223 config: LoopConfig,
224 steps: Vec<BuilderStep>,
225 },
226 DeclarativeLoop {
228 count: Option<usize>,
229 while_predicate: Option<LanguageExpressionDef>,
230 steps: Vec<BuilderStep>,
231 max_iterations: Option<usize>,
232 },
233 Enrich {
235 uri: String,
236 strategy: Option<String>,
237 timeout_ms: Option<u64>,
238 },
239 PollEnrich {
241 uri: String,
242 strategy: Option<String>,
243 timeout_ms: Option<u64>,
244 },
245 Validate {
248 predicate: LanguageExpressionDef,
249 },
250 ClaimCheck {
254 repository: String,
255 operation: String,
256 key: LanguageExpressionDef,
257 filter: Option<String>,
258 },
259 Sampling {
263 period: usize,
264 },
265 Sort {
268 expression: LanguageExpressionDef,
269 reverse: bool,
270 },
271 IdempotentConsumer {
275 repository: String,
276 expression: LanguageExpressionDef,
277 steps: Vec<BuilderStep>,
278 eager: bool,
279 remove_on_failure: bool,
280 },
281 Cache {
285 repository: Option<String>,
286 key: LanguageExpressionDef,
287 ttl: Option<String>,
288 max_entry_bytes: Option<usize>,
289 coalesce_misses: bool,
291 on_miss: Vec<BuilderStep>,
292 },
293 CacheInvalidate {
298 repository: Option<String>,
299 key: Option<LanguageExpressionDef>,
300 key_prefix: Option<LanguageExpressionDef>,
301 },
302 CacheClear {
305 repository: Option<String>,
306 },
307 CacheStats {
310 repository: Option<String>,
311 },
312 CachePeekStale {
315 repository: Option<String>,
316 key: LanguageExpressionDef,
317 on_miss: camel_processor::PeekStaleMissPolicy,
318 },
319 DeclarativeDoTry {
321 try_steps: Vec<BuilderStep>,
322 catch: Vec<DoTryCatchClauseBuilder>,
323 finally: Option<DoTryFinallyBuilder>,
324 },
325 Resequence {
328 policy_config: ResequencePolicyConfig,
329 },
330}
331
332impl BuilderStep {
333 pub(crate) fn span_label(&self) -> Option<String> {
347 match self {
348 Self::Processor(_) | Self::Stop => None,
350
351 Self::To(uri) => uri.contains(':').then(|| {
352 let scheme = uri.split(':').next().unwrap_or_default();
353 format!("to:{scheme}")
354 }),
355
356 Self::Log { .. } | Self::DeclarativeLog { .. } => Some("log".into()),
357
358 Self::DeclarativeSetHeader { .. } => Some("set-header".into()),
359 Self::DeclarativeSetHeaderIfAbsent { .. } => Some("set-header-if-absent".into()),
360 Self::DeclarativeRemoveHeader { .. } => Some("remove-header".into()),
361 Self::DeclarativeSetProperty { .. } => Some("set-property".into()),
362 Self::DeclarativeSetBody { .. } => Some("set-body".into()),
363
364 Self::DeclarativeFilter { .. } | Self::Filter { .. } => Some("filter".into()),
365 Self::DeclarativeChoice { .. } | Self::Choice { .. } => Some("choice".into()),
366 Self::DeclarativeScript { .. } | Self::Script { .. } => Some("script".into()),
367 Self::DeclarativeFunction { .. } => Some("function".into()),
368
369 Self::DeclarativeSplit { .. }
370 | Self::DeclarativeStreamSplit { .. }
371 | Self::Split { .. } => Some("split".into()),
372
373 Self::DeclarativeDynamicRouter { .. } | Self::DynamicRouter { .. } => {
374 Some("dynamic-router".into())
375 }
376 Self::DeclarativeRoutingSlip { .. } | Self::RoutingSlip { .. } => {
377 Some("routing-slip".into())
378 }
379 Self::DeclarativeRecipientList { .. } | Self::RecipientList { .. } => {
380 Some("recipient-list".into())
381 }
382
383 Self::Aggregate { .. } => Some("aggregate".into()),
384 Self::WireTap { .. } => Some("wire-tap".into()),
385 Self::Multicast { .. } => Some("multicast".into()),
386 Self::Bean { .. } => Some("bean".into()),
387 Self::Throttle { .. } => Some("throttle".into()),
388 Self::LoadBalance { .. } => Some("load-balance".into()),
389 Self::Delay { .. } => Some("delay".into()),
390 Self::Loop { .. } | Self::DeclarativeLoop { .. } => Some("loop".into()),
391 Self::Enrich { .. } => Some("enrich".into()),
392 Self::PollEnrich { .. } => Some("poll-enrich".into()),
393 Self::Validate { .. } => Some("validate".into()),
394 Self::ClaimCheck { .. } => Some("claim-check".into()),
395 Self::Sampling { .. } => Some("sampling".into()),
396 Self::Sort { .. } => Some("sort".into()),
397 Self::IdempotentConsumer { .. } => Some("idempotent-consumer".into()),
398 Self::Cache { .. } => Some("cache".into()),
399 Self::CacheInvalidate { .. } => Some("cache-invalidate".into()),
400 Self::CacheClear { .. } => Some("cache-clear".into()),
401 Self::CacheStats { .. } => Some("cache-stats".into()),
402 Self::CachePeekStale { .. } => Some("cache-peek-stale".into()),
403 Self::DeclarativeDoTry { .. } => Some("do-try".into()),
404 Self::Resequence { .. } => Some("resequence".into()),
405 }
406 }
407
408 pub(crate) fn to_uri_metadata(&self) -> Option<Arc<str>> {
419 match self {
420 Self::To(uri) => Some(Arc::from(uri.as_str())),
421
422 Self::Processor(..)
426 | Self::Stop
427 | Self::Log { .. }
428 | Self::DeclarativeSetHeader { .. }
429 | Self::DeclarativeSetHeaderIfAbsent { .. }
430 | Self::DeclarativeRemoveHeader { .. }
431 | Self::DeclarativeSetProperty { .. }
432 | Self::DeclarativeSetBody { .. }
433 | Self::DeclarativeFilter { .. }
434 | Self::DeclarativeChoice { .. }
435 | Self::DeclarativeScript { .. }
436 | Self::DeclarativeFunction { .. }
437 | Self::DeclarativeSplit { .. }
438 | Self::DeclarativeStreamSplit { .. }
439 | Self::DeclarativeDynamicRouter { .. }
440 | Self::DeclarativeRoutingSlip { .. }
441 | Self::Split { .. }
442 | Self::Aggregate { .. }
443 | Self::Filter { .. }
444 | Self::Choice { .. }
445 | Self::Multicast { .. }
446 | Self::DeclarativeLog { .. }
447 | Self::Bean { .. }
448 | Self::Script { .. }
449 | Self::Throttle { .. }
450 | Self::LoadBalance { .. }
451 | Self::DynamicRouter { .. }
452 | Self::RoutingSlip { .. }
453 | Self::RecipientList { .. }
454 | Self::DeclarativeRecipientList { .. }
455 | Self::Delay { .. }
456 | Self::Loop { .. }
457 | Self::DeclarativeLoop { .. }
458 | Self::Enrich { .. }
459 | Self::PollEnrich { .. }
460 | Self::WireTap { .. }
461 | Self::Validate { .. }
462 | Self::ClaimCheck { .. }
463 | Self::Sampling { .. }
464 | Self::Sort { .. }
465 | Self::IdempotentConsumer { .. }
466 | Self::Cache { .. }
467 | Self::CacheInvalidate { .. }
468 | Self::CacheClear { .. }
469 | Self::CacheStats { .. }
470 | Self::CachePeekStale { .. }
471 | Self::DeclarativeDoTry { .. }
472 | Self::Resequence { .. } => None,
473 }
474 }
475
476 pub(crate) fn span_kind_hint(&self) -> SpanKindHint {
492 match self {
493 Self::To(uri)
495 | Self::Enrich { uri, .. }
496 | Self::PollEnrich { uri, .. }
497 | Self::WireTap { uri, .. } => uri_span_kind(uri),
498
499 Self::Processor(..)
503 | Self::Stop
504 | Self::Log { .. }
505 | Self::DeclarativeSetHeader { .. }
506 | Self::DeclarativeSetHeaderIfAbsent { .. }
507 | Self::DeclarativeRemoveHeader { .. }
508 | Self::DeclarativeSetProperty { .. }
509 | Self::DeclarativeSetBody { .. }
510 | Self::DeclarativeFilter { .. }
511 | Self::DeclarativeChoice { .. }
512 | Self::DeclarativeScript { .. }
513 | Self::DeclarativeFunction { .. }
514 | Self::DeclarativeSplit { .. }
515 | Self::DeclarativeStreamSplit { .. }
516 | Self::DeclarativeDynamicRouter { .. }
517 | Self::DeclarativeRoutingSlip { .. }
518 | Self::Split { .. }
519 | Self::Aggregate { .. }
520 | Self::Filter { .. }
521 | Self::Choice { .. }
522 | Self::Multicast { .. }
523 | Self::DeclarativeLog { .. }
524 | Self::Bean { .. }
525 | Self::Script { .. }
526 | Self::Throttle { .. }
527 | Self::LoadBalance { .. }
528 | Self::DynamicRouter { .. }
529 | Self::RoutingSlip { .. }
530 | Self::RecipientList { .. }
531 | Self::DeclarativeRecipientList { .. }
532 | Self::Delay { .. }
533 | Self::Loop { .. }
534 | Self::DeclarativeLoop { .. }
535 | Self::Validate { .. }
536 | Self::ClaimCheck { .. }
537 | Self::Sampling { .. }
538 | Self::Sort { .. }
539 | Self::IdempotentConsumer { .. }
540 | Self::Cache { .. }
541 | Self::CacheInvalidate { .. }
542 | Self::CacheClear { .. }
543 | Self::CacheStats { .. }
544 | Self::CachePeekStale { .. }
545 | Self::DeclarativeDoTry { .. }
546 | Self::Resequence { .. } => SpanKindHint::Internal,
547 }
548 }
549}
550
551fn uri_span_kind(uri: &str) -> SpanKindHint {
558 const PRODUCER_SCHEMES: [&str; 5] = ["kafka", "jms", "activemq", "artemis", "mqtt"];
560 const CLIENT_SCHEMES: [&str; 12] = [
562 "http",
563 "https",
564 "grpc",
565 "grpcs",
566 "ws",
567 "redis",
568 "opensearch",
569 "sql",
570 "surrealdb",
571 "cxf",
572 "llm",
573 "mcp",
574 ];
575
576 let scheme = uri.split(':').next().unwrap_or_default();
577 if PRODUCER_SCHEMES
578 .iter()
579 .any(|s| s.eq_ignore_ascii_case(scheme))
580 {
581 SpanKindHint::Producer
582 } else if CLIENT_SCHEMES
583 .iter()
584 .any(|s| s.eq_ignore_ascii_case(scheme))
585 {
586 SpanKindHint::Client
587 } else {
588 SpanKindHint::Internal
589 }
590}
591
592pub struct RouteDefinition {
594 pub(crate) from_uri: String,
595 pub(crate) steps: Vec<BuilderStep>,
596 pub(crate) error_handler: Option<ErrorHandlerConfig>,
598 pub(crate) circuit_breaker: Option<CircuitBreakerConfig>,
600 pub(crate) circuit_breaker_fallback: Vec<BuilderStep>,
608 pub(crate) security_policy: Option<SecurityPolicyConfig>,
609 pub(crate) security_authenticator: Option<Arc<dyn TokenAuthenticator>>,
611 pub(crate) provider_registry: Option<Arc<camel_auth::ProviderRegistry>>,
614 pub(crate) security_provider: Option<String>,
618 pub(crate) security_audiences: Option<Vec<String>>,
623 pub(crate) unit_of_work: Option<UnitOfWorkConfig>,
625 pub(crate) concurrency: Option<ConcurrencyModel>,
628 pub(crate) route_id: String,
630 pub(crate) auto_startup: bool,
632 pub(crate) startup_order: i32,
634 pub(crate) source_hash: Option<u64>,
635}
636
637impl RouteDefinition {
638 pub fn new(from_uri: impl Into<String>, steps: Vec<BuilderStep>) -> Self {
640 Self {
641 from_uri: from_uri.into(),
642 steps,
643 error_handler: None,
644 circuit_breaker: None,
645 circuit_breaker_fallback: Vec::new(),
646 security_policy: None,
647 security_authenticator: None,
648 provider_registry: None,
649 security_provider: None,
650 security_audiences: None,
651 unit_of_work: None,
652 concurrency: None,
653 route_id: String::new(), auto_startup: true,
655 startup_order: 1000,
656 source_hash: None,
657 }
658 }
659
660 pub fn from_uri(&self) -> &str {
662 &self.from_uri
663 }
664
665 pub fn steps(&self) -> &[BuilderStep] {
667 &self.steps
668 }
669
670 pub fn circuit_breaker_fallback(&self) -> &[BuilderStep] {
671 &self.circuit_breaker_fallback
672 }
673
674 pub fn map_steps(mut self, f: impl FnOnce(Vec<BuilderStep>) -> Vec<BuilderStep>) -> Self {
679 self.steps = f(self.steps);
680 self
681 }
682
683 pub fn with_error_handler(mut self, config: ErrorHandlerConfig) -> Self {
685 self.error_handler = Some(config);
686 self
687 }
688
689 pub fn error_handler_config(&self) -> Option<&ErrorHandlerConfig> {
691 self.error_handler.as_ref()
692 }
693
694 pub fn with_circuit_breaker(mut self, config: CircuitBreakerConfig) -> Self {
696 self.circuit_breaker = Some(config);
697 self
698 }
699
700 pub fn with_circuit_breaker_fallback(mut self, steps: Vec<BuilderStep>) -> Self {
707 self.circuit_breaker_fallback = steps;
708 self
709 }
710
711 pub fn with_security_policy(mut self, config: SecurityPolicyConfig) -> Self {
713 self.security_policy = Some(config);
714 self
715 }
716
717 pub fn with_security_authenticator(
719 mut self,
720 authenticator: Arc<dyn TokenAuthenticator>,
721 ) -> Self {
722 self.security_authenticator = Some(authenticator);
723 self
724 }
725
726 pub fn with_provider_registry(mut self, registry: Arc<camel_auth::ProviderRegistry>) -> Self {
732 self.provider_registry = Some(registry);
733 self
734 }
735
736 pub fn with_security_provider(mut self, name: impl Into<String>) -> Self {
738 self.security_provider = Some(name.into());
739 self
740 }
741
742 pub fn with_security_audiences(mut self, audiences: Vec<String>) -> Self {
744 self.security_audiences = Some(audiences);
745 self
746 }
747
748 pub fn security_provider(&self) -> Option<&str> {
750 self.security_provider.as_deref()
751 }
752
753 pub fn security_audiences(&self) -> Option<&[String]> {
755 self.security_audiences.as_deref()
756 }
757
758 pub fn with_unit_of_work(mut self, config: UnitOfWorkConfig) -> Self {
760 self.unit_of_work = Some(config);
761 self
762 }
763
764 pub fn unit_of_work_config(&self) -> Option<&UnitOfWorkConfig> {
766 self.unit_of_work.as_ref()
767 }
768
769 pub fn circuit_breaker_config(&self) -> Option<&CircuitBreakerConfig> {
771 self.circuit_breaker.as_ref()
772 }
773
774 pub fn security_policy_config(&self) -> Option<&SecurityPolicyConfig> {
775 self.security_policy.as_ref()
776 }
777
778 pub fn security_authenticator(&self) -> Option<&Arc<dyn TokenAuthenticator>> {
779 self.security_authenticator.as_ref()
780 }
781
782 pub fn concurrency_override(&self) -> Option<&ConcurrencyModel> {
784 self.concurrency.as_ref()
785 }
786
787 pub fn with_concurrency(mut self, model: ConcurrencyModel) -> Self {
789 self.concurrency = Some(model);
790 self
791 }
792
793 pub fn route_id(&self) -> &str {
795 &self.route_id
796 }
797
798 pub fn auto_startup(&self) -> bool {
800 self.auto_startup
801 }
802
803 pub fn startup_order(&self) -> i32 {
805 self.startup_order
806 }
807
808 pub fn with_route_id(mut self, id: impl Into<String>) -> Self {
810 self.route_id = id.into();
811 self
812 }
813
814 pub fn with_auto_startup(mut self, auto: bool) -> Self {
816 self.auto_startup = auto;
817 self
818 }
819
820 pub fn with_startup_order(mut self, order: i32) -> Self {
822 self.startup_order = order;
823 self
824 }
825
826 pub fn with_source_hash(mut self, hash: u64) -> Self {
827 self.source_hash = Some(hash);
828 self
829 }
830
831 pub fn source_hash(&self) -> Option<u64> {
832 self.source_hash
833 }
834
835 pub fn to_info(&self) -> RouteDefinitionInfo {
838 RouteDefinitionInfo {
839 route_id: self.route_id.clone(),
840 auto_startup: self.auto_startup,
841 startup_order: self.startup_order,
842 source_hash: self.source_hash,
843 }
844 }
845}
846
847#[derive(Clone)]
853pub struct RouteDefinitionInfo {
854 route_id: String,
855 auto_startup: bool,
856 startup_order: i32,
857 pub(crate) source_hash: Option<u64>,
858}
859
860impl RouteDefinitionInfo {
861 pub fn route_id(&self) -> &str {
863 &self.route_id
864 }
865
866 pub fn auto_startup(&self) -> bool {
868 self.auto_startup
869 }
870
871 pub fn startup_order(&self) -> i32 {
873 self.startup_order
874 }
875
876 pub fn source_hash(&self) -> Option<u64> {
877 self.source_hash
878 }
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884 use camel_api::{RecipientSource, TargetSource};
885
886 #[test]
893 fn builder_step_span_label_mapping() {
894 use camel_api::declarative::LanguageExpressionDef;
895 use camel_api::splitter::AggregationStrategy;
896 use camel_api::{BoxProcessor, IdentityProcessor, OpaqueProcessor};
897
898 let expr = LanguageExpressionDef {
899 language: "simple".into(),
900 source: "${body}".into(),
901 };
902
903 assert_eq!(
905 BuilderStep::To("direct:tree-sub".into())
906 .span_label()
907 .as_deref(),
908 Some("to:direct")
909 );
910 assert_eq!(
911 BuilderStep::To("http://api.example/x".into())
912 .span_label()
913 .as_deref(),
914 Some("to:http")
915 );
916 assert_eq!(BuilderStep::To("garbage".into()).span_label(), None);
918
919 assert_eq!(
921 BuilderStep::Log {
922 level: camel_processor::LogLevel::Info,
923 message: "m".into(),
924 }
925 .span_label()
926 .as_deref(),
927 Some("log")
928 );
929 assert_eq!(
930 BuilderStep::Split {
931 config: camel_api::splitter::SplitterConfig::new(
932 camel_api::splitter::split_body_lines()
933 ),
934 steps: vec![BuilderStep::Stop],
935 }
936 .span_label()
937 .as_deref(),
938 Some("split")
939 );
940 assert_eq!(
941 BuilderStep::DeclarativeSplit {
942 expression: expr.clone(),
943 aggregation: AggregationStrategy::Original,
944 parallel: false,
945 parallel_limit: None,
946 trace_item_threshold: None,
947 stop_on_exception: true,
948 steps: vec![BuilderStep::Stop],
949 }
950 .span_label()
951 .as_deref(),
952 Some("split")
953 );
954 assert_eq!(
955 BuilderStep::DeclarativeStreamSplit {
956 stream_config: camel_api::StreamSplitConfig::default(),
957 aggregation: AggregationStrategy::Original,
958 stop_on_exception: true,
959 steps: vec![BuilderStep::Stop],
960 }
961 .span_label()
962 .as_deref(),
963 Some("split")
964 );
965 assert_eq!(BuilderStep::Stop.span_label(), None);
966
967 assert_eq!(
969 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor)))
970 .span_label(),
971 None
972 );
973 }
974
975 #[test]
987 fn builder_step_span_kind_hint_mapping() {
988 use camel_api::splitter::split_body_lines;
989 use camel_api::{Exchange, FilterPredicate, SpanKindHint};
990
991 let kind = |uri: &str| BuilderStep::To(uri.into()).span_kind_hint();
992
993 assert_eq!(kind("kafka:orders"), SpanKindHint::Producer);
995 assert_eq!(kind("jms:q"), SpanKindHint::Producer);
996 assert_eq!(kind("activemq:q"), SpanKindHint::Producer);
997 assert_eq!(kind("artemis:q"), SpanKindHint::Producer);
998 assert_eq!(kind("mqtt:t"), SpanKindHint::Producer);
999 assert_eq!(kind("KAFKA:orders"), SpanKindHint::Producer);
1001
1002 assert_eq!(kind("http://x"), SpanKindHint::Client);
1004 assert_eq!(kind("https://x"), SpanKindHint::Client);
1005 assert_eq!(kind("grpc://x"), SpanKindHint::Client);
1006 assert_eq!(kind("grpcs://x"), SpanKindHint::Client);
1007 assert_eq!(kind("ws://x"), SpanKindHint::Client);
1008 assert_eq!(kind("redis://x"), SpanKindHint::Client);
1009 assert_eq!(kind("opensearch://x"), SpanKindHint::Client);
1010 assert_eq!(kind("sql:db"), SpanKindHint::Client);
1011 assert_eq!(kind("surrealdb://x"), SpanKindHint::Client);
1012 assert_eq!(kind("cxf://x"), SpanKindHint::Client);
1013 assert_eq!(kind("llm://x"), SpanKindHint::Client);
1014 assert_eq!(kind("mcp://x"), SpanKindHint::Client);
1015
1016 assert_eq!(kind("direct:y"), SpanKindHint::Internal);
1018 assert_eq!(kind("timer:z"), SpanKindHint::Internal);
1019 assert_eq!(kind("garbage"), SpanKindHint::Internal);
1021
1022 assert_eq!(
1024 BuilderStep::Log {
1025 level: camel_processor::LogLevel::Info,
1026 message: "m".into(),
1027 }
1028 .span_kind_hint(),
1029 SpanKindHint::Internal
1030 );
1031 assert_eq!(
1032 BuilderStep::Filter {
1033 predicate: FilterPredicate::new(|_: &Exchange| true),
1034 steps: vec![BuilderStep::Stop],
1035 }
1036 .span_kind_hint(),
1037 SpanKindHint::Internal
1038 );
1039 assert_eq!(
1040 BuilderStep::Split {
1041 config: camel_api::splitter::SplitterConfig::new(split_body_lines()),
1042 steps: vec![BuilderStep::Stop],
1043 }
1044 .span_kind_hint(),
1045 SpanKindHint::Internal
1046 );
1047
1048 let enrich = |uri: &str| BuilderStep::Enrich {
1052 uri: uri.into(),
1053 strategy: None,
1054 timeout_ms: None,
1055 };
1056 let poll_enrich = |uri: &str| BuilderStep::PollEnrich {
1057 uri: uri.into(),
1058 strategy: None,
1059 timeout_ms: None,
1060 };
1061 let wire_tap = |uri: &str| BuilderStep::WireTap { uri: uri.into() };
1062
1063 assert_eq!(enrich("http://x").span_kind_hint(), SpanKindHint::Client);
1065 assert_eq!(
1066 poll_enrich("http://x").span_kind_hint(),
1067 SpanKindHint::Client
1068 );
1069 assert_eq!(wire_tap("http://x").span_kind_hint(), SpanKindHint::Client);
1070 assert_eq!(
1072 enrich("kafka:orders").span_kind_hint(),
1073 SpanKindHint::Producer
1074 );
1075 assert_eq!(
1076 poll_enrich("kafka:orders").span_kind_hint(),
1077 SpanKindHint::Producer
1078 );
1079 assert_eq!(
1080 wire_tap("kafka:orders").span_kind_hint(),
1081 SpanKindHint::Producer
1082 );
1083 assert_eq!(enrich("direct:y").span_kind_hint(), SpanKindHint::Internal);
1085 assert_eq!(
1086 poll_enrich("seda:q").span_kind_hint(),
1087 SpanKindHint::Internal
1088 );
1089 assert_eq!(
1090 wire_tap("direct:y").span_kind_hint(),
1091 SpanKindHint::Internal
1092 );
1093 }
1094
1095 #[test]
1100 fn golden_debug_output_all_variants() {
1101 use camel_api::declarative::LanguageExpressionDef;
1102 use camel_api::loop_eip::LoopMode;
1103 use camel_api::recipient_list::RecipientListConfig;
1104 use camel_api::splitter::{AggregationStrategy, StreamSplitConfig, StreamSplitFormat};
1105 use camel_api::{
1106 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, FunctionDefinition,
1107 FunctionId, IdentityProcessor, MulticastConfig, OpaqueProcessor, RoutingSlipConfig,
1108 Value,
1109 };
1110 use std::sync::Arc;
1111
1112 let expr = LanguageExpressionDef {
1113 language: "simple".into(),
1114 source: "${body}".into(),
1115 };
1116
1117 assert_eq!(format!("{:?}", BuilderStep::Stop), "Stop");
1120 assert_eq!(
1121 format!(
1122 "{:?}",
1123 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor)))
1124 ),
1125 "Processor(BoxProcessor(...))"
1126 );
1127 assert_eq!(
1128 format!("{:?}", BuilderStep::To("mock:out".into())),
1129 "To(\"mock:out\")"
1130 );
1131
1132 assert_eq!(
1135 format!(
1136 "{:?}",
1137 BuilderStep::Log {
1138 level: camel_processor::LogLevel::Info,
1139 message: "hello".into(),
1140 }
1141 ),
1142 "Log { level: Info, message: \"hello\" }"
1143 );
1144
1145 assert_eq!(
1146 format!(
1147 "{:?}",
1148 BuilderStep::DeclarativeSetHeader {
1149 key: "k".into(),
1150 value: ValueSourceDef::Literal(Value::String("v".into())),
1151 }
1152 ),
1153 "DeclarativeSetHeader { key: \"k\", value: Literal(String(\"v\")) }"
1154 );
1155
1156 assert_eq!(
1157 format!(
1158 "{:?}",
1159 BuilderStep::DeclarativeSetHeaderIfAbsent {
1160 key: "k".into(),
1161 value: ValueSourceDef::Literal(Value::String("v".into())),
1162 }
1163 ),
1164 "DeclarativeSetHeaderIfAbsent { key: \"k\", value: Literal(String(\"v\")) }"
1165 );
1166
1167 assert_eq!(
1168 format!(
1169 "{:?}",
1170 BuilderStep::DeclarativeSetBody {
1171 value: ValueSourceDef::Literal(Value::String("v".into())),
1172 }
1173 ),
1174 "DeclarativeSetBody { value: Literal(String(\"v\")) }"
1175 );
1176
1177 assert_eq!(
1178 format!(
1179 "{:?}",
1180 BuilderStep::DeclarativeSetProperty {
1181 key: "prop".into(),
1182 value_source: ValueSourceDef::Literal(Value::String("v".into())),
1183 }
1184 ),
1185 "DeclarativeSetProperty { key: \"prop\", value_source: Literal(String(\"v\")) }"
1186 );
1187
1188 assert_eq!(
1189 format!(
1190 "{:?}",
1191 BuilderStep::DeclarativeScript {
1192 expression: expr.clone(),
1193 }
1194 ),
1195 "DeclarativeScript { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
1196 );
1197
1198 let func_def = FunctionDefinition {
1200 id: FunctionId("test-id".into()),
1201 runtime: "my_runtime".into(),
1202 source: "${body}".into(),
1203 timeout_ms: 5000,
1204 route_id: None,
1205 step_index: None,
1206 };
1207 assert_eq!(
1208 format!(
1209 "{:?}",
1210 BuilderStep::DeclarativeFunction {
1211 definition: func_def,
1212 }
1213 ),
1214 "DeclarativeFunction { definition: FunctionDefinition { id: FunctionId(\"test-id\"), runtime: \"my_runtime\", source: \"${body}\", timeout_ms: 5000, route_id: None, step_index: None } }"
1215 );
1216
1217 assert_eq!(
1218 format!(
1219 "{:?}",
1220 BuilderStep::WireTap {
1221 uri: "mock:tap".into(),
1222 }
1223 ),
1224 "WireTap { uri: \"mock:tap\" }"
1225 );
1226
1227 assert_eq!(
1228 format!(
1229 "{:?}",
1230 BuilderStep::DeclarativeLog {
1231 level: camel_processor::LogLevel::Info,
1232 message: ValueSourceDef::Expression(expr.clone()),
1233 }
1234 ),
1235 "DeclarativeLog { level: Info, message: Expression(LanguageExpressionDef { language: \"simple\", source: \"${body}\" }) }"
1236 );
1237
1238 assert_eq!(
1239 format!(
1240 "{:?}",
1241 BuilderStep::Bean {
1242 name: "myBean".into(),
1243 method: "process".into(),
1244 }
1245 ),
1246 "Bean { name: \"myBean\", method: \"process\" }"
1247 );
1248
1249 assert_eq!(
1250 format!(
1251 "{:?}",
1252 BuilderStep::Script {
1253 language: "js".into(),
1254 script: "body".into(),
1255 }
1256 ),
1257 "Script { language: \"js\", script: \"body\" }"
1258 );
1259
1260 assert_eq!(
1261 format!(
1262 "{:?}",
1263 BuilderStep::Aggregate {
1264 config: camel_api::AggregatorConfig::correlate_by("id")
1265 .complete_when_size(1)
1266 .build()
1267 .unwrap(),
1268 }
1269 ),
1270 "Aggregate { config: AggregatorConfig { header_name: \"id\", completion: Single(Size(1)), correlation: HeaderName(\"id\"), strategy: CollectAll, max_buckets: Some(10000), max_bucket_size: Some(10000), bucket_ttl: Some(300s), force_completion_on_stop: false, discard_on_timeout: false, max_timeout_tasks: 1024 } }"
1271 );
1272
1273 assert_eq!(
1274 format!(
1275 "{:?}",
1276 BuilderStep::DynamicRouter {
1277 config: DynamicRouterConfig::new(TargetSource::Sync(Arc::new(
1278 |_: &Exchange| Some("mock:dr".into())
1279 ))),
1280 }
1281 ),
1282 "DynamicRouter { config: DynamicRouterConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000, timeout: Some(60s) } }"
1283 );
1284
1285 assert_eq!(
1286 format!(
1287 "{:?}",
1288 BuilderStep::RoutingSlip {
1289 config: RoutingSlipConfig::new(TargetSource::Sync(Arc::new(|_: &Exchange| {
1290 Some("mock:rs".into())
1291 }))),
1292 }
1293 ),
1294 "RoutingSlip { config: RoutingSlipConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false } }"
1295 );
1296
1297 assert_eq!(
1298 format!(
1299 "{:?}",
1300 BuilderStep::RecipientList {
1301 config: RecipientListConfig::new(RecipientSource::Sync(Arc::new(
1302 |_: &Exchange| String::new()
1303 ))),
1304 }
1305 ),
1306 "RecipientList { config: RecipientListConfig { delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, max_recipients: 1000 } }"
1307 );
1308
1309 assert_eq!(
1310 format!(
1311 "{:?}",
1312 BuilderStep::Enrich {
1313 uri: "mock:enrich".into(),
1314 strategy: Some("agg".into()),
1315 timeout_ms: Some(1000),
1316 }
1317 ),
1318 "Enrich { uri: \"mock:enrich\", strategy: Some(\"agg\"), timeout_ms: Some(1000) }"
1319 );
1320
1321 assert_eq!(
1322 format!(
1323 "{:?}",
1324 BuilderStep::PollEnrich {
1325 uri: "mock:poll".into(),
1326 strategy: None,
1327 timeout_ms: None,
1328 }
1329 ),
1330 "PollEnrich { uri: \"mock:poll\", strategy: None, timeout_ms: None }"
1331 );
1332
1333 assert_eq!(
1334 format!(
1335 "{:?}",
1336 BuilderStep::Validate {
1337 predicate: expr.clone(),
1338 }
1339 ),
1340 "Validate { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
1341 );
1342
1343 assert_eq!(
1344 format!("{:?}", BuilderStep::Sampling { period: 100 }),
1345 "Sampling { period: 100 }"
1346 );
1347
1348 assert_eq!(
1349 format!(
1350 "{:?}",
1351 BuilderStep::Resequence {
1352 policy_config: Default::default(),
1353 }
1354 ),
1355 "Resequence { policy_config: ResequencePolicyConfig { mode: Batch { correlation: \"header.id\", sort: \"header.id\", completion: SizeOrTimeout(100, 30000) } } }"
1356 );
1357
1358 assert_eq!(
1360 format!(
1361 "{:?}",
1362 BuilderStep::DeclarativeFilter {
1363 predicate: expr.clone(),
1364 steps: vec![BuilderStep::Stop],
1365 }
1366 ),
1367 "DeclarativeFilter { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }"
1368 );
1369
1370 assert_eq!(
1371 format!(
1372 "{:?}",
1373 BuilderStep::DeclarativeSplit {
1374 expression: expr.clone(),
1375 aggregation: AggregationStrategy::Original,
1376 parallel: false,
1377 parallel_limit: Some(2),
1378 trace_item_threshold: None,
1379 stop_on_exception: true,
1380 steps: vec![BuilderStep::Stop],
1381 }
1382 ),
1383 "DeclarativeSplit { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, aggregation: Original, parallel: false, parallel_limit: Some(2), trace_item_threshold: None, stop_on_exception: true, steps: [Stop] }"
1384 );
1385
1386 assert_eq!(
1387 format!(
1388 "{:?}",
1389 BuilderStep::Split {
1390 config: camel_api::splitter::SplitterConfig::new(
1391 camel_api::splitter::split_body_lines()
1392 ),
1393 steps: vec![BuilderStep::Stop],
1394 }
1395 ),
1396 "Split { config: SplitterConfig { expression: \"<split-expression>\", aggregation: LastWins, parallel: false, parallel_limit: None, stop_on_exception: true, max_fragments: 100000, trace_item_threshold: 100 }, steps: [Stop] }"
1397 );
1398
1399 assert_eq!(
1400 format!(
1401 "{:?}",
1402 BuilderStep::Filter {
1403 predicate: FilterPredicate::new(|_: &Exchange| true),
1404 steps: vec![BuilderStep::Stop],
1405 }
1406 ),
1407 "Filter { predicate: FilterPredicate(..), steps: [Stop] }"
1408 );
1409
1410 assert_eq!(
1411 format!(
1412 "{:?}",
1413 BuilderStep::Throttle {
1414 config: camel_api::ThrottlerConfig::new(
1415 10,
1416 std::time::Duration::from_millis(10)
1417 ),
1418 steps: vec![BuilderStep::Stop],
1419 }
1420 ),
1421 "Throttle { config: ThrottlerConfig { max_requests: 10, period: 10ms, strategy: Delay }, steps: [Stop] }"
1422 );
1423
1424 assert_eq!(
1425 format!(
1426 "{:?}",
1427 BuilderStep::LoadBalance {
1428 config: camel_api::LoadBalancerConfig::round_robin(),
1429 steps: vec![BuilderStep::To("mock:l1".into())],
1430 }
1431 ),
1432 "LoadBalance { config: LoadBalancerConfig { strategy: RoundRobin }, steps: [To(\"mock:l1\")] }"
1433 );
1434
1435 assert_eq!(
1436 format!(
1437 "{:?}",
1438 BuilderStep::Delay {
1439 config: camel_api::DelayConfig::new(500),
1440 }
1441 ),
1442 "Delay { config: DelayConfig { delay_ms: 500, dynamic_header: None, max_delay_ms: 3600000 } }"
1443 );
1444
1445 assert_eq!(
1449 format!(
1450 "{:?}",
1451 BuilderStep::Choice {
1452 whens: vec![WhenStep {
1453 predicate: FilterPredicate::new(|_: &Exchange| true),
1454 steps: vec![BuilderStep::To("mock:a".into())],
1455 }],
1456 otherwise: None,
1457 }
1458 ),
1459 "Choice { whens: [WhenStep { predicate: FilterPredicate(..), steps: [To(\"mock:a\")] }], otherwise: None }"
1460 );
1461
1462 assert_eq!(
1463 format!(
1464 "{:?}",
1465 BuilderStep::DeclarativeChoice {
1466 whens: vec![DeclarativeWhenStep {
1467 predicate: expr.clone(),
1468 steps: vec![BuilderStep::Stop],
1469 }],
1470 otherwise: Some(vec![BuilderStep::Stop]),
1471 }
1472 ),
1473 "DeclarativeChoice { whens: [DeclarativeWhenStep { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }], otherwise: Some([Stop]) }"
1474 );
1475
1476 assert_eq!(
1478 format!(
1479 "{:?}",
1480 BuilderStep::Multicast {
1481 steps: vec![BuilderStep::To("direct:a".into())],
1482 config: MulticastConfig::new(),
1483 }
1484 ),
1485 "Multicast { steps: [To(\"direct:a\")], config: MulticastConfig { parallel: false, parallel_limit: None, stop_on_exception: false, timeout: None, aggregation: LastWins } }"
1486 );
1487
1488 assert_eq!(
1490 format!(
1491 "{:?}",
1492 BuilderStep::DeclarativeDynamicRouter {
1493 expression: expr.clone(),
1494 uri_delimiter: ",".into(),
1495 cache_size: 1000,
1496 ignore_invalid_endpoints: false,
1497 max_iterations: 1000,
1498 }
1499 ),
1500 "DeclarativeDynamicRouter { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000 }"
1501 );
1502
1503 assert_eq!(
1504 format!(
1505 "{:?}",
1506 BuilderStep::DeclarativeRoutingSlip {
1507 expression: expr.clone(),
1508 uri_delimiter: ",".into(),
1509 cache_size: 1000,
1510 ignore_invalid_endpoints: false,
1511 }
1512 ),
1513 "DeclarativeRoutingSlip { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false }"
1514 );
1515
1516 assert_eq!(
1518 format!(
1519 "{:?}",
1520 BuilderStep::DeclarativeRecipientList {
1521 expression: expr.clone(),
1522 delimiter: ",".into(),
1523 parallel: false,
1524 parallel_limit: None,
1525 stop_on_exception: false,
1526 aggregation: "original".into(),
1527 }
1528 ),
1529 "DeclarativeRecipientList { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, aggregation: \"original\" }"
1530 );
1531
1532 assert_eq!(
1534 format!(
1535 "{:?}",
1536 BuilderStep::Loop {
1537 config: camel_api::loop_eip::LoopConfig::new(LoopMode::Count(3)),
1538 steps: vec![],
1539 }
1540 ),
1541 "Loop { config: LoopConfig { mode: Count(3), max_iterations: 10000 }, steps: [] }"
1542 );
1543
1544 assert_eq!(
1545 format!(
1546 "{:?}",
1547 BuilderStep::DeclarativeLoop {
1548 count: Some(5),
1549 while_predicate: None,
1550 steps: vec![],
1551 max_iterations: Some(100),
1552 }
1553 ),
1554 "DeclarativeLoop { count: Some(5), while_predicate: None, steps: [], max_iterations: Some(100) }"
1555 );
1556
1557 assert_eq!(
1559 format!(
1560 "{:?}",
1561 BuilderStep::ClaimCheck {
1562 repository: "myRepo".into(),
1563 operation: "checkout".into(),
1564 key: expr.clone(),
1565 filter: None,
1566 }
1567 ),
1568 "ClaimCheck { repository: \"myRepo\", operation: \"checkout\", key: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, filter: None }"
1569 );
1570
1571 assert_eq!(
1573 format!(
1574 "{:?}",
1575 BuilderStep::Sort {
1576 expression: expr.clone(),
1577 reverse: false,
1578 }
1579 ),
1580 "Sort { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, reverse: false }"
1581 );
1582
1583 assert_eq!(
1585 format!(
1586 "{:?}",
1587 BuilderStep::IdempotentConsumer {
1588 repository: "myRepo".into(),
1589 expression: expr.clone(),
1590 steps: vec![],
1591 eager: true,
1592 remove_on_failure: false,
1593 }
1594 ),
1595 "IdempotentConsumer { repository: \"myRepo\", expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [], eager: true, remove_on_failure: false }"
1596 );
1597
1598 assert_eq!(
1600 format!(
1601 "{:?}",
1602 BuilderStep::DeclarativeDoTry {
1603 try_steps: vec![BuilderStep::Stop],
1604 catch: vec![],
1605 finally: None,
1606 }
1607 ),
1608 "DeclarativeDoTry { try_steps: [Stop], catch: [], finally: None }"
1609 );
1610
1611 assert_eq!(
1613 format!(
1614 "{:?}",
1615 BuilderStep::DeclarativeStreamSplit {
1616 stream_config: StreamSplitConfig {
1617 format: StreamSplitFormat::Ndjson,
1618 max_record_bytes: 1024 * 1024,
1619 batch_size: 1,
1620 chunk_size: None,
1621 include_origin: true,
1622 },
1623 aggregation: AggregationStrategy::Original,
1624 stop_on_exception: true,
1625 steps: vec![BuilderStep::Stop],
1626 }
1627 ),
1628 "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] }"
1629 );
1630 }
1631
1632 #[test]
1633 fn test_builder_step_multicast_variant() {
1634 use camel_api::MulticastConfig;
1635
1636 let step = BuilderStep::Multicast {
1637 steps: vec![BuilderStep::To("direct:a".into())],
1638 config: MulticastConfig::new(),
1639 };
1640
1641 assert!(matches!(step, BuilderStep::Multicast { .. }));
1642 }
1643
1644 #[test]
1645 fn test_route_definition_defaults() {
1646 let def = RouteDefinition::new("direct:test", vec![]).with_route_id("test-route");
1647 assert_eq!(def.route_id(), "test-route");
1648 assert!(def.auto_startup());
1649 assert_eq!(def.startup_order(), 1000);
1650 }
1651
1652 #[test]
1653 fn test_route_definition_builders() {
1654 let def = RouteDefinition::new("direct:test", vec![])
1655 .with_route_id("my-route")
1656 .with_auto_startup(false)
1657 .with_startup_order(50);
1658 assert_eq!(def.route_id(), "my-route");
1659 assert!(!def.auto_startup());
1660 assert_eq!(def.startup_order(), 50);
1661 }
1662
1663 #[test]
1664 fn test_route_definition_accessors_cover_core_fields() {
1665 let def = RouteDefinition::new("direct:in", vec![BuilderStep::To("mock:out".into())])
1666 .with_route_id("accessor-route");
1667
1668 assert_eq!(def.from_uri(), "direct:in");
1669 assert_eq!(def.steps().len(), 1);
1670 assert!(matches!(def.steps()[0], BuilderStep::To(_)));
1671 }
1672
1673 #[test]
1674 fn test_route_definition_error_handler_circuit_breaker_and_concurrency_accessors() {
1675 use camel_api::circuit_breaker::CircuitBreakerConfig;
1676 use camel_api::error_handler::ErrorHandlerConfig;
1677 use camel_component_api::ConcurrencyModel;
1678
1679 let def = RouteDefinition::new("direct:test", vec![])
1680 .with_route_id("eh-route")
1681 .with_error_handler(ErrorHandlerConfig::dead_letter_channel("log:dlc"))
1682 .with_circuit_breaker(CircuitBreakerConfig::new())
1683 .with_concurrency(ConcurrencyModel::Concurrent { max: Some(4) });
1684
1685 let eh = def
1686 .error_handler_config()
1687 .expect("error handler should be set");
1688 assert_eq!(eh.dlc_uri.as_deref(), Some("log:dlc"));
1689 assert!(def.circuit_breaker_config().is_some());
1690 assert!(matches!(
1691 def.concurrency_override(),
1692 Some(ConcurrencyModel::Concurrent { max: Some(4) })
1693 ));
1694 }
1695
1696 #[test]
1697 fn test_builder_step_debug_covers_many_variants() {
1698 use camel_api::splitter::{AggregationStrategy, SplitterConfig, split_body_lines};
1699 use camel_api::{
1700 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, IdentityProcessor,
1701 OpaqueProcessor, RoutingSlipConfig, Value,
1702 };
1703 use std::sync::Arc;
1704
1705 let expr = LanguageExpressionDef {
1706 language: "simple".into(),
1707 source: "${body}".into(),
1708 };
1709
1710 let steps = vec![
1711 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor))),
1712 BuilderStep::To("mock:out".into()),
1713 BuilderStep::Stop,
1714 BuilderStep::Log {
1715 level: camel_processor::LogLevel::Info,
1716 message: "hello".into(),
1717 },
1718 BuilderStep::DeclarativeSetHeader {
1719 key: "k".into(),
1720 value: ValueSourceDef::Literal(Value::String("v".into())),
1721 },
1722 BuilderStep::DeclarativeSetBody {
1723 value: ValueSourceDef::Expression(expr.clone()),
1724 },
1725 BuilderStep::DeclarativeFilter {
1726 predicate: expr.clone(),
1727 steps: vec![BuilderStep::Stop],
1728 },
1729 BuilderStep::DeclarativeChoice {
1730 whens: vec![DeclarativeWhenStep {
1731 predicate: expr.clone(),
1732 steps: vec![BuilderStep::Stop],
1733 }],
1734 otherwise: Some(vec![BuilderStep::Stop]),
1735 },
1736 BuilderStep::DeclarativeScript {
1737 expression: expr.clone(),
1738 },
1739 BuilderStep::DeclarativeSplit {
1740 expression: expr.clone(),
1741 aggregation: AggregationStrategy::Original,
1742 parallel: false,
1743 parallel_limit: Some(2),
1744 trace_item_threshold: None,
1745 stop_on_exception: true,
1746 steps: vec![BuilderStep::Stop],
1747 },
1748 BuilderStep::Split {
1749 config: SplitterConfig::new(split_body_lines()),
1750 steps: vec![BuilderStep::Stop],
1751 },
1752 BuilderStep::Aggregate {
1753 config: camel_api::AggregatorConfig::correlate_by("id")
1754 .complete_when_size(1)
1755 .build()
1756 .unwrap(),
1757 },
1758 BuilderStep::Filter {
1759 predicate: FilterPredicate::new(|_: &Exchange| true),
1760 steps: vec![BuilderStep::Stop],
1761 },
1762 BuilderStep::WireTap {
1763 uri: "mock:tap".into(),
1764 },
1765 BuilderStep::DeclarativeLog {
1766 level: camel_processor::LogLevel::Info,
1767 message: ValueSourceDef::Expression(expr.clone()),
1768 },
1769 BuilderStep::Bean {
1770 name: "bean".into(),
1771 method: "call".into(),
1772 },
1773 BuilderStep::Script {
1774 language: "rhai".into(),
1775 script: "body".into(),
1776 },
1777 BuilderStep::Throttle {
1778 config: camel_api::ThrottlerConfig::new(10, std::time::Duration::from_millis(10)),
1779 steps: vec![BuilderStep::Stop],
1780 },
1781 BuilderStep::LoadBalance {
1782 config: camel_api::LoadBalancerConfig::round_robin(),
1783 steps: vec![BuilderStep::To("mock:l1".into())],
1784 },
1785 BuilderStep::DynamicRouter {
1786 config: DynamicRouterConfig::new(TargetSource::Sync(Arc::new(|_| {
1787 Some("mock:dr".into())
1788 }))),
1789 },
1790 BuilderStep::RoutingSlip {
1791 config: RoutingSlipConfig::new(TargetSource::Sync(Arc::new(|_| {
1792 Some("mock:rs".into())
1793 }))),
1794 },
1795 ];
1796
1797 for step in steps {
1798 let dbg = format!("{step:?}");
1799 assert!(!dbg.is_empty());
1800 }
1801 }
1802
1803 #[test]
1804 fn test_route_definition_to_info_preserves_metadata() {
1805 let info = RouteDefinition::new("direct:test", vec![])
1806 .with_route_id("meta-route")
1807 .with_auto_startup(false)
1808 .with_startup_order(7)
1809 .to_info();
1810
1811 assert_eq!(info.route_id(), "meta-route");
1812 assert!(!info.auto_startup());
1813 assert_eq!(info.startup_order(), 7);
1814 }
1815
1816 #[test]
1817 fn test_choice_builder_step_debug() {
1818 use camel_api::FilterPredicate;
1819
1820 fn always_true(_: &camel_api::Exchange) -> bool {
1821 true
1822 }
1823
1824 let step = BuilderStep::Choice {
1825 whens: vec![WhenStep {
1826 predicate: FilterPredicate::new(always_true),
1827 steps: vec![BuilderStep::To("mock:a".into())],
1828 }],
1829 otherwise: None,
1830 };
1831 let debug = format!("{step:?}");
1832 assert!(debug.contains("Choice"));
1833 }
1834
1835 #[test]
1836 fn test_route_definition_unit_of_work() {
1837 use camel_api::UnitOfWorkConfig;
1838 let config = UnitOfWorkConfig {
1839 on_complete: Some("log:complete".into()),
1840 on_failure: Some("log:failed".into()),
1841 };
1842 let def = RouteDefinition::new("direct:test", vec![])
1843 .with_route_id("uow-test")
1844 .with_unit_of_work(config.clone());
1845 assert_eq!(
1846 def.unit_of_work_config().unwrap().on_complete.as_deref(),
1847 Some("log:complete")
1848 );
1849 assert_eq!(
1850 def.unit_of_work_config().unwrap().on_failure.as_deref(),
1851 Some("log:failed")
1852 );
1853
1854 let def_no_uow = RouteDefinition::new("direct:test", vec![]).with_route_id("no-uow");
1855 assert!(def_no_uow.unit_of_work_config().is_none());
1856 }
1857
1858 #[test]
1859 fn test_route_definition_security_policy_accessor() {
1860 use async_trait::async_trait;
1861 use camel_api::CamelError;
1862 use camel_api::Exchange;
1863 use camel_api::security_policy::{
1864 AuthContext, AuthorizationDecision, Principal, SecurityPolicy, SecurityPolicyConfig,
1865 };
1866
1867 struct StubPolicy;
1868 #[async_trait]
1869 impl SecurityPolicy for StubPolicy {
1870 async fn evaluate(
1871 &self,
1872 _exchange: &mut Exchange,
1873 _auth: &AuthContext<'_>,
1874 ) -> Result<AuthorizationDecision, CamelError> {
1875 Ok(AuthorizationDecision::Granted {
1876 principal: Principal {
1877 subject: "test".into(),
1878 issuer: "test".into(),
1879 audience: vec![],
1880 scopes: vec![],
1881 roles: vec![],
1882 claims: serde_json::Value::Null,
1883 },
1884 })
1885 }
1886 }
1887
1888 let def_no_sp = RouteDefinition::new("direct:test", vec![]).with_route_id("no-sp");
1889 assert!(def_no_sp.security_policy_config().is_none());
1890
1891 let def = RouteDefinition::new("direct:test", vec![])
1892 .with_route_id("sp-test")
1893 .with_security_policy(SecurityPolicyConfig::new(StubPolicy));
1894 assert!(def.security_policy_config().is_some());
1895 }
1896
1897 #[test]
1898 fn test_route_definition_security_authenticator_accessor() {
1899 use camel_api::security_policy::Principal;
1900
1901 struct TestAuth;
1902 #[async_trait::async_trait]
1903 impl TokenAuthenticator for TestAuth {
1904 async fn authenticate_bearer(
1905 &self,
1906 _token: &str,
1907 ) -> Result<Principal, camel_api::CamelError> {
1908 Ok(Principal {
1909 subject: "test".into(),
1910 issuer: "test".into(),
1911 audience: vec![],
1912 scopes: vec![],
1913 roles: vec![],
1914 claims: serde_json::Value::Null,
1915 })
1916 }
1917 }
1918
1919 let def_no_auth = RouteDefinition::new("direct:test".to_string(), vec![]);
1920 assert!(def_no_auth.security_authenticator().is_none());
1921
1922 let auth = Arc::new(TestAuth);
1923 let def = RouteDefinition::new("direct:test".to_string(), vec![])
1924 .with_security_authenticator(auth);
1925 assert!(def.security_authenticator().is_some());
1926 }
1927
1928 #[test]
1929 fn test_map_steps_swaps_steps_and_preserves_other_fields() {
1930 let original = RouteDefinition::new(
1931 "direct:test".to_string(),
1932 vec![BuilderStep::To("mock:a".into()), BuilderStep::Stop],
1933 )
1934 .with_route_id("my-route");
1935
1936 let mapped = original.map_steps(|steps| {
1937 let mut out = Vec::with_capacity(steps.len() + 1);
1938 out.push(BuilderStep::To("mock:prefix".into()));
1939 out.extend(steps);
1940 out
1941 });
1942
1943 assert_eq!(mapped.steps().len(), 3);
1945 assert!(matches!(mapped.steps()[0], BuilderStep::To(ref s) if s == "mock:prefix"));
1946 assert!(matches!(mapped.steps()[1], BuilderStep::To(ref s) if s == "mock:a"));
1947 assert_eq!(mapped.route_id(), "my-route");
1949 }
1950
1951 #[test]
1952 fn circuit_breaker_fallback_accessor_returns_steps() {
1953 let def = RouteDefinition::new("direct:start", vec![])
1954 .with_circuit_breaker_fallback(vec![BuilderStep::To("mock:out".into())]);
1955 assert_eq!(def.circuit_breaker_fallback().len(), 1);
1956 }
1957}