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 trace_item_threshold: None,
661 stop_on_exception: true,
662 steps: vec![BuilderStep::To("exec:echo".to_string())],
663 }],
664 )
665 .with_route_id("r".to_string());
666 assert!(route_definitions_reference_scheme(&[route], "exec"));
667 }
668
669 #[test]
671 fn scheme_scanner_detects_exec_in_declarative_stream_split() {
672 use camel_api::splitter::{AggregationStrategy, StreamSplitConfig, StreamSplitFormat};
673 let route = RouteDefinition::new(
674 "direct:start",
675 vec![BuilderStep::DeclarativeStreamSplit {
676 stream_config: StreamSplitConfig {
677 format: StreamSplitFormat::Ndjson,
678 max_record_bytes: 1024,
679 batch_size: 1,
680 chunk_size: None,
681 include_origin: true,
682 },
683 aggregation: AggregationStrategy::Original,
684 stop_on_exception: true,
685 steps: vec![BuilderStep::To("exec:echo".to_string())],
686 }],
687 )
688 .with_route_id("r".to_string());
689 assert!(route_definitions_reference_scheme(&[route], "exec"));
690 }
691
692 #[test]
694 fn scheme_scanner_detects_exec_in_declarative_loop() {
695 use crate::lifecycle::application::route_definition::LanguageExpressionDef;
696 let expr = LanguageExpressionDef {
697 language: "simple".into(),
698 source: "${body}".into(),
699 };
700 let route = RouteDefinition::new(
701 "direct:start",
702 vec![BuilderStep::DeclarativeLoop {
703 count: Some(5),
704 while_predicate: Some(expr),
705 steps: vec![BuilderStep::To("exec:echo".to_string())],
706 max_iterations: Some(100),
707 }],
708 )
709 .with_route_id("r".to_string());
710 assert!(route_definitions_reference_scheme(&[route], "exec"));
711 }
712
713 #[test]
715 fn scheme_scanner_detects_exec_in_choice_branch() {
716 use crate::lifecycle::application::route_definition::WhenStep;
717 use camel_api::FilterPredicate;
718 let route = RouteDefinition::new(
719 "direct:start",
720 vec![BuilderStep::Choice {
721 whens: vec![WhenStep {
722 predicate: FilterPredicate::new(|_| true),
723 steps: vec![BuilderStep::To("exec:echo".to_string())],
724 }],
725 otherwise: None,
726 }],
727 )
728 .with_route_id("r".to_string());
729 assert!(route_definitions_reference_scheme(&[route], "exec"));
730 }
731
732 #[test]
734 fn scheme_scanner_detects_exec_in_choice_otherwise() {
735 use crate::lifecycle::application::route_definition::WhenStep;
736 use camel_api::FilterPredicate;
737 let route = RouteDefinition::new(
738 "direct:start",
739 vec![BuilderStep::Choice {
740 whens: vec![WhenStep {
741 predicate: FilterPredicate::new(|_| false),
742 steps: vec![BuilderStep::To("log:info".to_string())],
743 }],
744 otherwise: Some(vec![BuilderStep::To("exec:echo".to_string())]),
745 }],
746 )
747 .with_route_id("r".to_string());
748 assert!(route_definitions_reference_scheme(&[route], "exec"));
749 }
750
751 #[test]
753 fn scheme_scanner_detects_exec_in_declarative_choice_when() {
754 use crate::lifecycle::application::route_definition::{
755 DeclarativeWhenStep, LanguageExpressionDef,
756 };
757 let expr = LanguageExpressionDef {
758 language: "simple".into(),
759 source: "${body}".into(),
760 };
761 let route = RouteDefinition::new(
762 "direct:start",
763 vec![BuilderStep::DeclarativeChoice {
764 whens: vec![DeclarativeWhenStep {
765 predicate: expr,
766 steps: vec![BuilderStep::To("exec:echo".to_string())],
767 }],
768 otherwise: None,
769 }],
770 )
771 .with_route_id("r".to_string());
772 assert!(route_definitions_reference_scheme(&[route], "exec"));
773 }
774
775 #[test]
777 fn scheme_scanner_detects_exec_in_declarative_choice_otherwise() {
778 use crate::lifecycle::application::route_definition::{
779 DeclarativeWhenStep, LanguageExpressionDef,
780 };
781 let expr = LanguageExpressionDef {
782 language: "simple".into(),
783 source: "${body}".into(),
784 };
785 let route = RouteDefinition::new(
786 "direct:start",
787 vec![BuilderStep::DeclarativeChoice {
788 whens: vec![DeclarativeWhenStep {
789 predicate: expr,
790 steps: vec![BuilderStep::To("log:info".to_string())],
791 }],
792 otherwise: Some(vec![BuilderStep::To("exec:echo".to_string())]),
793 }],
794 )
795 .with_route_id("r".to_string());
796 assert!(route_definitions_reference_scheme(&[route], "exec"));
797 }
798
799 #[test]
801 fn scheme_scanner_detects_exec_in_dotry() {
802 let route = RouteDefinition::new(
803 "direct:start",
804 vec![BuilderStep::DeclarativeDoTry {
805 try_steps: vec![BuilderStep::To("exec:echo".to_string())],
806 catch: vec![],
807 finally: None,
808 }],
809 )
810 .with_route_id("r".to_string());
811 assert!(route_definitions_reference_scheme(&[route], "exec"));
812 }
813
814 #[test]
816 fn scheme_scanner_detects_exec_in_dotry_catch() {
817 use crate::lifecycle::application::route_definition::DoTryCatchClauseBuilder;
818 let route = RouteDefinition::new(
819 "direct:start",
820 vec![BuilderStep::DeclarativeDoTry {
821 try_steps: vec![BuilderStep::To("log:info".to_string())],
822 catch: vec![DoTryCatchClauseBuilder {
823 exception: None,
824 when: None,
825 on_when: None,
826 disposition: camel_api::error_handler::ExceptionDisposition::Propagate,
827 steps: vec![BuilderStep::To("exec:echo".to_string())],
828 }],
829 finally: None,
830 }],
831 )
832 .with_route_id("r".to_string());
833 assert!(route_definitions_reference_scheme(&[route], "exec"));
834 }
835
836 #[test]
838 fn scheme_scanner_detects_exec_in_dotry_finally() {
839 use crate::lifecycle::application::route_definition::DoTryFinallyBuilder;
840 let route = RouteDefinition::new(
841 "direct:start",
842 vec![BuilderStep::DeclarativeDoTry {
843 try_steps: vec![BuilderStep::To("log:info".to_string())],
844 catch: vec![],
845 finally: Some(DoTryFinallyBuilder {
846 on_when: None,
847 steps: vec![BuilderStep::To("exec:echo".to_string())],
848 }),
849 }],
850 )
851 .with_route_id("r".to_string());
852 assert!(route_definitions_reference_scheme(&[route], "exec"));
853 }
854
855 #[test]
857 fn scheme_scanner_detects_exec_across_multiple_routes() {
858 let r1 = RouteDefinition::new("timer:tick", vec![]).with_route_id("r1".to_string());
859 let r2 = RouteDefinition::new(
860 "direct:start",
861 vec![BuilderStep::To("exec:echo".to_string())],
862 )
863 .with_route_id("r2".to_string());
864 assert!(route_definitions_reference_scheme(&[r1, r2], "exec"));
865 }
866
867 #[test]
869 fn scheme_scanner_false_for_non_exec_route() {
870 let route =
871 RouteDefinition::new("timer:tick", vec![BuilderStep::To("log:info".to_string())])
872 .with_route_id("r".to_string());
873 assert!(!route_definitions_reference_scheme(&[route], "exec"));
874 }
875
876 #[test]
879 fn scheme_scanner_false_for_dynamic_uri_only() {
880 use camel_api::RoutingSlipConfig;
881 use std::sync::Arc;
882 let route = RouteDefinition::new(
883 "timer:tick",
884 vec![BuilderStep::RoutingSlip {
885 config: RoutingSlipConfig::new(Arc::new(|_| Some("exec:echo".to_string()))),
886 }],
887 )
888 .with_route_id("r".to_string());
889 assert!(!route_definitions_reference_scheme(&[route], "exec"));
890 }
891}