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