1use anyhow::Result;
5use derive_builder::Builder;
6use figment::{
7 Figment,
8 providers::{Env, Format, Serialized, Toml},
9};
10use serde::{Deserialize, Serialize};
11use std::fmt;
12use std::sync::OnceLock;
13use validator::Validate;
14
15const DEFAULT_SYSTEM_HOST: &str = "0.0.0.0";
17
18const DEFAULT_SYSTEM_PORT: i16 = -1;
20
21const DEFAULT_SYSTEM_HEALTH_PATH: &str = "/health";
23const DEFAULT_SYSTEM_LIVE_PATH: &str = "/live";
24
25pub const DEFAULT_CANARY_WAIT_TIME_SECS: u64 = 10;
28pub const DEFAULT_HEALTH_CHECK_REQUEST_TIMEOUT_SECS: u64 = 3;
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct WorkerConfig {
33 pub graceful_shutdown_timeout: u64,
35}
36
37impl WorkerConfig {
38 pub fn from_settings() -> Self {
41 Figment::new()
43 .merge(Serialized::defaults(Self::default()))
44 .merge(Env::prefixed("DYN_WORKER_"))
45 .extract()
46 .unwrap() }
48}
49
50impl Default for WorkerConfig {
51 fn default() -> Self {
52 WorkerConfig {
53 graceful_shutdown_timeout: if cfg!(debug_assertions) {
54 1 } else {
56 30 },
58 }
59 }
60}
61
62#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
63#[serde(rename_all = "lowercase")]
64pub enum HealthStatus {
65 Ready,
66 NotReady,
67}
68
69#[derive(Serialize, Deserialize, Validate, Debug, Builder, Clone)]
72#[builder(build_fn(private, name = "build_internal"), derive(Debug, Serialize))]
73pub struct RuntimeConfig {
74 #[validate(range(min = 1))]
79 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
80 pub num_worker_threads: Option<usize>,
81
82 #[validate(range(min = 1))]
87 #[builder(default = "512")]
88 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
89 pub max_blocking_threads: usize,
90
91 #[builder(default = "DEFAULT_SYSTEM_HOST.to_string()")]
94 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
95 pub system_host: String,
96
97 #[builder(default = "DEFAULT_SYSTEM_PORT")]
102 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
103 pub system_port: i16,
104
105 #[deprecated(
109 note = "Use system_port instead. Set DYN_SYSTEM_PORT to enable the system metrics server."
110 )]
111 #[builder(default = "false")]
112 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
113 pub system_enabled: bool,
114
115 #[builder(default = "HealthStatus::NotReady")]
118 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
119 pub starting_health_status: HealthStatus,
120
121 #[builder(default = "vec![]")]
127 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
128 pub use_endpoint_health_status: Vec<String>,
129
130 #[builder(default = "DEFAULT_SYSTEM_HEALTH_PATH.to_string()")]
133 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
134 pub system_health_path: String,
135 #[builder(default = "DEFAULT_SYSTEM_LIVE_PATH.to_string()")]
137 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
138 pub system_live_path: String,
139
140 #[builder(default = "None")]
144 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
145 pub compute_threads: Option<usize>,
146
147 #[builder(default = "Some(2 * 1024 * 1024)")]
151 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
152 pub compute_stack_size: Option<usize>,
153
154 #[builder(default = "\"compute\".to_string()")]
157 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
158 pub compute_thread_prefix: String,
159
160 #[builder(default = "false")]
163 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
164 pub health_check_enabled: bool,
165
166 #[builder(default = "DEFAULT_CANARY_WAIT_TIME_SECS")]
169 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
170 pub canary_wait_time_secs: u64,
171
172 #[builder(default = "DEFAULT_HEALTH_CHECK_REQUEST_TIMEOUT_SECS")]
175 #[builder_field_attr(serde(skip_serializing_if = "Option::is_none"))]
176 pub health_check_request_timeout_secs: u64,
177}
178
179impl fmt::Display for RuntimeConfig {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 match self.num_worker_threads {
183 Some(val) => write!(f, "num_worker_threads={val}, ")?,
184 None => write!(f, "num_worker_threads=default (num_cores), ")?,
185 }
186
187 write!(f, "max_blocking_threads={}, ", self.max_blocking_threads)?;
188 write!(f, "system_host={}, ", self.system_host)?;
189 write!(f, "system_port={}, ", self.system_port)?;
190 write!(
191 f,
192 "use_endpoint_health_status={:?}",
193 self.use_endpoint_health_status
194 )?;
195 write!(
196 f,
197 "starting_health_status={:?}",
198 self.starting_health_status
199 )?;
200 write!(f, ", system_health_path={}", self.system_health_path)?;
201 write!(f, ", system_live_path={}", self.system_live_path)?;
202 write!(f, ", health_check_enabled={}", self.health_check_enabled)?;
203 write!(f, ", canary_wait_time_secs={}", self.canary_wait_time_secs)?;
204 write!(
205 f,
206 ", health_check_request_timeout_secs={}",
207 self.health_check_request_timeout_secs
208 )?;
209
210 Ok(())
211 }
212}
213
214impl RuntimeConfig {
215 pub fn builder() -> RuntimeConfigBuilder {
216 RuntimeConfigBuilder::default()
217 }
218
219 pub(crate) fn figment() -> Figment {
220 Figment::new()
221 .merge(Serialized::defaults(RuntimeConfig::default()))
222 .merge(Toml::file("/opt/dynamo/defaults/runtime.toml"))
223 .merge(Toml::file("/opt/dynamo/etc/runtime.toml"))
224 .merge(Env::prefixed("DYN_RUNTIME_").filter_map(|k| {
225 let full_key = format!("DYN_RUNTIME_{}", k.as_str());
226 match std::env::var(&full_key) {
228 Ok(v) if !v.is_empty() => Some(k.into()),
229 _ => None,
230 }
231 }))
232 .merge(Env::prefixed("DYN_SYSTEM_").filter_map(|k| {
233 let full_key = format!("DYN_SYSTEM_{}", k.as_str());
234 match std::env::var(&full_key) {
236 Ok(v) if !v.is_empty() => {
237 let mapped_key = match k.as_str() {
239 "HOST" => "system_host",
240 "PORT" => "system_port",
241 "ENABLED" => "system_enabled",
242 "USE_ENDPOINT_HEALTH_STATUS" => "use_endpoint_health_status",
243 "STARTING_HEALTH_STATUS" => "starting_health_status",
244 "HEALTH_PATH" => "system_health_path",
245 "LIVE_PATH" => "system_live_path",
246 _ => k.as_str(),
247 };
248 Some(mapped_key.into())
249 }
250 _ => None,
251 }
252 }))
253 .merge(Env::prefixed("DYN_COMPUTE_").filter_map(|k| {
254 let full_key = format!("DYN_COMPUTE_{}", k.as_str());
255 match std::env::var(&full_key) {
257 Ok(v) if !v.is_empty() => {
258 let mapped_key = match k.as_str() {
260 "THREADS" => "compute_threads",
261 "STACK_SIZE" => "compute_stack_size",
262 "THREAD_PREFIX" => "compute_thread_prefix",
263 _ => k.as_str(),
264 };
265 Some(mapped_key.into())
266 }
267 _ => None,
268 }
269 }))
270 .merge(Env::prefixed("DYN_HEALTH_CHECK_").filter_map(|k| {
271 let full_key = format!("DYN_HEALTH_CHECK_{}", k.as_str());
272 match std::env::var(&full_key) {
274 Ok(v) if !v.is_empty() => {
275 let mapped_key = match k.as_str() {
277 "ENABLED" => "health_check_enabled",
278 "REQUEST_TIMEOUT" => "health_check_request_timeout_secs",
279 _ => k.as_str(),
280 };
281 Some(mapped_key.into())
282 }
283 _ => None,
284 }
285 }))
286 .merge(Env::prefixed("DYN_CANARY_").filter_map(|k| {
287 let full_key = format!("DYN_CANARY_{}", k.as_str());
288 match std::env::var(&full_key) {
290 Ok(v) if !v.is_empty() => {
291 let mapped_key = match k.as_str() {
293 "WAIT_TIME" => "canary_wait_time_secs",
294 _ => k.as_str(),
295 };
296 Some(mapped_key.into())
297 }
298 _ => None,
299 }
300 }))
301 }
302
303 pub fn from_settings() -> Result<RuntimeConfig> {
312 if std::env::var("DYN_SYSTEM_USE_ENDPOINT_HEALTH_STATUS").is_ok() {
314 tracing::warn!(
315 "DYN_SYSTEM_USE_ENDPOINT_HEALTH_STATUS is deprecated and no longer used. \
316 System health is now determined by endpoints that register with health check payloads. \
317 Please update your configuration to register health check payloads directly on endpoints."
318 );
319 }
320
321 if std::env::var("DYN_SYSTEM_ENABLED").is_ok() {
322 tracing::warn!(
323 "DYN_SYSTEM_ENABLED is deprecated. \
324 System metrics server is now controlled solely by DYN_SYSTEM_PORT. \
325 Set DYN_SYSTEM_PORT to a positive value to enable the server, or set to -1 to disable (default)."
326 );
327 }
328
329 let config: RuntimeConfig = Self::figment().extract()?;
330 config.validate()?;
331 Ok(config)
332 }
333
334 pub fn system_server_enabled(&self) -> bool {
339 self.system_port > 0
340 }
341
342 pub fn single_threaded() -> Self {
343 RuntimeConfig {
344 num_worker_threads: Some(1),
345 max_blocking_threads: 1,
346 system_host: DEFAULT_SYSTEM_HOST.to_string(),
347 system_port: DEFAULT_SYSTEM_PORT,
348 #[allow(deprecated)]
349 system_enabled: false,
350 starting_health_status: HealthStatus::NotReady,
351 use_endpoint_health_status: vec![],
352 system_health_path: DEFAULT_SYSTEM_HEALTH_PATH.to_string(),
353 system_live_path: DEFAULT_SYSTEM_LIVE_PATH.to_string(),
354 compute_threads: Some(1),
355 compute_stack_size: Some(2 * 1024 * 1024),
356 compute_thread_prefix: "compute".to_string(),
357 health_check_enabled: false,
358 canary_wait_time_secs: DEFAULT_CANARY_WAIT_TIME_SECS,
359 health_check_request_timeout_secs: DEFAULT_HEALTH_CHECK_REQUEST_TIMEOUT_SECS,
360 }
361 }
362
363 pub(crate) fn create_runtime(&self) -> std::io::Result<tokio::runtime::Runtime> {
365 tokio::runtime::Builder::new_multi_thread()
366 .worker_threads(
367 self.num_worker_threads
368 .unwrap_or_else(|| std::thread::available_parallelism().unwrap().get()),
369 )
370 .max_blocking_threads(self.max_blocking_threads)
371 .enable_all()
372 .build()
373 }
374}
375
376impl Default for RuntimeConfig {
377 fn default() -> Self {
378 let num_cores = std::thread::available_parallelism().unwrap().get();
379 Self {
380 num_worker_threads: Some(num_cores),
381 max_blocking_threads: num_cores,
382 system_host: DEFAULT_SYSTEM_HOST.to_string(),
383 system_port: DEFAULT_SYSTEM_PORT,
384 #[allow(deprecated)]
385 system_enabled: false,
386 starting_health_status: HealthStatus::NotReady,
387 use_endpoint_health_status: vec![],
388 system_health_path: DEFAULT_SYSTEM_HEALTH_PATH.to_string(),
389 system_live_path: DEFAULT_SYSTEM_LIVE_PATH.to_string(),
390 compute_threads: None,
391 compute_stack_size: Some(2 * 1024 * 1024),
392 compute_thread_prefix: "compute".to_string(),
393 health_check_enabled: false,
394 canary_wait_time_secs: DEFAULT_CANARY_WAIT_TIME_SECS,
395 health_check_request_timeout_secs: DEFAULT_HEALTH_CHECK_REQUEST_TIMEOUT_SECS,
396 }
397 }
398}
399
400impl RuntimeConfigBuilder {
401 pub fn build(&self) -> Result<RuntimeConfig> {
403 let config = self.build_internal()?;
404 config.validate()?;
405 Ok(config)
406 }
407}
408
409pub fn is_truthy(val: &str) -> bool {
414 matches!(val.to_lowercase().as_str(), "1" | "true" | "on" | "yes")
415}
416
417pub fn parse_bool(val: &str) -> anyhow::Result<bool> {
418 if is_truthy(val) {
419 Ok(true)
420 } else if is_falsey(val) {
421 Ok(false)
422 } else {
423 anyhow::bail!(
424 "Invalid boolean value: '{}'. Expected one of: true/false, 1/0, on/off, yes/no",
425 val
426 )
427 }
428}
429
430pub fn is_falsey(val: &str) -> bool {
435 matches!(val.to_lowercase().as_str(), "0" | "false" | "off" | "no")
436}
437
438pub fn env_is_truthy(env: &str) -> bool {
440 match std::env::var(env) {
441 Ok(val) => is_truthy(val.as_str()),
442 Err(_) => false,
443 }
444}
445
446pub fn env_is_falsey(env: &str) -> bool {
448 match std::env::var(env) {
449 Ok(val) => is_falsey(val.as_str()),
450 Err(_) => false,
451 }
452}
453
454pub fn jsonl_logging_enabled() -> bool {
457 env_is_truthy("DYN_LOGGING_JSONL")
458}
459
460pub fn disable_ansi_logging() -> bool {
463 env_is_truthy("DYN_SDK_DISABLE_ANSI_LOGGING")
464}
465
466pub fn use_local_timezone() -> bool {
469 env_is_truthy("DYN_LOG_USE_LOCAL_TZ")
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
479#[serde(rename_all = "lowercase")]
480pub enum RequestPlaneMode {
481 Nats,
483 Http,
485 Tcp,
487}
488
489impl Default for RequestPlaneMode {
490 fn default() -> Self {
491 Self::Nats
492 }
493}
494
495impl fmt::Display for RequestPlaneMode {
496 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
497 match self {
498 Self::Nats => write!(f, "nats"),
499 Self::Http => write!(f, "http"),
500 Self::Tcp => write!(f, "tcp"),
501 }
502 }
503}
504
505impl std::str::FromStr for RequestPlaneMode {
506 type Err = anyhow::Error;
507
508 fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
509 match s.to_lowercase().as_str() {
510 "nats" => Ok(Self::Nats),
511 "http" => Ok(Self::Http),
512 "tcp" => Ok(Self::Tcp),
513 _ => Err(anyhow::anyhow!(
514 "Invalid request plane mode: '{}'. Valid options are: 'nats', 'http', 'tcp'",
515 s
516 )),
517 }
518 }
519}
520
521static REQUEST_PLANE_MODE: OnceLock<RequestPlaneMode> = OnceLock::new();
523
524impl RequestPlaneMode {
525 pub fn get() -> Self {
528 *REQUEST_PLANE_MODE.get_or_init(Self::from_env)
529 }
530
531 pub fn from_env() -> Self {
534 std::env::var("DYN_REQUEST_PLANE")
535 .ok()
536 .and_then(|s| s.parse().ok())
537 .unwrap_or_default()
538 }
539}
540
541#[cfg(test)]
542mod tests {
543 use super::*;
544
545 #[test]
546 fn test_runtime_config_with_env_vars() -> Result<()> {
547 temp_env::with_vars(
548 vec![
549 ("DYN_RUNTIME_NUM_WORKER_THREADS", Some("24")),
550 ("DYN_RUNTIME_MAX_BLOCKING_THREADS", Some("32")),
551 ],
552 || {
553 let config = RuntimeConfig::from_settings()?;
554 assert_eq!(config.num_worker_threads, Some(24));
555 assert_eq!(config.max_blocking_threads, 32);
556 Ok(())
557 },
558 )
559 }
560
561 #[test]
562 fn test_runtime_config_defaults() -> Result<()> {
563 temp_env::with_vars(
564 vec![
565 ("DYN_RUNTIME_NUM_WORKER_THREADS", None::<&str>),
566 ("DYN_RUNTIME_MAX_BLOCKING_THREADS", Some("")),
567 ],
568 || {
569 let config = RuntimeConfig::from_settings()?;
570
571 let default_config = RuntimeConfig::default();
572 assert_eq!(config.num_worker_threads, default_config.num_worker_threads);
573 assert_eq!(
574 config.max_blocking_threads,
575 default_config.max_blocking_threads
576 );
577 Ok(())
578 },
579 )
580 }
581
582 #[test]
583 fn test_runtime_config_rejects_invalid_thread_count() -> Result<()> {
584 temp_env::with_vars(
585 vec![
586 ("DYN_RUNTIME_NUM_WORKER_THREADS", Some("0")),
587 ("DYN_RUNTIME_MAX_BLOCKING_THREADS", Some("0")),
588 ],
589 || {
590 let result = RuntimeConfig::from_settings();
591 assert!(result.is_err());
592 if let Err(e) = result {
593 assert!(
594 e.to_string()
595 .contains("num_worker_threads: Validation error")
596 );
597 assert!(
598 e.to_string()
599 .contains("max_blocking_threads: Validation error")
600 );
601 }
602 Ok(())
603 },
604 )
605 }
606
607 #[test]
608 fn test_runtime_config_system_server_env_vars() -> Result<()> {
609 temp_env::with_vars(
610 vec![
611 ("DYN_SYSTEM_HOST", Some("127.0.0.1")),
612 ("DYN_SYSTEM_PORT", Some("9090")),
613 ],
614 || {
615 let config = RuntimeConfig::from_settings()?;
616 assert_eq!(config.system_host, "127.0.0.1");
617 assert_eq!(config.system_port, 9090);
618 Ok(())
619 },
620 )
621 }
622
623 #[test]
624 fn test_system_server_disabled_by_default() {
625 temp_env::with_vars(vec![("DYN_SYSTEM_PORT", None::<&str>)], || {
626 let config = RuntimeConfig::from_settings().unwrap();
627 assert!(!config.system_server_enabled());
628 assert_eq!(config.system_port, -1);
629 });
630 }
631
632 #[test]
633 fn test_system_server_disabled_with_negative_port() {
634 temp_env::with_vars(vec![("DYN_SYSTEM_PORT", Some("-1"))], || {
635 let config = RuntimeConfig::from_settings().unwrap();
636 assert!(!config.system_server_enabled());
637 assert_eq!(config.system_port, -1);
638 });
639 }
640
641 #[test]
642 fn test_system_server_enabled_with_port() {
643 temp_env::with_vars(vec![("DYN_SYSTEM_PORT", Some("9527"))], || {
644 let config = RuntimeConfig::from_settings().unwrap();
645 assert!(config.system_server_enabled());
646 assert_eq!(config.system_port, 9527);
647 });
648 }
649
650 #[test]
651 fn test_system_server_starting_health_status_ready() {
652 temp_env::with_vars(
653 vec![("DYN_SYSTEM_STARTING_HEALTH_STATUS", Some("ready"))],
654 || {
655 let config = RuntimeConfig::from_settings().unwrap();
656 assert!(config.starting_health_status == HealthStatus::Ready);
657 },
658 );
659 }
660
661 #[test]
662 fn test_system_use_endpoint_health_status() {
663 temp_env::with_vars(
664 vec![("DYN_SYSTEM_USE_ENDPOINT_HEALTH_STATUS", Some("[\"ready\"]"))],
665 || {
666 let config = RuntimeConfig::from_settings().unwrap();
667 assert!(config.use_endpoint_health_status == vec!["ready"]);
668 },
669 );
670 }
671
672 #[test]
673 fn test_system_health_endpoint_path_default() {
674 temp_env::with_vars(vec![("DYN_SYSTEM_HEALTH_PATH", None::<&str>)], || {
675 let config = RuntimeConfig::from_settings().unwrap();
676 assert_eq!(
677 config.system_health_path,
678 DEFAULT_SYSTEM_HEALTH_PATH.to_string()
679 );
680 });
681
682 temp_env::with_vars(vec![("DYN_SYSTEM_LIVE_PATH", None::<&str>)], || {
683 let config = RuntimeConfig::from_settings().unwrap();
684 assert_eq!(
685 config.system_live_path,
686 DEFAULT_SYSTEM_LIVE_PATH.to_string()
687 );
688 });
689 }
690
691 #[test]
692 fn test_system_health_endpoint_path_custom() {
693 temp_env::with_vars(
694 vec![("DYN_SYSTEM_HEALTH_PATH", Some("/custom/health"))],
695 || {
696 let config = RuntimeConfig::from_settings().unwrap();
697 assert_eq!(config.system_health_path, "/custom/health");
698 },
699 );
700
701 temp_env::with_vars(vec![("DYN_SYSTEM_LIVE_PATH", Some("/custom/live"))], || {
702 let config = RuntimeConfig::from_settings().unwrap();
703 assert_eq!(config.system_live_path, "/custom/live");
704 });
705 }
706
707 #[test]
708 fn test_is_truthy_and_falsey() {
709 assert!(is_truthy("1"));
711 assert!(is_truthy("true"));
712 assert!(is_truthy("TRUE"));
713 assert!(is_truthy("on"));
714 assert!(is_truthy("yes"));
715
716 assert!(is_falsey("0"));
718 assert!(is_falsey("false"));
719 assert!(is_falsey("FALSE"));
720 assert!(is_falsey("off"));
721 assert!(is_falsey("no"));
722
723 assert!(!is_truthy("0"));
725 assert!(!is_falsey("1"));
726
727 temp_env::with_vars(vec![("TEST_TRUTHY", Some("true"))], || {
729 assert!(env_is_truthy("TEST_TRUTHY"));
730 assert!(!env_is_falsey("TEST_TRUTHY"));
731 });
732
733 temp_env::with_vars(vec![("TEST_FALSEY", Some("false"))], || {
734 assert!(!env_is_truthy("TEST_FALSEY"));
735 assert!(env_is_falsey("TEST_FALSEY"));
736 });
737
738 temp_env::with_vars(vec![("TEST_MISSING", None::<&str>)], || {
740 assert!(!env_is_truthy("TEST_MISSING"));
741 assert!(!env_is_falsey("TEST_MISSING"));
742 });
743 }
744
745 #[test]
746 fn test_request_plane_mode_from_str() {
747 assert_eq!(
748 "nats".parse::<RequestPlaneMode>().unwrap(),
749 RequestPlaneMode::Nats
750 );
751 assert_eq!(
752 "http".parse::<RequestPlaneMode>().unwrap(),
753 RequestPlaneMode::Http
754 );
755 assert_eq!(
756 "tcp".parse::<RequestPlaneMode>().unwrap(),
757 RequestPlaneMode::Tcp
758 );
759 assert_eq!(
760 "NATS".parse::<RequestPlaneMode>().unwrap(),
761 RequestPlaneMode::Nats
762 );
763 assert_eq!(
764 "HTTP".parse::<RequestPlaneMode>().unwrap(),
765 RequestPlaneMode::Http
766 );
767 assert_eq!(
768 "TCP".parse::<RequestPlaneMode>().unwrap(),
769 RequestPlaneMode::Tcp
770 );
771 assert!("invalid".parse::<RequestPlaneMode>().is_err());
772 }
773
774 #[test]
775 fn test_request_plane_mode_display() {
776 assert_eq!(RequestPlaneMode::Nats.to_string(), "nats");
777 assert_eq!(RequestPlaneMode::Http.to_string(), "http");
778 assert_eq!(RequestPlaneMode::Tcp.to_string(), "tcp");
779 }
780
781 #[test]
782 fn test_request_plane_mode_default() {
783 assert_eq!(RequestPlaneMode::default(), RequestPlaneMode::Nats);
784 }
785
786 #[test]
787 fn test_request_plane_mode_get_cached() {
788 let mode1 = RequestPlaneMode::get();
790 let mode2 = RequestPlaneMode::get();
791 assert_eq!(mode1, mode2, "Cached mode should be consistent");
792
793 assert!(
795 matches!(
796 mode1,
797 RequestPlaneMode::Nats | RequestPlaneMode::Http | RequestPlaneMode::Tcp
798 ),
799 "Mode should be a valid variant"
800 );
801 }
802}