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(Clone)]
62pub struct DoTryCatchClause {
63 pub matcher: camel_processor::CatchMatcher,
64 pub on_when: Option<camel_api::PredicateSource>,
65 pub disposition: camel_api::error_handler::ExceptionDisposition,
66 pub steps: Vec<BuilderStep>,
67}
68
69impl std::fmt::Debug for DoTryCatchClause {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.debug_struct("DoTryCatchClause")
72 .field("matcher", &"<compiled>")
73 .field("on_when", &self.on_when.is_some())
74 .field("disposition", &self.disposition)
75 .field("steps", &self.steps)
76 .finish()
77 }
78}
79
80#[derive(Clone)]
82pub struct DoTryFinallyClause {
83 pub on_when: Option<camel_api::PredicateSource>,
84 pub steps: Vec<BuilderStep>,
85}
86
87impl std::fmt::Debug for DoTryFinallyClause {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("DoTryFinallyClause")
90 .field("on_when", &self.on_when.is_some())
91 .field("steps", &self.steps)
92 .finish()
93 }
94}
95
96#[derive(Clone)]
102pub struct StreamSplitExpression(pub camel_api::StreamingSplitExpression);
103
104impl std::fmt::Debug for StreamSplitExpression {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.write_str("StreamingSplitExpression(...)")
107 }
108}
109
110impl From<camel_api::StreamingSplitExpression> for StreamSplitExpression {
111 fn from(expression: camel_api::StreamingSplitExpression) -> Self {
112 Self(expression)
113 }
114}
115
116#[derive(Debug, Clone)]
118pub enum BuilderStep {
119 Processor(OpaqueProcessor),
121 To(String),
123 Stop,
125 Log {
127 level: camel_processor::LogLevel,
128 message: String,
129 },
130 DeclarativeSetHeader {
132 key: String,
133 value: ValueSourceDef,
134 },
135 DeclarativeSetHeaderIfAbsent {
137 key: String,
138 value: ValueSourceDef,
139 },
140 DeclarativeRemoveHeader {
142 key: String,
143 },
144 DeclarativeSetProperty {
145 key: String,
146 value_source: ValueSourceDef,
147 },
148 DeclarativeSetBody {
150 value: ValueSourceDef,
151 },
152 DeclarativeFilter {
154 predicate: LanguageExpressionDef,
155 steps: Vec<BuilderStep>,
156 },
157 DeclarativeChoice {
159 whens: Vec<DeclarativeWhenStep>,
160 otherwise: Option<Vec<BuilderStep>>,
161 },
162 DeclarativeScript {
165 expression: LanguageExpressionDef,
166 },
167 DeclarativeFunction {
168 definition: camel_api::FunctionDefinition,
169 },
170 DeclarativeSplit {
172 expression: LanguageExpressionDef,
173 aggregation: camel_api::splitter::AggregationStrategy,
174 parallel: bool,
175 parallel_limit: Option<usize>,
176 trace_item_threshold: Option<usize>,
179 stop_on_exception: bool,
180 steps: Vec<BuilderStep>,
181 },
182 DeclarativeStreamSplit {
184 stream_config: camel_api::StreamSplitConfig,
185 aggregation: camel_api::splitter::AggregationStrategy,
186 stop_on_exception: bool,
187 steps: Vec<BuilderStep>,
188 },
189 StreamSplit {
194 expression: StreamSplitExpression,
195 aggregation: camel_api::splitter::AggregationStrategy,
196 stop_on_exception: bool,
197 steps: Vec<BuilderStep>,
198 },
199 DeclarativeDynamicRouter {
200 expression: LanguageExpressionDef,
201 uri_delimiter: String,
202 cache_size: i32,
203 ignore_invalid_endpoints: bool,
204 max_iterations: usize,
205 },
206 DeclarativeRoutingSlip {
207 expression: LanguageExpressionDef,
208 uri_delimiter: String,
209 cache_size: i32,
210 ignore_invalid_endpoints: bool,
211 },
212 Split {
214 config: SplitterConfig,
215 steps: Vec<BuilderStep>,
216 },
217 Aggregate {
219 config: AggregatorConfig,
220 },
221 Filter {
223 predicate: FilterPredicate,
224 steps: Vec<BuilderStep>,
225 },
226 Choice {
229 whens: Vec<WhenStep>,
230 otherwise: Option<Vec<BuilderStep>>,
231 },
232 WireTap {
234 uri: String,
235 },
236 Multicast {
238 steps: Vec<BuilderStep>,
239 config: MulticastConfig,
240 },
241 DeclarativeLog {
243 level: camel_processor::LogLevel,
244 message: ValueSourceDef,
245 },
246 Bean {
248 name: String,
249 method: String,
250 },
251 Script {
254 language: String,
255 script: String,
256 },
257 Throttle {
259 config: camel_api::ThrottlerConfig,
260 steps: Vec<BuilderStep>,
261 },
262 LoadBalance {
264 config: camel_api::LoadBalancerConfig,
265 steps: Vec<BuilderStep>,
266 },
267 DynamicRouter {
269 config: camel_api::DynamicRouterConfig,
270 },
271 RoutingSlip {
272 config: camel_api::RoutingSlipConfig,
273 },
274 RecipientList {
275 config: camel_api::recipient_list::RecipientListConfig,
276 },
277 DeclarativeRecipientList {
278 expression: LanguageExpressionDef,
279 delimiter: String,
280 parallel: bool,
281 parallel_limit: Option<usize>,
282 stop_on_exception: bool,
283 aggregation: String,
284 },
285 Delay {
286 config: camel_api::DelayConfig,
287 },
288 Loop {
290 config: LoopConfig,
291 steps: Vec<BuilderStep>,
292 },
293 DeclarativeLoop {
295 count: Option<usize>,
296 while_predicate: Option<LanguageExpressionDef>,
297 steps: Vec<BuilderStep>,
298 max_iterations: Option<usize>,
299 },
300 Enrich {
302 uri: String,
303 strategy: Option<String>,
304 timeout_ms: Option<u64>,
305 },
306 PollEnrich {
308 uri: String,
309 strategy: Option<String>,
310 timeout_ms: Option<u64>,
311 },
312 Validate {
315 predicate: LanguageExpressionDef,
316 },
317 ClaimCheck {
321 repository: String,
322 operation: String,
323 key: LanguageExpressionDef,
324 filter: Option<String>,
325 },
326 Sampling {
330 period: usize,
331 },
332 Sort {
335 expression: LanguageExpressionDef,
336 reverse: bool,
337 },
338 IdempotentConsumer {
342 repository: String,
343 expression: LanguageExpressionDef,
344 steps: Vec<BuilderStep>,
345 eager: bool,
346 remove_on_failure: bool,
347 },
348 Cache {
352 repository: Option<String>,
353 key: LanguageExpressionDef,
354 ttl: Option<String>,
355 max_entry_bytes: Option<usize>,
356 coalesce_misses: bool,
358 on_miss: Vec<BuilderStep>,
359 },
360 CacheInvalidate {
365 repository: Option<String>,
366 key: Option<LanguageExpressionDef>,
367 key_prefix: Option<LanguageExpressionDef>,
368 },
369 CacheClear {
372 repository: Option<String>,
373 },
374 CacheStats {
377 repository: Option<String>,
378 },
379 CachePeekStale {
382 repository: Option<String>,
383 key: LanguageExpressionDef,
384 on_miss: camel_processor::PeekStaleMissPolicy,
385 },
386 DeclarativeDoTry {
388 try_steps: Vec<BuilderStep>,
389 catch: Vec<DoTryCatchClauseBuilder>,
390 finally: Option<DoTryFinallyBuilder>,
391 },
392 DoTry {
395 try_steps: Vec<BuilderStep>,
396 catches: Vec<DoTryCatchClause>,
397 finally: Option<DoTryFinallyClause>,
398 },
399 Resequence {
402 policy_config: ResequencePolicyConfig,
403 },
404}
405
406impl BuilderStep {
407 pub(crate) fn span_label(&self) -> Option<String> {
421 match self {
422 Self::Processor(_) | Self::Stop => None,
424
425 Self::To(uri) => uri.contains(':').then(|| {
426 let scheme = uri.split(':').next().unwrap_or_default();
427 format!("to:{scheme}")
428 }),
429
430 Self::Log { .. } | Self::DeclarativeLog { .. } => Some("log".into()),
431
432 Self::DeclarativeSetHeader { .. } => Some("set-header".into()),
433 Self::DeclarativeSetHeaderIfAbsent { .. } => Some("set-header-if-absent".into()),
434 Self::DeclarativeRemoveHeader { .. } => Some("remove-header".into()),
435 Self::DeclarativeSetProperty { .. } => Some("set-property".into()),
436 Self::DeclarativeSetBody { .. } => Some("set-body".into()),
437
438 Self::DeclarativeFilter { .. } | Self::Filter { .. } => Some("filter".into()),
439 Self::DeclarativeChoice { .. } | Self::Choice { .. } => Some("choice".into()),
440 Self::DeclarativeScript { .. } | Self::Script { .. } => Some("script".into()),
441 Self::DeclarativeFunction { .. } => Some("function".into()),
442
443 Self::DeclarativeSplit { .. }
444 | Self::DeclarativeStreamSplit { .. }
445 | Self::StreamSplit { .. }
446 | Self::Split { .. } => Some("split".into()),
447
448 Self::DeclarativeDynamicRouter { .. } | Self::DynamicRouter { .. } => {
449 Some("dynamic-router".into())
450 }
451 Self::DeclarativeRoutingSlip { .. } | Self::RoutingSlip { .. } => {
452 Some("routing-slip".into())
453 }
454 Self::DeclarativeRecipientList { .. } | Self::RecipientList { .. } => {
455 Some("recipient-list".into())
456 }
457
458 Self::Aggregate { .. } => Some("aggregate".into()),
459 Self::WireTap { .. } => Some("wire-tap".into()),
460 Self::Multicast { .. } => Some("multicast".into()),
461 Self::Bean { .. } => Some("bean".into()),
462 Self::Throttle { .. } => Some("throttle".into()),
463 Self::LoadBalance { .. } => Some("load-balance".into()),
464 Self::Delay { .. } => Some("delay".into()),
465 Self::Loop { .. } | Self::DeclarativeLoop { .. } => Some("loop".into()),
466 Self::Enrich { .. } => Some("enrich".into()),
467 Self::PollEnrich { .. } => Some("poll-enrich".into()),
468 Self::Validate { .. } => Some("validate".into()),
469 Self::ClaimCheck { .. } => Some("claim-check".into()),
470 Self::Sampling { .. } => Some("sampling".into()),
471 Self::Sort { .. } => Some("sort".into()),
472 Self::IdempotentConsumer { .. } => Some("idempotent-consumer".into()),
473 Self::Cache { .. } => Some("cache".into()),
474 Self::CacheInvalidate { .. } => Some("cache-invalidate".into()),
475 Self::CacheClear { .. } => Some("cache-clear".into()),
476 Self::CacheStats { .. } => Some("cache-stats".into()),
477 Self::CachePeekStale { .. } => Some("cache-peek-stale".into()),
478 Self::DeclarativeDoTry { .. } | Self::DoTry { .. } => Some("do-try".into()),
479 Self::Resequence { .. } => Some("resequence".into()),
480 }
481 }
482
483 pub(crate) fn to_uri_metadata(&self) -> Option<Arc<str>> {
494 match self {
495 Self::To(uri) => Some(Arc::from(uri.as_str())),
496
497 Self::Processor(..)
501 | Self::Stop
502 | Self::Log { .. }
503 | Self::DeclarativeSetHeader { .. }
504 | Self::DeclarativeSetHeaderIfAbsent { .. }
505 | Self::DeclarativeRemoveHeader { .. }
506 | Self::DeclarativeSetProperty { .. }
507 | Self::DeclarativeSetBody { .. }
508 | Self::DeclarativeFilter { .. }
509 | Self::DeclarativeChoice { .. }
510 | Self::DeclarativeScript { .. }
511 | Self::DeclarativeFunction { .. }
512 | Self::DeclarativeSplit { .. }
513 | Self::DeclarativeStreamSplit { .. }
514 | Self::StreamSplit { .. }
515 | Self::DeclarativeDynamicRouter { .. }
516 | Self::DeclarativeRoutingSlip { .. }
517 | Self::Split { .. }
518 | Self::Aggregate { .. }
519 | Self::Filter { .. }
520 | Self::Choice { .. }
521 | Self::Multicast { .. }
522 | Self::DeclarativeLog { .. }
523 | Self::Bean { .. }
524 | Self::Script { .. }
525 | Self::Throttle { .. }
526 | Self::LoadBalance { .. }
527 | Self::DynamicRouter { .. }
528 | Self::RoutingSlip { .. }
529 | Self::RecipientList { .. }
530 | Self::DeclarativeRecipientList { .. }
531 | Self::Delay { .. }
532 | Self::Loop { .. }
533 | Self::DeclarativeLoop { .. }
534 | Self::Enrich { .. }
535 | Self::PollEnrich { .. }
536 | Self::WireTap { .. }
537 | Self::Validate { .. }
538 | Self::ClaimCheck { .. }
539 | Self::Sampling { .. }
540 | Self::Sort { .. }
541 | Self::IdempotentConsumer { .. }
542 | Self::Cache { .. }
543 | Self::CacheInvalidate { .. }
544 | Self::CacheClear { .. }
545 | Self::CacheStats { .. }
546 | Self::CachePeekStale { .. }
547 | Self::DeclarativeDoTry { .. }
548 | Self::DoTry { .. }
549 | Self::Resequence { .. } => None,
550 }
551 }
552
553 pub(crate) fn span_kind_hint(&self) -> SpanKindHint {
569 match self {
570 Self::To(uri)
572 | Self::Enrich { uri, .. }
573 | Self::PollEnrich { uri, .. }
574 | Self::WireTap { uri, .. } => uri_span_kind(uri),
575
576 Self::Processor(..)
580 | Self::Stop
581 | Self::Log { .. }
582 | Self::DeclarativeSetHeader { .. }
583 | Self::DeclarativeSetHeaderIfAbsent { .. }
584 | Self::DeclarativeRemoveHeader { .. }
585 | Self::DeclarativeSetProperty { .. }
586 | Self::DeclarativeSetBody { .. }
587 | Self::DeclarativeFilter { .. }
588 | Self::DeclarativeChoice { .. }
589 | Self::DeclarativeScript { .. }
590 | Self::DeclarativeFunction { .. }
591 | Self::DeclarativeSplit { .. }
592 | Self::DeclarativeStreamSplit { .. }
593 | Self::StreamSplit { .. }
594 | Self::DeclarativeDynamicRouter { .. }
595 | Self::DeclarativeRoutingSlip { .. }
596 | Self::Split { .. }
597 | Self::Aggregate { .. }
598 | Self::Filter { .. }
599 | Self::Choice { .. }
600 | Self::Multicast { .. }
601 | Self::DeclarativeLog { .. }
602 | Self::Bean { .. }
603 | Self::Script { .. }
604 | Self::Throttle { .. }
605 | Self::LoadBalance { .. }
606 | Self::DynamicRouter { .. }
607 | Self::RoutingSlip { .. }
608 | Self::RecipientList { .. }
609 | Self::DeclarativeRecipientList { .. }
610 | Self::Delay { .. }
611 | Self::Loop { .. }
612 | Self::DeclarativeLoop { .. }
613 | Self::Validate { .. }
614 | Self::ClaimCheck { .. }
615 | Self::Sampling { .. }
616 | Self::Sort { .. }
617 | Self::IdempotentConsumer { .. }
618 | Self::Cache { .. }
619 | Self::CacheInvalidate { .. }
620 | Self::CacheClear { .. }
621 | Self::CacheStats { .. }
622 | Self::CachePeekStale { .. }
623 | Self::DeclarativeDoTry { .. }
624 | Self::DoTry { .. }
625 | Self::Resequence { .. } => SpanKindHint::Internal,
626 }
627 }
628}
629
630fn uri_span_kind(uri: &str) -> SpanKindHint {
637 const PRODUCER_SCHEMES: [&str; 5] = ["kafka", "jms", "activemq", "artemis", "mqtt"];
639 const CLIENT_SCHEMES: [&str; 12] = [
641 "http",
642 "https",
643 "grpc",
644 "grpcs",
645 "ws",
646 "redis",
647 "opensearch",
648 "sql",
649 "surrealdb",
650 "cxf",
651 "llm",
652 "mcp",
653 ];
654
655 let scheme = uri.split(':').next().unwrap_or_default();
656 if PRODUCER_SCHEMES
657 .iter()
658 .any(|s| s.eq_ignore_ascii_case(scheme))
659 {
660 SpanKindHint::Producer
661 } else if CLIENT_SCHEMES
662 .iter()
663 .any(|s| s.eq_ignore_ascii_case(scheme))
664 {
665 SpanKindHint::Client
666 } else {
667 SpanKindHint::Internal
668 }
669}
670
671pub struct RouteDefinition {
673 pub(crate) from_uri: String,
674 pub(crate) steps: Vec<BuilderStep>,
675 pub(crate) error_handler: Option<ErrorHandlerConfig>,
677 pub(crate) circuit_breaker: Option<CircuitBreakerConfig>,
679 pub(crate) circuit_breaker_fallback: Vec<BuilderStep>,
687 pub(crate) security_policy: Option<SecurityPolicyConfig>,
688 pub(crate) security_authenticator: Option<Arc<dyn TokenAuthenticator>>,
690 pub(crate) provider_registry: Option<Arc<camel_auth::ProviderRegistry>>,
693 pub(crate) security_provider: Option<String>,
697 pub(crate) security_audiences: Option<Vec<String>>,
702 pub(crate) unit_of_work: Option<UnitOfWorkConfig>,
704 pub(crate) concurrency: Option<ConcurrencyModel>,
707 pub(crate) route_id: String,
709 pub(crate) auto_startup: bool,
711 pub(crate) startup_order: i32,
713 pub(crate) source_hash: Option<u64>,
714}
715
716impl RouteDefinition {
717 pub fn new(from_uri: impl Into<String>, steps: Vec<BuilderStep>) -> Self {
719 Self {
720 from_uri: from_uri.into(),
721 steps,
722 error_handler: None,
723 circuit_breaker: None,
724 circuit_breaker_fallback: Vec::new(),
725 security_policy: None,
726 security_authenticator: None,
727 provider_registry: None,
728 security_provider: None,
729 security_audiences: None,
730 unit_of_work: None,
731 concurrency: None,
732 route_id: String::new(), auto_startup: true,
734 startup_order: 1000,
735 source_hash: None,
736 }
737 }
738
739 pub fn from_uri(&self) -> &str {
741 &self.from_uri
742 }
743
744 pub fn steps(&self) -> &[BuilderStep] {
746 &self.steps
747 }
748
749 pub fn circuit_breaker_fallback(&self) -> &[BuilderStep] {
750 &self.circuit_breaker_fallback
751 }
752
753 pub fn map_steps(mut self, f: impl FnOnce(Vec<BuilderStep>) -> Vec<BuilderStep>) -> Self {
758 self.steps = f(self.steps);
759 self
760 }
761
762 pub fn with_error_handler(mut self, config: ErrorHandlerConfig) -> Self {
764 self.error_handler = Some(config);
765 self
766 }
767
768 pub fn error_handler_config(&self) -> Option<&ErrorHandlerConfig> {
770 self.error_handler.as_ref()
771 }
772
773 pub fn with_circuit_breaker(mut self, config: CircuitBreakerConfig) -> Self {
775 self.circuit_breaker = Some(config);
776 self
777 }
778
779 pub fn with_circuit_breaker_fallback(mut self, steps: Vec<BuilderStep>) -> Self {
786 self.circuit_breaker_fallback = steps;
787 self
788 }
789
790 pub fn with_security_policy(mut self, config: SecurityPolicyConfig) -> Self {
792 self.security_policy = Some(config);
793 self
794 }
795
796 pub fn with_security_authenticator(
798 mut self,
799 authenticator: Arc<dyn TokenAuthenticator>,
800 ) -> Self {
801 self.security_authenticator = Some(authenticator);
802 self
803 }
804
805 pub fn with_provider_registry(mut self, registry: Arc<camel_auth::ProviderRegistry>) -> Self {
811 self.provider_registry = Some(registry);
812 self
813 }
814
815 pub fn with_security_provider(mut self, name: impl Into<String>) -> Self {
817 self.security_provider = Some(name.into());
818 self
819 }
820
821 pub fn with_security_audiences(mut self, audiences: Vec<String>) -> Self {
823 self.security_audiences = Some(audiences);
824 self
825 }
826
827 pub fn security_provider(&self) -> Option<&str> {
829 self.security_provider.as_deref()
830 }
831
832 pub fn security_audiences(&self) -> Option<&[String]> {
834 self.security_audiences.as_deref()
835 }
836
837 pub fn with_unit_of_work(mut self, config: UnitOfWorkConfig) -> Self {
839 self.unit_of_work = Some(config);
840 self
841 }
842
843 pub fn unit_of_work_config(&self) -> Option<&UnitOfWorkConfig> {
845 self.unit_of_work.as_ref()
846 }
847
848 pub fn circuit_breaker_config(&self) -> Option<&CircuitBreakerConfig> {
850 self.circuit_breaker.as_ref()
851 }
852
853 pub fn security_policy_config(&self) -> Option<&SecurityPolicyConfig> {
854 self.security_policy.as_ref()
855 }
856
857 pub fn security_authenticator(&self) -> Option<&Arc<dyn TokenAuthenticator>> {
858 self.security_authenticator.as_ref()
859 }
860
861 pub fn concurrency_override(&self) -> Option<&ConcurrencyModel> {
863 self.concurrency.as_ref()
864 }
865
866 pub fn with_concurrency(mut self, model: ConcurrencyModel) -> Self {
868 self.concurrency = Some(model);
869 self
870 }
871
872 pub fn route_id(&self) -> &str {
874 &self.route_id
875 }
876
877 pub fn auto_startup(&self) -> bool {
879 self.auto_startup
880 }
881
882 pub fn startup_order(&self) -> i32 {
884 self.startup_order
885 }
886
887 pub fn with_route_id(mut self, id: impl Into<String>) -> Self {
889 self.route_id = id.into();
890 self
891 }
892
893 pub fn with_auto_startup(mut self, auto: bool) -> Self {
895 self.auto_startup = auto;
896 self
897 }
898
899 pub fn with_startup_order(mut self, order: i32) -> Self {
901 self.startup_order = order;
902 self
903 }
904
905 pub fn with_source_hash(mut self, hash: u64) -> Self {
906 self.source_hash = Some(hash);
907 self
908 }
909
910 pub fn source_hash(&self) -> Option<u64> {
911 self.source_hash
912 }
913
914 pub fn to_info(&self) -> RouteDefinitionInfo {
917 RouteDefinitionInfo {
918 route_id: self.route_id.clone(),
919 auto_startup: self.auto_startup,
920 startup_order: self.startup_order,
921 source_hash: self.source_hash,
922 }
923 }
924}
925
926#[derive(Clone)]
932pub struct RouteDefinitionInfo {
933 route_id: String,
934 auto_startup: bool,
935 startup_order: i32,
936 pub(crate) source_hash: Option<u64>,
937}
938
939impl RouteDefinitionInfo {
940 pub fn route_id(&self) -> &str {
942 &self.route_id
943 }
944
945 pub fn auto_startup(&self) -> bool {
947 self.auto_startup
948 }
949
950 pub fn startup_order(&self) -> i32 {
952 self.startup_order
953 }
954
955 pub fn source_hash(&self) -> Option<u64> {
956 self.source_hash
957 }
958}
959
960#[cfg(test)]
961mod tests {
962 use super::*;
963 use camel_api::{RecipientSource, TargetSource};
964
965 #[test]
972 fn builder_step_span_label_mapping() {
973 use camel_api::declarative::LanguageExpressionDef;
974 use camel_api::splitter::AggregationStrategy;
975 use camel_api::{BoxProcessor, IdentityProcessor, OpaqueProcessor};
976
977 let expr = LanguageExpressionDef {
978 language: "simple".into(),
979 source: "${body}".into(),
980 };
981
982 assert_eq!(
984 BuilderStep::To("direct:tree-sub".into())
985 .span_label()
986 .as_deref(),
987 Some("to:direct")
988 );
989 assert_eq!(
990 BuilderStep::To("http://api.example/x".into())
991 .span_label()
992 .as_deref(),
993 Some("to:http")
994 );
995 assert_eq!(BuilderStep::To("garbage".into()).span_label(), None);
997
998 assert_eq!(
1000 BuilderStep::Log {
1001 level: camel_processor::LogLevel::Info,
1002 message: "m".into(),
1003 }
1004 .span_label()
1005 .as_deref(),
1006 Some("log")
1007 );
1008 assert_eq!(
1009 BuilderStep::Split {
1010 config: camel_api::splitter::SplitterConfig::new(
1011 camel_api::splitter::split_body_lines()
1012 ),
1013 steps: vec![BuilderStep::Stop],
1014 }
1015 .span_label()
1016 .as_deref(),
1017 Some("split")
1018 );
1019 assert_eq!(
1020 BuilderStep::DeclarativeSplit {
1021 expression: expr.clone(),
1022 aggregation: AggregationStrategy::Original,
1023 parallel: false,
1024 parallel_limit: None,
1025 trace_item_threshold: None,
1026 stop_on_exception: true,
1027 steps: vec![BuilderStep::Stop],
1028 }
1029 .span_label()
1030 .as_deref(),
1031 Some("split")
1032 );
1033 assert_eq!(
1034 BuilderStep::DeclarativeStreamSplit {
1035 stream_config: camel_api::StreamSplitConfig::default(),
1036 aggregation: AggregationStrategy::Original,
1037 stop_on_exception: true,
1038 steps: vec![BuilderStep::Stop],
1039 }
1040 .span_label()
1041 .as_deref(),
1042 Some("split")
1043 );
1044 assert_eq!(BuilderStep::Stop.span_label(), None);
1045
1046 assert_eq!(
1048 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor)))
1049 .span_label(),
1050 None
1051 );
1052 }
1053
1054 #[test]
1066 fn builder_step_span_kind_hint_mapping() {
1067 use camel_api::splitter::split_body_lines;
1068 use camel_api::{Exchange, FilterPredicate, SpanKindHint};
1069
1070 let kind = |uri: &str| BuilderStep::To(uri.into()).span_kind_hint();
1071
1072 assert_eq!(kind("kafka:orders"), SpanKindHint::Producer);
1074 assert_eq!(kind("jms:q"), SpanKindHint::Producer);
1075 assert_eq!(kind("activemq:q"), SpanKindHint::Producer);
1076 assert_eq!(kind("artemis:q"), SpanKindHint::Producer);
1077 assert_eq!(kind("mqtt:t"), SpanKindHint::Producer);
1078 assert_eq!(kind("KAFKA:orders"), SpanKindHint::Producer);
1080
1081 assert_eq!(kind("http://x"), SpanKindHint::Client);
1083 assert_eq!(kind("https://x"), SpanKindHint::Client);
1084 assert_eq!(kind("grpc://x"), SpanKindHint::Client);
1085 assert_eq!(kind("grpcs://x"), SpanKindHint::Client);
1086 assert_eq!(kind("ws://x"), SpanKindHint::Client);
1087 assert_eq!(kind("redis://x"), SpanKindHint::Client);
1088 assert_eq!(kind("opensearch://x"), SpanKindHint::Client);
1089 assert_eq!(kind("sql:db"), SpanKindHint::Client);
1090 assert_eq!(kind("surrealdb://x"), SpanKindHint::Client);
1091 assert_eq!(kind("cxf://x"), SpanKindHint::Client);
1092 assert_eq!(kind("llm://x"), SpanKindHint::Client);
1093 assert_eq!(kind("mcp://x"), SpanKindHint::Client);
1094
1095 assert_eq!(kind("direct:y"), SpanKindHint::Internal);
1097 assert_eq!(kind("timer:z"), SpanKindHint::Internal);
1098 assert_eq!(kind("garbage"), SpanKindHint::Internal);
1100
1101 assert_eq!(
1103 BuilderStep::Log {
1104 level: camel_processor::LogLevel::Info,
1105 message: "m".into(),
1106 }
1107 .span_kind_hint(),
1108 SpanKindHint::Internal
1109 );
1110 assert_eq!(
1111 BuilderStep::Filter {
1112 predicate: FilterPredicate::new(|_: &Exchange| true),
1113 steps: vec![BuilderStep::Stop],
1114 }
1115 .span_kind_hint(),
1116 SpanKindHint::Internal
1117 );
1118 assert_eq!(
1119 BuilderStep::Split {
1120 config: camel_api::splitter::SplitterConfig::new(split_body_lines()),
1121 steps: vec![BuilderStep::Stop],
1122 }
1123 .span_kind_hint(),
1124 SpanKindHint::Internal
1125 );
1126
1127 let enrich = |uri: &str| BuilderStep::Enrich {
1131 uri: uri.into(),
1132 strategy: None,
1133 timeout_ms: None,
1134 };
1135 let poll_enrich = |uri: &str| BuilderStep::PollEnrich {
1136 uri: uri.into(),
1137 strategy: None,
1138 timeout_ms: None,
1139 };
1140 let wire_tap = |uri: &str| BuilderStep::WireTap { uri: uri.into() };
1141
1142 assert_eq!(enrich("http://x").span_kind_hint(), SpanKindHint::Client);
1144 assert_eq!(
1145 poll_enrich("http://x").span_kind_hint(),
1146 SpanKindHint::Client
1147 );
1148 assert_eq!(wire_tap("http://x").span_kind_hint(), SpanKindHint::Client);
1149 assert_eq!(
1151 enrich("kafka:orders").span_kind_hint(),
1152 SpanKindHint::Producer
1153 );
1154 assert_eq!(
1155 poll_enrich("kafka:orders").span_kind_hint(),
1156 SpanKindHint::Producer
1157 );
1158 assert_eq!(
1159 wire_tap("kafka:orders").span_kind_hint(),
1160 SpanKindHint::Producer
1161 );
1162 assert_eq!(enrich("direct:y").span_kind_hint(), SpanKindHint::Internal);
1164 assert_eq!(
1165 poll_enrich("seda:q").span_kind_hint(),
1166 SpanKindHint::Internal
1167 );
1168 assert_eq!(
1169 wire_tap("direct:y").span_kind_hint(),
1170 SpanKindHint::Internal
1171 );
1172 }
1173
1174 #[test]
1179 fn golden_debug_output_all_variants() {
1180 use camel_api::declarative::LanguageExpressionDef;
1181 use camel_api::loop_eip::LoopMode;
1182 use camel_api::recipient_list::RecipientListConfig;
1183 use camel_api::splitter::{AggregationStrategy, StreamSplitConfig, StreamSplitFormat};
1184 use camel_api::{
1185 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, FunctionDefinition,
1186 FunctionId, IdentityProcessor, MulticastConfig, OpaqueProcessor, RoutingSlipConfig,
1187 Value,
1188 };
1189 use std::sync::Arc;
1190
1191 let expr = LanguageExpressionDef {
1192 language: "simple".into(),
1193 source: "${body}".into(),
1194 };
1195
1196 assert_eq!(format!("{:?}", BuilderStep::Stop), "Stop");
1199 assert_eq!(
1200 format!(
1201 "{:?}",
1202 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor)))
1203 ),
1204 "Processor(BoxProcessor(...))"
1205 );
1206 assert_eq!(
1207 format!("{:?}", BuilderStep::To("mock:out".into())),
1208 "To(\"mock:out\")"
1209 );
1210
1211 assert_eq!(
1214 format!(
1215 "{:?}",
1216 BuilderStep::Log {
1217 level: camel_processor::LogLevel::Info,
1218 message: "hello".into(),
1219 }
1220 ),
1221 "Log { level: Info, message: \"hello\" }"
1222 );
1223
1224 assert_eq!(
1225 format!(
1226 "{:?}",
1227 BuilderStep::DeclarativeSetHeader {
1228 key: "k".into(),
1229 value: ValueSourceDef::Literal(Value::String("v".into())),
1230 }
1231 ),
1232 "DeclarativeSetHeader { key: \"k\", value: Literal(String(\"v\")) }"
1233 );
1234
1235 assert_eq!(
1236 format!(
1237 "{:?}",
1238 BuilderStep::DeclarativeSetHeaderIfAbsent {
1239 key: "k".into(),
1240 value: ValueSourceDef::Literal(Value::String("v".into())),
1241 }
1242 ),
1243 "DeclarativeSetHeaderIfAbsent { key: \"k\", value: Literal(String(\"v\")) }"
1244 );
1245
1246 assert_eq!(
1247 format!(
1248 "{:?}",
1249 BuilderStep::DeclarativeSetBody {
1250 value: ValueSourceDef::Literal(Value::String("v".into())),
1251 }
1252 ),
1253 "DeclarativeSetBody { value: Literal(String(\"v\")) }"
1254 );
1255
1256 assert_eq!(
1257 format!(
1258 "{:?}",
1259 BuilderStep::DeclarativeSetProperty {
1260 key: "prop".into(),
1261 value_source: ValueSourceDef::Literal(Value::String("v".into())),
1262 }
1263 ),
1264 "DeclarativeSetProperty { key: \"prop\", value_source: Literal(String(\"v\")) }"
1265 );
1266
1267 assert_eq!(
1268 format!(
1269 "{:?}",
1270 BuilderStep::DeclarativeScript {
1271 expression: expr.clone(),
1272 }
1273 ),
1274 "DeclarativeScript { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
1275 );
1276
1277 let func_def = FunctionDefinition {
1279 id: FunctionId("test-id".into()),
1280 runtime: "my_runtime".into(),
1281 source: "${body}".into(),
1282 timeout_ms: 5000,
1283 route_id: None,
1284 step_index: None,
1285 };
1286 assert_eq!(
1287 format!(
1288 "{:?}",
1289 BuilderStep::DeclarativeFunction {
1290 definition: func_def,
1291 }
1292 ),
1293 "DeclarativeFunction { definition: FunctionDefinition { id: FunctionId(\"test-id\"), runtime: \"my_runtime\", source: \"${body}\", timeout_ms: 5000, route_id: None, step_index: None } }"
1294 );
1295
1296 assert_eq!(
1297 format!(
1298 "{:?}",
1299 BuilderStep::WireTap {
1300 uri: "mock:tap".into(),
1301 }
1302 ),
1303 "WireTap { uri: \"mock:tap\" }"
1304 );
1305
1306 assert_eq!(
1307 format!(
1308 "{:?}",
1309 BuilderStep::DeclarativeLog {
1310 level: camel_processor::LogLevel::Info,
1311 message: ValueSourceDef::Expression(expr.clone()),
1312 }
1313 ),
1314 "DeclarativeLog { level: Info, message: Expression(LanguageExpressionDef { language: \"simple\", source: \"${body}\" }) }"
1315 );
1316
1317 assert_eq!(
1318 format!(
1319 "{:?}",
1320 BuilderStep::Bean {
1321 name: "myBean".into(),
1322 method: "process".into(),
1323 }
1324 ),
1325 "Bean { name: \"myBean\", method: \"process\" }"
1326 );
1327
1328 assert_eq!(
1329 format!(
1330 "{:?}",
1331 BuilderStep::Script {
1332 language: "js".into(),
1333 script: "body".into(),
1334 }
1335 ),
1336 "Script { language: \"js\", script: \"body\" }"
1337 );
1338
1339 assert_eq!(
1340 format!(
1341 "{:?}",
1342 BuilderStep::Aggregate {
1343 config: camel_api::AggregatorConfig::correlate_by("id")
1344 .complete_when_size(1)
1345 .build()
1346 .unwrap(),
1347 }
1348 ),
1349 "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 } }"
1350 );
1351
1352 assert_eq!(
1353 format!(
1354 "{:?}",
1355 BuilderStep::DynamicRouter {
1356 config: DynamicRouterConfig::new(TargetSource::Sync(Arc::new(
1357 |_: &Exchange| Some("mock:dr".into())
1358 ))),
1359 }
1360 ),
1361 "DynamicRouter { config: DynamicRouterConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000, timeout: Some(60s) } }"
1362 );
1363
1364 assert_eq!(
1365 format!(
1366 "{:?}",
1367 BuilderStep::RoutingSlip {
1368 config: RoutingSlipConfig::new(TargetSource::Sync(Arc::new(|_: &Exchange| {
1369 Some("mock:rs".into())
1370 }))),
1371 }
1372 ),
1373 "RoutingSlip { config: RoutingSlipConfig { uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false } }"
1374 );
1375
1376 assert_eq!(
1377 format!(
1378 "{:?}",
1379 BuilderStep::RecipientList {
1380 config: RecipientListConfig::new(RecipientSource::Sync(Arc::new(
1381 |_: &Exchange| String::new()
1382 ))),
1383 }
1384 ),
1385 "RecipientList { config: RecipientListConfig { delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, max_recipients: 1000 } }"
1386 );
1387
1388 assert_eq!(
1389 format!(
1390 "{:?}",
1391 BuilderStep::Enrich {
1392 uri: "mock:enrich".into(),
1393 strategy: Some("agg".into()),
1394 timeout_ms: Some(1000),
1395 }
1396 ),
1397 "Enrich { uri: \"mock:enrich\", strategy: Some(\"agg\"), timeout_ms: Some(1000) }"
1398 );
1399
1400 assert_eq!(
1401 format!(
1402 "{:?}",
1403 BuilderStep::PollEnrich {
1404 uri: "mock:poll".into(),
1405 strategy: None,
1406 timeout_ms: None,
1407 }
1408 ),
1409 "PollEnrich { uri: \"mock:poll\", strategy: None, timeout_ms: None }"
1410 );
1411
1412 assert_eq!(
1413 format!(
1414 "{:?}",
1415 BuilderStep::Validate {
1416 predicate: expr.clone(),
1417 }
1418 ),
1419 "Validate { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" } }"
1420 );
1421
1422 assert_eq!(
1423 format!("{:?}", BuilderStep::Sampling { period: 100 }),
1424 "Sampling { period: 100 }"
1425 );
1426
1427 assert_eq!(
1428 format!(
1429 "{:?}",
1430 BuilderStep::Resequence {
1431 policy_config: Default::default(),
1432 }
1433 ),
1434 "Resequence { policy_config: ResequencePolicyConfig { mode: Batch { correlation: \"header.id\", sort: \"header.id\", completion: SizeOrTimeout(100, 30000) } } }"
1435 );
1436
1437 assert_eq!(
1439 format!(
1440 "{:?}",
1441 BuilderStep::DeclarativeFilter {
1442 predicate: expr.clone(),
1443 steps: vec![BuilderStep::Stop],
1444 }
1445 ),
1446 "DeclarativeFilter { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }"
1447 );
1448
1449 assert_eq!(
1450 format!(
1451 "{:?}",
1452 BuilderStep::DeclarativeSplit {
1453 expression: expr.clone(),
1454 aggregation: AggregationStrategy::Original,
1455 parallel: false,
1456 parallel_limit: Some(2),
1457 trace_item_threshold: None,
1458 stop_on_exception: true,
1459 steps: vec![BuilderStep::Stop],
1460 }
1461 ),
1462 "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] }"
1463 );
1464
1465 assert_eq!(
1466 format!(
1467 "{:?}",
1468 BuilderStep::Split {
1469 config: camel_api::splitter::SplitterConfig::new(
1470 camel_api::splitter::split_body_lines()
1471 ),
1472 steps: vec![BuilderStep::Stop],
1473 }
1474 ),
1475 "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] }"
1476 );
1477
1478 assert_eq!(
1479 format!(
1480 "{:?}",
1481 BuilderStep::Filter {
1482 predicate: FilterPredicate::new(|_: &Exchange| true),
1483 steps: vec![BuilderStep::Stop],
1484 }
1485 ),
1486 "Filter { predicate: FilterPredicate(..), steps: [Stop] }"
1487 );
1488
1489 assert_eq!(
1490 format!(
1491 "{:?}",
1492 BuilderStep::Throttle {
1493 config: camel_api::ThrottlerConfig::new(
1494 10,
1495 std::time::Duration::from_millis(10)
1496 ),
1497 steps: vec![BuilderStep::Stop],
1498 }
1499 ),
1500 "Throttle { config: ThrottlerConfig { max_requests: 10, period: 10ms, strategy: Delay }, steps: [Stop] }"
1501 );
1502
1503 assert_eq!(
1504 format!(
1505 "{:?}",
1506 BuilderStep::LoadBalance {
1507 config: camel_api::LoadBalancerConfig::round_robin(),
1508 steps: vec![BuilderStep::To("mock:l1".into())],
1509 }
1510 ),
1511 "LoadBalance { config: LoadBalancerConfig { strategy: RoundRobin }, steps: [To(\"mock:l1\")] }"
1512 );
1513
1514 assert_eq!(
1515 format!(
1516 "{:?}",
1517 BuilderStep::Delay {
1518 config: camel_api::DelayConfig::new(500),
1519 }
1520 ),
1521 "Delay { config: DelayConfig { delay_ms: 500, dynamic_header: None, max_delay_ms: 3600000 } }"
1522 );
1523
1524 assert_eq!(
1528 format!(
1529 "{:?}",
1530 BuilderStep::Choice {
1531 whens: vec![WhenStep {
1532 predicate: FilterPredicate::new(|_: &Exchange| true),
1533 steps: vec![BuilderStep::To("mock:a".into())],
1534 }],
1535 otherwise: None,
1536 }
1537 ),
1538 "Choice { whens: [WhenStep { predicate: FilterPredicate(..), steps: [To(\"mock:a\")] }], otherwise: None }"
1539 );
1540
1541 assert_eq!(
1542 format!(
1543 "{:?}",
1544 BuilderStep::DeclarativeChoice {
1545 whens: vec![DeclarativeWhenStep {
1546 predicate: expr.clone(),
1547 steps: vec![BuilderStep::Stop],
1548 }],
1549 otherwise: Some(vec![BuilderStep::Stop]),
1550 }
1551 ),
1552 "DeclarativeChoice { whens: [DeclarativeWhenStep { predicate: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [Stop] }], otherwise: Some([Stop]) }"
1553 );
1554
1555 assert_eq!(
1557 format!(
1558 "{:?}",
1559 BuilderStep::Multicast {
1560 steps: vec![BuilderStep::To("direct:a".into())],
1561 config: MulticastConfig::new(),
1562 }
1563 ),
1564 "Multicast { steps: [To(\"direct:a\")], config: MulticastConfig { parallel: false, parallel_limit: None, stop_on_exception: false, timeout: None, aggregation: LastWins } }"
1565 );
1566
1567 assert_eq!(
1569 format!(
1570 "{:?}",
1571 BuilderStep::DeclarativeDynamicRouter {
1572 expression: expr.clone(),
1573 uri_delimiter: ",".into(),
1574 cache_size: 1000,
1575 ignore_invalid_endpoints: false,
1576 max_iterations: 1000,
1577 }
1578 ),
1579 "DeclarativeDynamicRouter { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false, max_iterations: 1000 }"
1580 );
1581
1582 assert_eq!(
1583 format!(
1584 "{:?}",
1585 BuilderStep::DeclarativeRoutingSlip {
1586 expression: expr.clone(),
1587 uri_delimiter: ",".into(),
1588 cache_size: 1000,
1589 ignore_invalid_endpoints: false,
1590 }
1591 ),
1592 "DeclarativeRoutingSlip { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, uri_delimiter: \",\", cache_size: 1000, ignore_invalid_endpoints: false }"
1593 );
1594
1595 assert_eq!(
1597 format!(
1598 "{:?}",
1599 BuilderStep::DeclarativeRecipientList {
1600 expression: expr.clone(),
1601 delimiter: ",".into(),
1602 parallel: false,
1603 parallel_limit: None,
1604 stop_on_exception: false,
1605 aggregation: "original".into(),
1606 }
1607 ),
1608 "DeclarativeRecipientList { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, delimiter: \",\", parallel: false, parallel_limit: None, stop_on_exception: false, aggregation: \"original\" }"
1609 );
1610
1611 assert_eq!(
1613 format!(
1614 "{:?}",
1615 BuilderStep::Loop {
1616 config: camel_api::loop_eip::LoopConfig::new(LoopMode::Count(3)),
1617 steps: vec![],
1618 }
1619 ),
1620 "Loop { config: LoopConfig { mode: Count(3), max_iterations: 10000 }, steps: [] }"
1621 );
1622
1623 assert_eq!(
1624 format!(
1625 "{:?}",
1626 BuilderStep::DeclarativeLoop {
1627 count: Some(5),
1628 while_predicate: None,
1629 steps: vec![],
1630 max_iterations: Some(100),
1631 }
1632 ),
1633 "DeclarativeLoop { count: Some(5), while_predicate: None, steps: [], max_iterations: Some(100) }"
1634 );
1635
1636 assert_eq!(
1638 format!(
1639 "{:?}",
1640 BuilderStep::ClaimCheck {
1641 repository: "myRepo".into(),
1642 operation: "checkout".into(),
1643 key: expr.clone(),
1644 filter: None,
1645 }
1646 ),
1647 "ClaimCheck { repository: \"myRepo\", operation: \"checkout\", key: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, filter: None }"
1648 );
1649
1650 assert_eq!(
1652 format!(
1653 "{:?}",
1654 BuilderStep::Sort {
1655 expression: expr.clone(),
1656 reverse: false,
1657 }
1658 ),
1659 "Sort { expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, reverse: false }"
1660 );
1661
1662 assert_eq!(
1664 format!(
1665 "{:?}",
1666 BuilderStep::IdempotentConsumer {
1667 repository: "myRepo".into(),
1668 expression: expr.clone(),
1669 steps: vec![],
1670 eager: true,
1671 remove_on_failure: false,
1672 }
1673 ),
1674 "IdempotentConsumer { repository: \"myRepo\", expression: LanguageExpressionDef { language: \"simple\", source: \"${body}\" }, steps: [], eager: true, remove_on_failure: false }"
1675 );
1676
1677 assert_eq!(
1679 format!(
1680 "{:?}",
1681 BuilderStep::DeclarativeDoTry {
1682 try_steps: vec![BuilderStep::Stop],
1683 catch: vec![],
1684 finally: None,
1685 }
1686 ),
1687 "DeclarativeDoTry { try_steps: [Stop], catch: [], finally: None }"
1688 );
1689
1690 assert_eq!(
1692 format!(
1693 "{:?}",
1694 BuilderStep::DeclarativeStreamSplit {
1695 stream_config: StreamSplitConfig {
1696 format: StreamSplitFormat::Ndjson,
1697 max_record_bytes: 1024 * 1024,
1698 batch_size: 1,
1699 chunk_size: None,
1700 include_origin: true,
1701 },
1702 aggregation: AggregationStrategy::Original,
1703 stop_on_exception: true,
1704 steps: vec![BuilderStep::Stop],
1705 }
1706 ),
1707 "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] }"
1708 );
1709 }
1710
1711 #[test]
1712 fn test_builder_step_multicast_variant() {
1713 use camel_api::MulticastConfig;
1714
1715 let step = BuilderStep::Multicast {
1716 steps: vec![BuilderStep::To("direct:a".into())],
1717 config: MulticastConfig::new(),
1718 };
1719
1720 assert!(matches!(step, BuilderStep::Multicast { .. }));
1721 }
1722
1723 #[test]
1724 fn test_route_definition_defaults() {
1725 let def = RouteDefinition::new("direct:test", vec![]).with_route_id("test-route");
1726 assert_eq!(def.route_id(), "test-route");
1727 assert!(def.auto_startup());
1728 assert_eq!(def.startup_order(), 1000);
1729 }
1730
1731 #[test]
1732 fn test_route_definition_builders() {
1733 let def = RouteDefinition::new("direct:test", vec![])
1734 .with_route_id("my-route")
1735 .with_auto_startup(false)
1736 .with_startup_order(50);
1737 assert_eq!(def.route_id(), "my-route");
1738 assert!(!def.auto_startup());
1739 assert_eq!(def.startup_order(), 50);
1740 }
1741
1742 #[test]
1743 fn test_route_definition_accessors_cover_core_fields() {
1744 let def = RouteDefinition::new("direct:in", vec![BuilderStep::To("mock:out".into())])
1745 .with_route_id("accessor-route");
1746
1747 assert_eq!(def.from_uri(), "direct:in");
1748 assert_eq!(def.steps().len(), 1);
1749 assert!(matches!(def.steps()[0], BuilderStep::To(_)));
1750 }
1751
1752 #[test]
1753 fn test_route_definition_error_handler_circuit_breaker_and_concurrency_accessors() {
1754 use camel_api::circuit_breaker::CircuitBreakerConfig;
1755 use camel_api::error_handler::ErrorHandlerConfig;
1756 use camel_component_api::ConcurrencyModel;
1757
1758 let def = RouteDefinition::new("direct:test", vec![])
1759 .with_route_id("eh-route")
1760 .with_error_handler(ErrorHandlerConfig::dead_letter_channel("log:dlc"))
1761 .with_circuit_breaker(CircuitBreakerConfig::new())
1762 .with_concurrency(ConcurrencyModel::Concurrent { max: Some(4) });
1763
1764 let eh = def
1765 .error_handler_config()
1766 .expect("error handler should be set");
1767 assert_eq!(eh.dlc_uri.as_deref(), Some("log:dlc"));
1768 assert!(def.circuit_breaker_config().is_some());
1769 assert!(matches!(
1770 def.concurrency_override(),
1771 Some(ConcurrencyModel::Concurrent { max: Some(4) })
1772 ));
1773 }
1774
1775 #[test]
1776 fn test_builder_step_debug_covers_many_variants() {
1777 use camel_api::splitter::{AggregationStrategy, SplitterConfig, split_body_lines};
1778 use camel_api::{
1779 BoxProcessor, DynamicRouterConfig, Exchange, FilterPredicate, IdentityProcessor,
1780 OpaqueProcessor, RoutingSlipConfig, Value,
1781 };
1782 use std::sync::Arc;
1783
1784 let expr = LanguageExpressionDef {
1785 language: "simple".into(),
1786 source: "${body}".into(),
1787 };
1788
1789 let steps = vec![
1790 BuilderStep::Processor(OpaqueProcessor(BoxProcessor::new(IdentityProcessor))),
1791 BuilderStep::To("mock:out".into()),
1792 BuilderStep::Stop,
1793 BuilderStep::Log {
1794 level: camel_processor::LogLevel::Info,
1795 message: "hello".into(),
1796 },
1797 BuilderStep::DeclarativeSetHeader {
1798 key: "k".into(),
1799 value: ValueSourceDef::Literal(Value::String("v".into())),
1800 },
1801 BuilderStep::DeclarativeSetBody {
1802 value: ValueSourceDef::Expression(expr.clone()),
1803 },
1804 BuilderStep::DeclarativeFilter {
1805 predicate: expr.clone(),
1806 steps: vec![BuilderStep::Stop],
1807 },
1808 BuilderStep::DeclarativeChoice {
1809 whens: vec![DeclarativeWhenStep {
1810 predicate: expr.clone(),
1811 steps: vec![BuilderStep::Stop],
1812 }],
1813 otherwise: Some(vec![BuilderStep::Stop]),
1814 },
1815 BuilderStep::DeclarativeScript {
1816 expression: expr.clone(),
1817 },
1818 BuilderStep::DeclarativeSplit {
1819 expression: expr.clone(),
1820 aggregation: AggregationStrategy::Original,
1821 parallel: false,
1822 parallel_limit: Some(2),
1823 trace_item_threshold: None,
1824 stop_on_exception: true,
1825 steps: vec![BuilderStep::Stop],
1826 },
1827 BuilderStep::Split {
1828 config: SplitterConfig::new(split_body_lines()),
1829 steps: vec![BuilderStep::Stop],
1830 },
1831 BuilderStep::Aggregate {
1832 config: camel_api::AggregatorConfig::correlate_by("id")
1833 .complete_when_size(1)
1834 .build()
1835 .unwrap(),
1836 },
1837 BuilderStep::Filter {
1838 predicate: FilterPredicate::new(|_: &Exchange| true),
1839 steps: vec![BuilderStep::Stop],
1840 },
1841 BuilderStep::WireTap {
1842 uri: "mock:tap".into(),
1843 },
1844 BuilderStep::DeclarativeLog {
1845 level: camel_processor::LogLevel::Info,
1846 message: ValueSourceDef::Expression(expr.clone()),
1847 },
1848 BuilderStep::Bean {
1849 name: "bean".into(),
1850 method: "call".into(),
1851 },
1852 BuilderStep::Script {
1853 language: "rhai".into(),
1854 script: "body".into(),
1855 },
1856 BuilderStep::Throttle {
1857 config: camel_api::ThrottlerConfig::new(10, std::time::Duration::from_millis(10)),
1858 steps: vec![BuilderStep::Stop],
1859 },
1860 BuilderStep::LoadBalance {
1861 config: camel_api::LoadBalancerConfig::round_robin(),
1862 steps: vec![BuilderStep::To("mock:l1".into())],
1863 },
1864 BuilderStep::DynamicRouter {
1865 config: DynamicRouterConfig::new(TargetSource::Sync(Arc::new(|_| {
1866 Some("mock:dr".into())
1867 }))),
1868 },
1869 BuilderStep::RoutingSlip {
1870 config: RoutingSlipConfig::new(TargetSource::Sync(Arc::new(|_| {
1871 Some("mock:rs".into())
1872 }))),
1873 },
1874 ];
1875
1876 for step in steps {
1877 let dbg = format!("{step:?}");
1878 assert!(!dbg.is_empty());
1879 }
1880 }
1881
1882 #[test]
1883 fn test_route_definition_to_info_preserves_metadata() {
1884 let info = RouteDefinition::new("direct:test", vec![])
1885 .with_route_id("meta-route")
1886 .with_auto_startup(false)
1887 .with_startup_order(7)
1888 .to_info();
1889
1890 assert_eq!(info.route_id(), "meta-route");
1891 assert!(!info.auto_startup());
1892 assert_eq!(info.startup_order(), 7);
1893 }
1894
1895 #[test]
1896 fn test_choice_builder_step_debug() {
1897 use camel_api::FilterPredicate;
1898
1899 fn always_true(_: &camel_api::Exchange) -> bool {
1900 true
1901 }
1902
1903 let step = BuilderStep::Choice {
1904 whens: vec![WhenStep {
1905 predicate: FilterPredicate::new(always_true),
1906 steps: vec![BuilderStep::To("mock:a".into())],
1907 }],
1908 otherwise: None,
1909 };
1910 let debug = format!("{step:?}");
1911 assert!(debug.contains("Choice"));
1912 }
1913
1914 #[test]
1915 fn test_route_definition_unit_of_work() {
1916 use camel_api::UnitOfWorkConfig;
1917 let config = UnitOfWorkConfig {
1918 on_complete: Some("log:complete".into()),
1919 on_failure: Some("log:failed".into()),
1920 };
1921 let def = RouteDefinition::new("direct:test", vec![])
1922 .with_route_id("uow-test")
1923 .with_unit_of_work(config.clone());
1924 assert_eq!(
1925 def.unit_of_work_config().unwrap().on_complete.as_deref(),
1926 Some("log:complete")
1927 );
1928 assert_eq!(
1929 def.unit_of_work_config().unwrap().on_failure.as_deref(),
1930 Some("log:failed")
1931 );
1932
1933 let def_no_uow = RouteDefinition::new("direct:test", vec![]).with_route_id("no-uow");
1934 assert!(def_no_uow.unit_of_work_config().is_none());
1935 }
1936
1937 #[test]
1938 fn test_route_definition_security_policy_accessor() {
1939 use async_trait::async_trait;
1940 use camel_api::CamelError;
1941 use camel_api::Exchange;
1942 use camel_api::security_policy::{
1943 AuthContext, AuthorizationDecision, Principal, SecurityPolicy, SecurityPolicyConfig,
1944 };
1945
1946 struct StubPolicy;
1947 #[async_trait]
1948 impl SecurityPolicy for StubPolicy {
1949 async fn evaluate(
1950 &self,
1951 _exchange: &mut Exchange,
1952 _auth: &AuthContext<'_>,
1953 ) -> Result<AuthorizationDecision, CamelError> {
1954 Ok(AuthorizationDecision::Granted {
1955 principal: Principal {
1956 subject: "test".into(),
1957 issuer: "test".into(),
1958 audience: vec![],
1959 scopes: vec![],
1960 roles: vec![],
1961 claims: serde_json::Value::Null,
1962 },
1963 })
1964 }
1965 }
1966
1967 let def_no_sp = RouteDefinition::new("direct:test", vec![]).with_route_id("no-sp");
1968 assert!(def_no_sp.security_policy_config().is_none());
1969
1970 let def = RouteDefinition::new("direct:test", vec![])
1971 .with_route_id("sp-test")
1972 .with_security_policy(SecurityPolicyConfig::new(StubPolicy));
1973 assert!(def.security_policy_config().is_some());
1974 }
1975
1976 #[test]
1977 fn test_route_definition_security_authenticator_accessor() {
1978 use camel_api::security_policy::Principal;
1979
1980 struct TestAuth;
1981 #[async_trait::async_trait]
1982 impl TokenAuthenticator for TestAuth {
1983 async fn authenticate_bearer(
1984 &self,
1985 _token: &str,
1986 ) -> Result<Principal, camel_api::CamelError> {
1987 Ok(Principal {
1988 subject: "test".into(),
1989 issuer: "test".into(),
1990 audience: vec![],
1991 scopes: vec![],
1992 roles: vec![],
1993 claims: serde_json::Value::Null,
1994 })
1995 }
1996 }
1997
1998 let def_no_auth = RouteDefinition::new("direct:test".to_string(), vec![]);
1999 assert!(def_no_auth.security_authenticator().is_none());
2000
2001 let auth = Arc::new(TestAuth);
2002 let def = RouteDefinition::new("direct:test".to_string(), vec![])
2003 .with_security_authenticator(auth);
2004 assert!(def.security_authenticator().is_some());
2005 }
2006
2007 #[test]
2008 fn test_map_steps_swaps_steps_and_preserves_other_fields() {
2009 let original = RouteDefinition::new(
2010 "direct:test".to_string(),
2011 vec![BuilderStep::To("mock:a".into()), BuilderStep::Stop],
2012 )
2013 .with_route_id("my-route");
2014
2015 let mapped = original.map_steps(|steps| {
2016 let mut out = Vec::with_capacity(steps.len() + 1);
2017 out.push(BuilderStep::To("mock:prefix".into()));
2018 out.extend(steps);
2019 out
2020 });
2021
2022 assert_eq!(mapped.steps().len(), 3);
2024 assert!(matches!(mapped.steps()[0], BuilderStep::To(ref s) if s == "mock:prefix"));
2025 assert!(matches!(mapped.steps()[1], BuilderStep::To(ref s) if s == "mock:a"));
2026 assert_eq!(mapped.route_id(), "my-route");
2028 }
2029
2030 #[test]
2031 fn circuit_breaker_fallback_accessor_returns_steps() {
2032 let def = RouteDefinition::new("direct:start", vec![])
2033 .with_circuit_breaker_fallback(vec![BuilderStep::To("mock:out".into())]);
2034 assert_eq!(def.circuit_breaker_fallback().len(), 1);
2035 }
2036}