1use crate::lifecycle::application::route_definition::{BuilderStep, RouteDefinition};
9use camel_api::{CamelError, ConfigValidationError};
10use camel_endpoint::uri::parse_bool_param;
11
12pub trait ConfigCheck: Send + Sync {
20 fn name(&self) -> &'static str;
23
24 fn description(&self) -> &'static str;
26
27 fn run(&self) -> Result<(), CamelError>;
29}
30
31#[derive(Debug, Default, Clone)]
36pub struct StartupValidationReport {
37 pub failures: Vec<String>,
39}
40
41impl StartupValidationReport {
42 pub fn is_ok(&self) -> bool {
45 self.failures.is_empty()
46 }
47}
48
49pub fn run_startup_validation(
54 checks: Vec<Box<dyn ConfigCheck>>,
55) -> Result<StartupValidationReport, CamelError> {
56 let mut report = StartupValidationReport::default();
57 for check in &checks {
58 if let Err(e) = check.run() {
59 report.failures.push(format!("{}: {}", check.name(), e));
60 }
61 }
62 if !report.is_ok() {
63 return Err(CamelError::Config(report.failures.join("; ")));
64 }
65 Ok(report)
66}
67
68pub struct SqlDynamicQueryCheck {
75 pub use_message_body_for_sql: bool,
76 pub allow_dynamic_query: bool,
77}
78
79impl ConfigCheck for SqlDynamicQueryCheck {
80 fn name(&self) -> &'static str {
81 "sql-dynamic-query"
82 }
83 fn description(&self) -> &'static str {
84 "SQL use_message_body_for_sql requires allow_dynamic_query=true"
85 }
86 fn run(&self) -> Result<(), CamelError> {
87 if self.use_message_body_for_sql && !self.allow_dynamic_query {
88 return Err(CamelError::from(
89 ConfigValidationError::SqlDynamicQueryWithoutAllowDynamic,
90 ));
91 }
92 Ok(())
93 }
94}
95
96pub fn scan_route_definitions_for_sql_checks(
111 routes: &[RouteDefinition],
112) -> Vec<Box<dyn ConfigCheck>> {
113 let mut out: Vec<Box<dyn ConfigCheck>> = Vec::new();
114 for route in routes {
115 collect_sql_checks_for_uri(route.from_uri(), &mut out);
116 for step in route.steps() {
117 for_each_step_uri(step, &mut |uri| collect_sql_checks_for_uri(uri, &mut out));
118 }
119 }
120 out
121}
122
123fn for_each_step_uri<F: FnMut(&str)>(step: &BuilderStep, f: &mut F) {
137 match step {
138 BuilderStep::To(uri) => f(uri),
139 BuilderStep::WireTap { uri } | BuilderStep::Enrich { uri, .. } => {
140 f(uri);
141 }
142 BuilderStep::PollEnrich { uri, .. } => {
143 f(uri);
144 }
145 BuilderStep::Filter { steps, .. }
146 | BuilderStep::DeclarativeFilter { steps, .. }
147 | BuilderStep::Split { steps, .. }
148 | BuilderStep::DeclarativeSplit { steps, .. }
149 | BuilderStep::DeclarativeStreamSplit { steps, .. }
150 | BuilderStep::StreamSplit { steps, .. }
151 | BuilderStep::Multicast { steps, .. }
152 | BuilderStep::Throttle { steps, .. }
153 | BuilderStep::LoadBalance { steps, .. }
154 | BuilderStep::Loop { steps, .. }
155 | BuilderStep::DeclarativeLoop { steps, .. }
156 | BuilderStep::IdempotentConsumer { steps, .. } => {
157 for s in steps {
158 for_each_step_uri(s, f);
159 }
160 }
161 BuilderStep::Choice { whens, otherwise } => {
162 for when in whens {
163 for s in &when.steps {
164 for_each_step_uri(s, f);
165 }
166 }
167 if let Some(ow) = otherwise {
168 for s in ow {
169 for_each_step_uri(s, f);
170 }
171 }
172 }
173 BuilderStep::DeclarativeChoice { whens, otherwise } => {
174 for when in whens {
175 for s in &when.steps {
176 for_each_step_uri(s, f);
177 }
178 }
179 if let Some(ow) = otherwise {
180 for s in ow {
181 for_each_step_uri(s, f);
182 }
183 }
184 }
185 BuilderStep::DeclarativeDoTry {
186 try_steps,
187 catch,
188 finally,
189 } => {
190 for s in try_steps {
191 for_each_step_uri(s, f);
192 }
193 for clause in catch {
194 for s in &clause.steps {
195 for_each_step_uri(s, f);
196 }
197 }
198 if let Some(fin) = finally {
199 for s in &fin.steps {
200 for_each_step_uri(s, f);
201 }
202 }
203 }
204 _ => {}
208 }
209}
210
211pub fn route_definitions_reference_scheme(routes: &[RouteDefinition], scheme: &str) -> bool {
229 use std::cell::Cell;
230 let found = Cell::new(false);
231 let mut check = |uri: &str| {
232 if found.get() {
233 return;
234 }
235 if let Ok(parts) = camel_endpoint::parse_uri(uri)
236 && parts.scheme == scheme
237 {
238 found.set(true);
239 }
240 };
241 for route in routes {
242 if found.get() {
243 break;
244 }
245 check(route.from_uri());
246 if found.get() {
247 break;
248 }
249 for step in route.steps() {
250 for_each_step_uri(step, &mut check);
251 if found.get() {
252 break;
253 }
254 }
255 }
256 found.get()
257}
258
259fn collect_sql_checks_for_uri(uri: &str, out: &mut Vec<Box<dyn ConfigCheck>>) {
260 let Ok(parts) = camel_endpoint::parse_uri(uri) else {
261 return;
262 };
263 if parts.scheme != "sql" {
264 return;
265 }
266 let use_body = parts
270 .params
271 .get("useMessageBodyForSql")
272 .and_then(|v| parse_bool_param(v).ok())
273 .unwrap_or(false);
274 let allow_dynamic = parts
275 .params
276 .get("allowDynamicQuery")
277 .and_then(|v| parse_bool_param(v).ok())
278 .unwrap_or(false);
279 if use_body || allow_dynamic {
280 out.push(Box::new(SqlDynamicQueryCheck {
284 use_message_body_for_sql: use_body,
285 allow_dynamic_query: allow_dynamic,
286 }));
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293
294 #[test]
296 fn empty_registry_returns_empty_ok_report() {
297 let report = run_startup_validation(vec![]).expect("empty registry must return Ok");
298 assert!(report.is_ok());
299 assert!(report.failures.is_empty());
300 }
301
302 #[test]
306 fn trait_is_object_safe_and_dispatchable() {
307 struct AlwaysOk;
308 impl ConfigCheck for AlwaysOk {
309 fn name(&self) -> &'static str {
310 "always-ok"
311 }
312 fn description(&self) -> &'static str {
313 "always-ok skeleton check"
314 }
315 fn run(&self) -> Result<(), CamelError> {
316 Ok(())
317 }
318 }
319
320 let check: Box<dyn ConfigCheck> = Box::new(AlwaysOk);
321 assert_eq!(check.name(), "always-ok");
322 assert!(check.run().is_ok());
323 }
324
325 #[test]
329 fn sql_dynamic_query_check_refuses_startup() {
330 let check = SqlDynamicQueryCheck {
331 use_message_body_for_sql: true,
332 allow_dynamic_query: false,
333 };
334 let result = run_startup_validation(vec![Box::new(check)]);
335 match result {
336 Err(CamelError::Config(msg)) => {
337 assert!(msg.contains("sql-dynamic-query"));
338 assert!(msg.contains("allow_dynamic_query"));
339 }
340 other => panic!("expected CamelError::Config, got {other:?}"),
341 }
342 }
343
344 #[test]
351 fn sql_dynamic_query_check_run_returns_typed_error() {
352 let check = SqlDynamicQueryCheck {
353 use_message_body_for_sql: true,
354 allow_dynamic_query: false,
355 };
356 let result = check.run();
357 assert!(
358 matches!(
359 result,
360 Err(CamelError::ConfigValidation(
361 ConfigValidationError::SqlDynamicQueryWithoutAllowDynamic,
362 ))
363 ),
364 "expected ConfigValidation(SqlDynamicQueryWithoutAllowDynamic), got: {result:?}"
365 );
366 }
367
368 #[test]
370 fn sql_dynamic_query_check_passes_with_opt_in() {
371 let check = SqlDynamicQueryCheck {
372 use_message_body_for_sql: true,
373 allow_dynamic_query: true,
374 };
375 let report =
376 run_startup_validation(vec![Box::new(check)]).expect("opt-in must satisfy the check");
377 assert!(report.is_ok());
378 }
379
380 #[test]
383 fn sql_dynamic_query_check_passes_without_body_sql() {
384 let check = SqlDynamicQueryCheck {
385 use_message_body_for_sql: false,
386 allow_dynamic_query: false,
387 };
388 let report = run_startup_validation(vec![Box::new(check)])
389 .expect("no body-sourced queries → no fail-closed condition");
390 assert!(report.is_ok());
391 }
392
393 #[test]
397 fn scanner_flags_from_sql_endpoint_with_body_but_no_allow() {
398 let route = RouteDefinition::new(
399 "sql:select * from t?db_url=postgres://x/y&useMessageBodyForSql=true",
400 vec![],
401 )
402 .with_route_id("r".to_string());
403 let checks = scan_route_definitions_for_sql_checks(&[route]);
404 assert_eq!(checks.len(), 1);
405 assert!(run_startup_validation(checks).is_err());
406 }
407
408 #[test]
411 fn scanner_walks_top_level_to_sql_step() {
412 let route = RouteDefinition::new(
413 "direct:start",
414 vec![BuilderStep::To(
415 "sql:select 1?db_url=postgres://x/y&useMessageBodyForSql=true".to_string(),
416 )],
417 )
418 .with_route_id("r".to_string());
419 let checks = scan_route_definitions_for_sql_checks(&[route]);
420 assert_eq!(checks.len(), 1);
421 assert!(run_startup_validation(checks).is_err());
422 }
423
424 #[test]
426 fn scanner_emits_nothing_for_non_sql_route() {
427 let route = RouteDefinition::new("timer:tick?period=1000", vec![]);
428 let checks = scan_route_definitions_for_sql_checks(&[route]);
429 assert!(checks.is_empty());
430 }
431
432 #[test]
436 fn scanner_skips_sql_endpoint_without_dynamic_intent() {
437 let route = RouteDefinition::new("sql:select 1?db_url=postgres://x/y", vec![]);
438 let checks = scan_route_definitions_for_sql_checks(&[route]);
439 assert!(checks.is_empty());
440 }
441
442 #[test]
454 fn scheme_scanner_detects_exec_from_uri() {
455 let route = RouteDefinition::new("exec:echo", vec![]).with_route_id("r".to_string());
456 assert!(route_definitions_reference_scheme(&[route], "exec"));
457 }
458
459 #[test]
461 fn scheme_scanner_detects_exec_in_to_step() {
462 let route = RouteDefinition::new(
463 "timer:tick?period=500",
464 vec![BuilderStep::To("exec:echo".to_string())],
465 )
466 .with_route_id("r".to_string());
467 assert!(route_definitions_reference_scheme(&[route], "exec"));
468 }
469
470 #[test]
472 fn scheme_scanner_detects_exec_in_wiretap() {
473 let route = RouteDefinition::new(
474 "timer:tick",
475 vec![BuilderStep::WireTap {
476 uri: "exec:audit".to_string(),
477 }],
478 )
479 .with_route_id("r".to_string());
480 assert!(route_definitions_reference_scheme(&[route], "exec"));
481 }
482
483 #[test]
485 fn scheme_scanner_detects_exec_in_enrich() {
486 let route = RouteDefinition::new(
487 "direct:start",
488 vec![BuilderStep::Enrich {
489 uri: "exec:enricher".to_string(),
490 strategy: Some("agg".to_string()),
491 timeout_ms: Some(1000),
492 }],
493 )
494 .with_route_id("r".to_string());
495 assert!(route_definitions_reference_scheme(&[route], "exec"));
496 }
497
498 #[test]
500 fn scheme_scanner_detects_exec_in_pollenrich() {
501 let route = RouteDefinition::new(
502 "direct:start",
503 vec![BuilderStep::PollEnrich {
504 uri: "exec:poller".to_string(),
505 strategy: None,
506 timeout_ms: Some(500),
507 }],
508 )
509 .with_route_id("r".to_string());
510 assert!(route_definitions_reference_scheme(&[route], "exec"));
511 }
512
513 #[test]
515 fn scheme_scanner_detects_exec_in_filter() {
516 use camel_api::FilterPredicate;
517 let route = RouteDefinition::new(
518 "direct:start",
519 vec![BuilderStep::Filter {
520 predicate: FilterPredicate::new(|_| true),
521 steps: vec![BuilderStep::To("exec:echo".to_string())],
522 }],
523 )
524 .with_route_id("r".to_string());
525 assert!(route_definitions_reference_scheme(&[route], "exec"));
526 }
527
528 #[test]
530 fn scheme_scanner_detects_exec_in_split() {
531 use camel_api::splitter::{AggregationStrategy, SplitterConfig, split_body_lines};
532 let route = RouteDefinition::new(
533 "direct:start",
534 vec![BuilderStep::Split {
535 config: SplitterConfig::new(split_body_lines())
536 .aggregation(AggregationStrategy::Original),
537 steps: vec![BuilderStep::To("exec:echo".to_string())],
538 }],
539 )
540 .with_route_id("r".to_string());
541 assert!(route_definitions_reference_scheme(&[route], "exec"));
542 }
543
544 #[test]
546 fn scheme_scanner_detects_exec_in_multicast() {
547 use camel_api::MulticastConfig;
548 let route = RouteDefinition::new(
549 "direct:start",
550 vec![BuilderStep::Multicast {
551 steps: vec![BuilderStep::To("exec:echo".to_string())],
552 config: MulticastConfig::new(),
553 }],
554 )
555 .with_route_id("r".to_string());
556 assert!(route_definitions_reference_scheme(&[route], "exec"));
557 }
558
559 #[test]
561 fn scheme_scanner_detects_exec_in_loop() {
562 use camel_api::loop_eip::{LoopConfig, LoopMode};
563 let route = RouteDefinition::new(
564 "direct:start",
565 vec![BuilderStep::Loop {
566 config: LoopConfig::new(LoopMode::Count(3)),
567 steps: vec![BuilderStep::To("exec:echo".to_string())],
568 }],
569 )
570 .with_route_id("r".to_string());
571 assert!(route_definitions_reference_scheme(&[route], "exec"));
572 }
573
574 #[test]
576 fn scheme_scanner_detects_exec_in_idempotent_consumer() {
577 use crate::lifecycle::application::route_definition::LanguageExpressionDef;
578 let expr = LanguageExpressionDef {
579 language: "simple".into(),
580 source: "${header.id}".into(),
581 };
582 let route = RouteDefinition::new(
583 "direct:start",
584 vec![BuilderStep::IdempotentConsumer {
585 repository: "myRepo".to_string(),
586 expression: expr,
587 steps: vec![BuilderStep::To("exec:echo".to_string())],
588 eager: false,
589 remove_on_failure: false,
590 }],
591 )
592 .with_route_id("r".to_string());
593 assert!(route_definitions_reference_scheme(&[route], "exec"));
594 }
595
596 #[test]
598 fn scheme_scanner_detects_exec_in_throttle() {
599 use camel_api::ThrottlerConfig;
600 let route = RouteDefinition::new(
601 "direct:start",
602 vec![BuilderStep::Throttle {
603 config: ThrottlerConfig::new(10, std::time::Duration::from_millis(10)),
604 steps: vec![BuilderStep::To("exec:echo".to_string())],
605 }],
606 )
607 .with_route_id("r".to_string());
608 assert!(route_definitions_reference_scheme(&[route], "exec"));
609 }
610
611 #[test]
613 fn scheme_scanner_detects_exec_in_loadbalance() {
614 use camel_api::LoadBalancerConfig;
615 let route = RouteDefinition::new(
616 "direct:start",
617 vec![BuilderStep::LoadBalance {
618 config: LoadBalancerConfig::round_robin(),
619 steps: vec![BuilderStep::To("exec:echo".to_string())],
620 }],
621 )
622 .with_route_id("r".to_string());
623 assert!(route_definitions_reference_scheme(&[route], "exec"));
624 }
625
626 #[test]
628 fn scheme_scanner_detects_exec_in_declarative_filter() {
629 use crate::lifecycle::application::route_definition::LanguageExpressionDef;
630 let expr = LanguageExpressionDef {
631 language: "simple".into(),
632 source: "${body}".into(),
633 };
634 let route = RouteDefinition::new(
635 "direct:start",
636 vec![BuilderStep::DeclarativeFilter {
637 predicate: expr,
638 steps: vec![BuilderStep::To("exec:echo".to_string())],
639 }],
640 )
641 .with_route_id("r".to_string());
642 assert!(route_definitions_reference_scheme(&[route], "exec"));
643 }
644
645 #[test]
647 fn scheme_scanner_detects_exec_in_declarative_split() {
648 use crate::lifecycle::application::route_definition::LanguageExpressionDef;
649 use camel_api::splitter::AggregationStrategy;
650 let expr = LanguageExpressionDef {
651 language: "simple".into(),
652 source: "${body}".into(),
653 };
654 let route = RouteDefinition::new(
655 "direct:start",
656 vec![BuilderStep::DeclarativeSplit {
657 expression: expr,
658 aggregation: AggregationStrategy::Original,
659 parallel: false,
660 parallel_limit: None,
661 trace_item_threshold: None,
662 stop_on_exception: true,
663 steps: vec![BuilderStep::To("exec:echo".to_string())],
664 }],
665 )
666 .with_route_id("r".to_string());
667 assert!(route_definitions_reference_scheme(&[route], "exec"));
668 }
669
670 #[test]
672 fn scheme_scanner_detects_exec_in_declarative_stream_split() {
673 use camel_api::splitter::{AggregationStrategy, StreamSplitConfig, StreamSplitFormat};
674 let route = RouteDefinition::new(
675 "direct:start",
676 vec![BuilderStep::DeclarativeStreamSplit {
677 stream_config: StreamSplitConfig {
678 format: StreamSplitFormat::Ndjson,
679 max_record_bytes: 1024,
680 batch_size: 1,
681 chunk_size: None,
682 include_origin: true,
683 },
684 aggregation: AggregationStrategy::Original,
685 stop_on_exception: true,
686 steps: vec![BuilderStep::To("exec:echo".to_string())],
687 }],
688 )
689 .with_route_id("r".to_string());
690 assert!(route_definitions_reference_scheme(&[route], "exec"));
691 }
692
693 #[test]
695 fn scheme_scanner_detects_exec_in_declarative_loop() {
696 use crate::lifecycle::application::route_definition::LanguageExpressionDef;
697 let expr = LanguageExpressionDef {
698 language: "simple".into(),
699 source: "${body}".into(),
700 };
701 let route = RouteDefinition::new(
702 "direct:start",
703 vec![BuilderStep::DeclarativeLoop {
704 count: Some(5),
705 while_predicate: Some(expr),
706 steps: vec![BuilderStep::To("exec:echo".to_string())],
707 max_iterations: Some(100),
708 }],
709 )
710 .with_route_id("r".to_string());
711 assert!(route_definitions_reference_scheme(&[route], "exec"));
712 }
713
714 #[test]
716 fn scheme_scanner_detects_exec_in_choice_branch() {
717 use crate::lifecycle::application::route_definition::WhenStep;
718 use camel_api::FilterPredicate;
719 let route = RouteDefinition::new(
720 "direct:start",
721 vec![BuilderStep::Choice {
722 whens: vec![WhenStep {
723 predicate: FilterPredicate::new(|_| true),
724 steps: vec![BuilderStep::To("exec:echo".to_string())],
725 }],
726 otherwise: None,
727 }],
728 )
729 .with_route_id("r".to_string());
730 assert!(route_definitions_reference_scheme(&[route], "exec"));
731 }
732
733 #[test]
735 fn scheme_scanner_detects_exec_in_choice_otherwise() {
736 use crate::lifecycle::application::route_definition::WhenStep;
737 use camel_api::FilterPredicate;
738 let route = RouteDefinition::new(
739 "direct:start",
740 vec![BuilderStep::Choice {
741 whens: vec![WhenStep {
742 predicate: FilterPredicate::new(|_| false),
743 steps: vec![BuilderStep::To("log:info".to_string())],
744 }],
745 otherwise: Some(vec![BuilderStep::To("exec:echo".to_string())]),
746 }],
747 )
748 .with_route_id("r".to_string());
749 assert!(route_definitions_reference_scheme(&[route], "exec"));
750 }
751
752 #[test]
754 fn scheme_scanner_detects_exec_in_declarative_choice_when() {
755 use crate::lifecycle::application::route_definition::{
756 DeclarativeWhenStep, LanguageExpressionDef,
757 };
758 let expr = LanguageExpressionDef {
759 language: "simple".into(),
760 source: "${body}".into(),
761 };
762 let route = RouteDefinition::new(
763 "direct:start",
764 vec![BuilderStep::DeclarativeChoice {
765 whens: vec![DeclarativeWhenStep {
766 predicate: expr,
767 steps: vec![BuilderStep::To("exec:echo".to_string())],
768 }],
769 otherwise: None,
770 }],
771 )
772 .with_route_id("r".to_string());
773 assert!(route_definitions_reference_scheme(&[route], "exec"));
774 }
775
776 #[test]
778 fn scheme_scanner_detects_exec_in_declarative_choice_otherwise() {
779 use crate::lifecycle::application::route_definition::{
780 DeclarativeWhenStep, LanguageExpressionDef,
781 };
782 let expr = LanguageExpressionDef {
783 language: "simple".into(),
784 source: "${body}".into(),
785 };
786 let route = RouteDefinition::new(
787 "direct:start",
788 vec![BuilderStep::DeclarativeChoice {
789 whens: vec![DeclarativeWhenStep {
790 predicate: expr,
791 steps: vec![BuilderStep::To("log:info".to_string())],
792 }],
793 otherwise: Some(vec![BuilderStep::To("exec:echo".to_string())]),
794 }],
795 )
796 .with_route_id("r".to_string());
797 assert!(route_definitions_reference_scheme(&[route], "exec"));
798 }
799
800 #[test]
802 fn scheme_scanner_detects_exec_in_dotry() {
803 let route = RouteDefinition::new(
804 "direct:start",
805 vec![BuilderStep::DeclarativeDoTry {
806 try_steps: vec![BuilderStep::To("exec:echo".to_string())],
807 catch: vec![],
808 finally: None,
809 }],
810 )
811 .with_route_id("r".to_string());
812 assert!(route_definitions_reference_scheme(&[route], "exec"));
813 }
814
815 #[test]
817 fn scheme_scanner_detects_exec_in_dotry_catch() {
818 use crate::lifecycle::application::route_definition::DoTryCatchClauseBuilder;
819 let route = RouteDefinition::new(
820 "direct:start",
821 vec![BuilderStep::DeclarativeDoTry {
822 try_steps: vec![BuilderStep::To("log:info".to_string())],
823 catch: vec![DoTryCatchClauseBuilder {
824 exception: None,
825 when: None,
826 on_when: None,
827 disposition: camel_api::error_handler::ExceptionDisposition::Propagate,
828 steps: vec![BuilderStep::To("exec:echo".to_string())],
829 }],
830 finally: None,
831 }],
832 )
833 .with_route_id("r".to_string());
834 assert!(route_definitions_reference_scheme(&[route], "exec"));
835 }
836
837 #[test]
839 fn scheme_scanner_detects_exec_in_dotry_finally() {
840 use crate::lifecycle::application::route_definition::DoTryFinallyBuilder;
841 let route = RouteDefinition::new(
842 "direct:start",
843 vec![BuilderStep::DeclarativeDoTry {
844 try_steps: vec![BuilderStep::To("log:info".to_string())],
845 catch: vec![],
846 finally: Some(DoTryFinallyBuilder {
847 on_when: None,
848 steps: vec![BuilderStep::To("exec:echo".to_string())],
849 }),
850 }],
851 )
852 .with_route_id("r".to_string());
853 assert!(route_definitions_reference_scheme(&[route], "exec"));
854 }
855
856 #[test]
858 fn scheme_scanner_detects_exec_across_multiple_routes() {
859 let r1 = RouteDefinition::new("timer:tick", vec![]).with_route_id("r1".to_string());
860 let r2 = RouteDefinition::new(
861 "direct:start",
862 vec![BuilderStep::To("exec:echo".to_string())],
863 )
864 .with_route_id("r2".to_string());
865 assert!(route_definitions_reference_scheme(&[r1, r2], "exec"));
866 }
867
868 #[test]
870 fn scheme_scanner_false_for_non_exec_route() {
871 let route =
872 RouteDefinition::new("timer:tick", vec![BuilderStep::To("log:info".to_string())])
873 .with_route_id("r".to_string());
874 assert!(!route_definitions_reference_scheme(&[route], "exec"));
875 }
876
877 #[test]
880 fn scheme_scanner_false_for_dynamic_uri_only() {
881 use camel_api::RoutingSlipConfig;
882 use std::sync::Arc;
883 let route = RouteDefinition::new(
884 "timer:tick",
885 vec![BuilderStep::RoutingSlip {
886 config: RoutingSlipConfig::new(camel_api::TargetSource::Sync(Arc::new(|_| {
887 Some("exec:echo".to_string())
888 }))),
889 }],
890 )
891 .with_route_id("r".to_string());
892 assert!(!route_definitions_reference_scheme(&[route], "exec"));
893 }
894}