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