1#[cfg(not(target_arch = "wasm32"))]
7use super::super::BootstrapConfigLoadingError;
8use super::super::log_config::StdOutMode;
9use super::default_values::{
10 default_enabled_feature_toggle, default_http_client_max_response_size_bytes,
11 default_http_client_max_retries, default_http_client_retry_delay_secs, default_jti,
12 default_jwks_refresh_min_interval, default_log_channel_capacity, default_log_max_retries,
13 default_status_list_refresh_interval_max, default_token_cache_capacity,
14 default_token_cache_max_ttl, default_true,
15};
16#[cfg(not(target_arch = "wasm32"))]
17use super::default_values::{
18 default_http_client_request_timeout, default_stdout_buffer_limit, default_stdout_timeout_millis,
19};
20use super::feature_types::{FeatureToggle, LoggerType};
21use super::json_util::{
22 deserialize_jwks_refresh_interval, deserialize_jwks_refresh_min_interval,
23 deserialize_or_parse_string_as_json, deserialize_status_list_refresh_interval_max,
24 parse_option_string,
25};
26use crate::JwtConfig;
27use crate::LockTransport;
28use crate::jwt_config::{TrustedIssuerLoaderTypeRaw, WorkersCount};
29use crate::log::LogLevel;
30use jsonwebtoken::Algorithm;
31use serde::{Deserialize, Serialize};
32#[cfg(not(target_arch = "wasm32"))]
33use std::collections::HashMap;
34use std::collections::HashSet;
35#[cfg(not(target_arch = "wasm32"))]
36use std::env;
37
38use std::num::NonZeroUsize;
39
40#[derive(Deserialize, Serialize, PartialEq, Debug)]
47pub struct BootstrapConfigRaw {
48 #[serde(rename = "CEDARLING_APPLICATION_NAME")]
50 pub application_name: String,
51
52 #[serde(
54 rename = "CEDARLING_POLICY_STORE_URI",
55 default,
56 deserialize_with = "parse_option_string"
57 )]
58 pub policy_store_uri: Option<String>,
59
60 #[serde(rename = "CEDARLING_LOG_TYPE", default)]
62 pub log_type: LoggerType,
63
64 #[serde(rename = "CEDARLING_LOG_LEVEL", default)]
66 pub log_level: LogLevel,
67
68 #[serde(rename = "CEDARLING_LOG_TTL", default)]
71 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
72 pub log_ttl: Option<u64>,
73
74 #[serde(rename = "CEDARLING_LOG_MAX_ITEMS", default)]
77 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
78 pub log_max_items: Option<usize>,
79
80 #[serde(rename = "CEDARLING_LOG_MAX_ITEM_SIZE", default)]
83 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
84 pub log_max_item_size: Option<usize>,
85
86 #[serde(rename = "CEDARLING_STDOUT_MODE", default)]
89 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
90 pub stdout_mode: StdOutMode,
91
92 #[cfg(not(target_arch = "wasm32"))]
95 #[serde(
96 rename = "CEDARLING_STDOUT_TIMEOUT_MILLIS",
97 default = "default_stdout_timeout_millis"
98 )]
99 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
100 pub stdout_timeout_millis: u64,
101
102 #[cfg(not(target_arch = "wasm32"))]
105 #[serde(
106 rename = "CEDARLING_STDOUT_BUFFER_LIMIT",
107 default = "default_stdout_buffer_limit"
108 )]
109 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
110 pub stdout_buffer_limit: usize,
111
112 #[serde(
115 rename = "CEDARLING_DECISION_LOG_DEFAULT_JWT_ID",
116 default = "default_jti"
117 )]
118 pub decision_log_default_jwt_id: String,
119
120 #[serde(
122 rename = "CEDARLING_LOCAL_JWKS",
123 default,
124 deserialize_with = "parse_option_string"
125 )]
126 pub local_jwks: Option<String>,
127
128 #[serde(rename = "CEDARLING_POLICY_STORE_LOCAL", default)]
130 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
131 pub local_policy_store: Option<String>,
132
133 #[serde(
135 rename = "CEDARLING_POLICY_STORE_LOCAL_FN",
136 default,
137 deserialize_with = "parse_option_string"
138 )]
139 pub policy_store_local_fn: Option<String>,
140 #[serde(
142 rename = "CEDARLING_POLICY_STORE_CJAR_URL",
143 default,
144 deserialize_with = "parse_option_string"
145 )]
146 pub policy_store_cjar_url: Option<String>,
147
148 #[serde(rename = "CEDARLING_MAX_DEFAULT_ENTITIES", default)]
152 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
153 pub max_default_entities: Option<usize>,
154
155 #[serde(rename = "CEDARLING_MAX_BASE64_SIZE", default)]
159 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
160 pub max_base64_size: Option<usize>,
161
162 #[serde(
167 rename = "CEDARLING_JWT_SIG_VALIDATION",
168 default = "default_enabled_feature_toggle"
169 )]
170 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
171 pub jwt_sig_validation: FeatureToggle,
172
173 #[serde(
181 rename = "CEDARLING_JWT_STATUS_VALIDATION",
182 default = "default_enabled_feature_toggle"
183 )]
184 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
185 pub jwt_status_validation: FeatureToggle,
186
187 #[serde(
191 rename = "CEDARLING_STRICT_SCHEMA_VALIDATION",
192 default = "default_enabled_feature_toggle"
193 )]
194 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
195 pub strict_schema_validation: FeatureToggle,
196
197 #[serde(
199 rename = "CEDARLING_JWT_SIGNATURE_ALGORITHMS_SUPPORTED",
200 default = "JwtConfig::supported_algorithms"
201 )]
202 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
203 pub jwt_signature_algorithms_supported: HashSet<Algorithm>,
204
205 #[serde(rename = "CEDARLING_LOCK", default)]
208 pub lock: FeatureToggle,
209
210 #[serde(rename = "CEDARLING_LOCK_SERVER_CONFIGURATION_URI", default)]
215 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
216 pub lock_server_configuration_uri: Option<String>,
217
218 #[serde(rename = "CEDARLING_LOCK_DYNAMIC_CONFIGURATION", default)]
220 pub dynamic_configuration: FeatureToggle,
221
222 #[serde(
225 rename = "CEDARLING_LOCK_SSA_JWT",
226 default,
227 deserialize_with = "parse_option_string"
228 )]
229 pub lock_ssa_jwt: Option<String>,
230
231 #[cfg(not(target_arch = "wasm32"))]
246 #[serde(
247 rename = "CEDARLING_LOCK_ACCESS_TOKEN_JWT",
248 default,
249 deserialize_with = "parse_option_string"
250 )]
251 pub lock_access_token_jwt: Option<String>,
252
253 #[serde(rename = "CEDARLING_LOCK_LOG_INTERVAL", default)]
255 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
256 pub audit_log_interval: u64,
257
258 #[serde(rename = "CEDARLING_LOCK_HEALTH_INTERVAL", default)]
260 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
261 pub audit_health_interval: u64,
262
263 #[serde(rename = "CEDARLING_LOCK_TELEMETRY_INTERVAL", default)]
265 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
266 pub audit_telemetry_interval: u64,
267
268 #[serde(rename = "CEDARLING_LOCK_LISTEN_SSE", default)]
270 pub listen_sse: FeatureToggle,
271
272 #[serde(rename = "CEDARLING_LOCK_ACCEPT_INVALID_CERTS", default)]
274 pub accept_invalid_certs: FeatureToggle,
275
276 #[serde(
278 rename = "CEDARLING_LOCK_TRANSPORT",
279 deserialize_with = "deserialize_or_parse_string_as_json",
280 default
281 )]
282 pub lock_transport: LockTransport,
283
284 #[serde(
289 rename = "CEDARLING_LOCK_LOG_CHANNEL_CAPACITY",
290 deserialize_with = "deserialize_or_parse_string_as_json",
291 default = "default_log_channel_capacity"
292 )]
293 pub lock_log_channel_capacity: NonZeroUsize,
294
295 #[serde(
299 rename = "CEDARLING_LOCK_LOG_MAX_RETRIES",
300 deserialize_with = "deserialize_or_parse_string_as_json",
301 default = "default_log_max_retries"
302 )]
303 pub lock_log_max_retries: u32,
304
305 #[serde(
318 rename = "CEDARLING_TOKEN_CACHE_MAX_TTL",
319 default = "default_token_cache_max_ttl"
320 )]
321 pub token_cache_max_ttl: usize,
322 #[serde(
326 rename = "CEDARLING_TOKEN_CACHE_CAPACITY",
327 default = "default_token_cache_capacity"
328 )]
329 pub token_cache_capacity: usize,
330 #[serde(
335 rename = "CEDARLING_TOKEN_CACHE_EARLIEST_EXPIRATION_EVICTION",
336 default = "default_true"
337 )]
338 pub token_cache_earliest_expiration_eviction: bool,
339
340 #[serde(rename = "CEDARLING_DATA_STORE_MAX_ENTRIES", default)]
346 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
347 pub data_store_max_entries: Option<usize>,
348
349 #[serde(rename = "CEDARLING_DATA_STORE_MAX_ENTRY_SIZE", default)]
352 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
353 pub data_store_max_entry_size: Option<usize>,
354
355 #[serde(rename = "CEDARLING_DATA_STORE_DEFAULT_TTL", default)]
358 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
359 pub data_store_default_ttl: Option<u64>,
360
361 #[serde(rename = "CEDARLING_DATA_STORE_MAX_TTL", default)]
366 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
367 pub data_store_max_ttl: Option<u64>,
368
369 #[serde(rename = "CEDARLING_DATA_STORE_ENABLE_METRICS", default)]
372 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
373 pub data_store_enable_metrics: Option<bool>,
374
375 #[serde(rename = "CEDARLING_DATA_STORE_MEMORY_ALERT_THRESHOLD", default)]
379 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
380 pub data_store_memory_alert_threshold: Option<f64>,
381 #[serde(
388 rename = "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE",
389 default,
390 deserialize_with = "deserialize_or_parse_string_as_json"
391 )]
392 pub trusted_issuer_loader_type: TrustedIssuerLoaderTypeRaw,
393 #[serde(
400 rename = "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS",
401 default,
402 deserialize_with = "deserialize_or_parse_string_as_json"
403 )]
404 pub trusted_issuer_loader_workers: WorkersCount,
405 #[cfg(not(target_arch = "wasm32"))]
410 #[serde(
411 rename = "CEDARLING_HTTP_REQUEST_TIMEOUT",
412 default = "default_http_client_request_timeout",
413 deserialize_with = "deserialize_or_parse_string_as_json"
414 )]
415 pub http_client_request_timeout: u64,
416 #[serde(
418 rename = "CEDARLING_HTTP_REQUEST_MAX_RETRIES",
419 default = "default_http_client_max_retries",
420 deserialize_with = "deserialize_or_parse_string_as_json"
421 )]
422 pub http_client_request_max_retries: u32,
423
424 #[serde(
426 rename = "CEDARLING_HTTP_REQUEST_RETRY_DELAY",
427 default = "default_http_client_retry_delay_secs",
428 deserialize_with = "deserialize_or_parse_string_as_json"
429 )]
430 pub http_client_request_retry_delay: u64,
431
432 #[serde(
437 rename = "CEDARLING_HTTP_MAX_RESPONSE_SIZE_BYTES",
438 default = "default_http_client_max_response_size_bytes",
439 deserialize_with = "deserialize_or_parse_string_as_json"
440 )]
441 pub http_client_max_response_size_bytes: u64,
442
443 #[serde(rename = "CEDARLING_JWKS_REFRESH_INTERVAL", default)]
448 #[serde(deserialize_with = "deserialize_jwks_refresh_interval")]
449 pub jwks_refresh_interval: Option<u64>,
450
451 #[serde(
455 rename = "CEDARLING_JWKS_REFRESH_MIN_INTERVAL",
456 default = "default_jwks_refresh_min_interval"
457 )]
458 #[serde(deserialize_with = "deserialize_jwks_refresh_min_interval")]
459 pub jwks_refresh_min_interval: u64,
460
461 #[serde(
479 rename = "CEDARLING_JWT_STATUS_LIST_REFRESH_INTERVAL_MAX",
480 default = "default_status_list_refresh_interval_max"
481 )]
482 #[serde(deserialize_with = "deserialize_status_list_refresh_interval_max")]
483 pub status_list_refresh_interval_max: u64,
484
485 #[serde(rename = "CEDARLING_POLICY_STORE_REFRESH_INTERVAL", default)]
494 #[serde(deserialize_with = "deserialize_or_parse_string_as_json")]
495 pub policy_store_refresh_interval_secs: u64,
496}
497
498impl Default for BootstrapConfigRaw {
499 fn default() -> Self {
500 serde_json::from_value(serde_json::json!({"CEDARLING_APPLICATION_NAME":""}))
501 .expect("BootstrapConfigRaw should be deserialized from empty json object")
502 }
503}
504
505impl BootstrapConfigRaw {
506 #[cfg(not(target_arch = "wasm32"))]
512 pub fn from_raw_config_and_env(
513 raw: Option<BootstrapConfigRaw>,
514 ) -> Result<Self, BootstrapConfigLoadingError> {
515 let mut json_config_params = serde_json::json!(raw.unwrap_or_default())
516 .as_object()
517 .map(std::borrow::ToOwned::to_owned)
518 .unwrap_or_default();
519
520 for (k, v) in get_cedarling_env_vars() {
521 json_config_params.insert(k, v);
523 }
524
525 Ok(BootstrapConfigRaw::deserialize(json_config_params)?)
526 }
527}
528
529#[cfg(not(target_arch = "wasm32"))]
531fn get_cedarling_env_vars() -> HashMap<String, serde_json::Value> {
532 env::vars()
533 .filter_map(|(k, v)| {
534 k.starts_with("CEDARLING_")
535 .then_some((k, serde_json::json!(v)))
536 })
537 .collect()
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543 use crate::jwt_config::{MIN_JWKS_REFRESH_SECS, MIN_STATUS_LIST_REFRESH_SECS};
544 use std::{
545 env,
546 sync::{LazyLock, Mutex},
547 };
548 use test_utils::assert_eq;
549
550 static ENV_MUTEX: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
551
552 fn with_env_vars<F>(vars: &[(&str, &str)], test: F)
553 where
554 F: FnOnce(),
555 {
556 let _lock = ENV_MUTEX.lock().unwrap();
558
559 let mut all_vars = Vec::new();
561 for (key, value) in env::vars() {
562 let key_clone = key.clone();
563 all_vars.push((key, value));
564 unsafe {
565 env::remove_var(&key_clone);
566 }
567 }
568
569 for (key, value) in vars {
571 unsafe {
572 env::set_var(key, value);
573 }
574 }
575
576 test();
577
578 for (key, _) in vars {
580 unsafe {
581 env::remove_var(key);
582 }
583 }
584
585 for (key, value) in all_vars {
587 unsafe {
588 env::set_var(&key, value);
589 }
590 }
591 }
592
593 #[test]
596 fn test_from_raw_config_and_env_defaults() {
597 with_env_vars(&[], || {
598 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
599 assert_eq!(
600 config.application_name, "",
601 "Application name should be empty by default"
602 );
603 assert_eq!(
604 config.policy_store_uri, None,
605 "Policy store URI should be None by default"
606 );
607 assert_eq!(
608 config.log_type,
609 LoggerType::Off,
610 "Logger should be off by default"
611 );
612 assert_eq!(
613 config.log_level,
614 LogLevel::WARN,
615 "Default log level should be WARN"
616 );
617 assert_eq!(config.log_ttl, None, "Log TTL should be None by default");
618 assert_eq!(
619 config.decision_log_default_jwt_id, "jti",
620 "Default JWT ID for decision logging should be 'jti'"
621 );
622 assert_eq!(
623 config.lock_transport,
624 LockTransport::Rest,
625 "Default transport should be REST"
626 );
627 });
628 }
629
630 #[test]
632 fn test_from_raw_config_and_env() {
633 with_env_vars(&[], || {
634 let raw = BootstrapConfigRaw {
635 application_name: "test-app".to_string(),
636 log_type: LoggerType::Memory,
637 log_level: LogLevel::DEBUG,
638 ..Default::default()
639 };
640
641 let config = BootstrapConfigRaw::from_raw_config_and_env(Some(raw)).unwrap();
642
643 assert_eq!(config.application_name, "test-app");
644 assert_eq!(config.log_type, LoggerType::Memory);
645 assert_eq!(config.log_level, LogLevel::DEBUG);
646 });
647 }
648
649 #[test]
651 fn test_from_raw_config_and_env_with_env_vars() {
652 with_env_vars(
653 &[
654 ("CEDARLING_APPLICATION_NAME", "env-app"),
655 ("CEDARLING_LOG_TYPE", "memory"),
656 ("CEDARLING_LOG_LEVEL", "DEBUG"),
657 ],
658 || {
659 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
660
661 assert_eq!(config.application_name, "env-app");
662 assert_eq!(config.log_type, LoggerType::Memory);
663 assert_eq!(config.log_level, LogLevel::DEBUG);
664 },
665 );
666 }
667
668 #[test]
670 fn test_from_raw_config_and_env_vars_override() {
671 with_env_vars(
672 &[
673 ("CEDARLING_APPLICATION_NAME", "env-app"),
674 ("CEDARLING_LOG_TYPE", "memory"),
675 ],
676 || {
677 let raw = BootstrapConfigRaw {
678 application_name: "test-app".to_string(),
679 log_type: LoggerType::StdOut,
680 log_level: LogLevel::INFO,
681 ..Default::default()
682 };
683
684 let config = BootstrapConfigRaw::from_raw_config_and_env(Some(raw)).unwrap();
685
686 assert_eq!(config.application_name, "env-app");
688 assert_eq!(config.log_type, LoggerType::Memory);
689
690 assert_eq!(config.log_level, LogLevel::INFO);
692 },
693 );
694 }
695
696 #[test]
698 fn test_from_raw_config_and_env_invalid_env_var() {
699 with_env_vars(&[("CEDARLING_LOG_TYPE", "invalid")], || {
700 let result = BootstrapConfigRaw::from_raw_config_and_env(None);
701 assert!(result.is_err());
702 });
703 }
704
705 #[test]
707 fn test_from_raw_config_and_env_empty_strings() {
708 with_env_vars(
709 &[
710 ("CEDARLING_APPLICATION_NAME", ""),
711 ("CEDARLING_POLICY_STORE_URI", ""),
712 ],
713 || {
714 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
715
716 assert_eq!(config.application_name, "");
717 assert_eq!(config.policy_store_uri, None);
718 },
719 );
720 }
721
722 #[test]
724 fn test_trusted_issuer_loader_env_vars() {
725 with_env_vars(
726 &[
727 ("CEDARLING_TRUSTED_ISSUER_LOADER_TYPE", "ASYNC"),
728 ("CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS", "5"),
729 ],
730 || {
731 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
732
733 assert_eq!(
734 config.trusted_issuer_loader_type,
735 TrustedIssuerLoaderTypeRaw::Async,
736 "Loader type should be Async from env var"
737 );
738 assert_eq!(
739 config.trusted_issuer_loader_workers, 5,
740 "Worker count should be 5 from env var"
741 );
742 },
743 );
744 }
745
746 #[test]
748 fn test_trusted_issuer_loader_json_deserialization() {
749 let valid_cases = vec![
751 (
752 r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": "SYNC", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": 3}"#,
753 TrustedIssuerLoaderTypeRaw::Sync,
754 3,
755 ),
756 (
757 r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": "ASYNC", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": 1}"#,
758 TrustedIssuerLoaderTypeRaw::Async,
759 1,
760 ),
761 ];
762
763 for (json, expected_type, expected_workers) in valid_cases {
764 let config: BootstrapConfigRaw = serde_json::from_str(json).unwrap();
765 assert_eq!(
766 config.trusted_issuer_loader_type, expected_type,
767 "Loader type mismatch for JSON: {}",
768 json
769 );
770 assert_eq!(
771 config.trusted_issuer_loader_workers, expected_workers,
772 "Worker count mismatch for JSON: {}",
773 json
774 );
775 }
776
777 let invalid_cases = vec![
779 r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": "INVALID"}"#,
780 r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": -1}"#,
781 r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS": "not_a_number"}"#,
782 r#"{"CEDARLING_APPLICATION_NAME": "", "CEDARLING_TRUSTED_ISSUER_LOADER_TYPE": 123}"#,
783 ];
784
785 for json in invalid_cases {
786 let result: Result<BootstrapConfigRaw, _> = serde_json::from_str(json);
787 result.expect_err(&format!("Should fail to parse invalid JSON: {json}"));
788 }
789 }
790
791 #[test]
793 fn test_trusted_issuer_loader_claps_min() {
794 with_env_vars(&[("CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS", "0")], || {
795 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
796
797 assert_eq!(
798 config.trusted_issuer_loader_workers,
799 WorkersCount::MIN,
800 "Default worker count should be {}",
801 WorkersCount::MIN.get()
802 );
803 });
804 }
805
806 #[test]
808 fn test_trusted_issuer_loader_claps_max() {
809 let max_val = (WorkersCount::MAX.get() + 100).to_string();
810
811 with_env_vars(
812 &[("CEDARLING_TRUSTED_ISSUER_LOADER_WORKERS", max_val.as_str())],
813 || {
814 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
815
816 assert_eq!(
817 config.trusted_issuer_loader_workers,
818 WorkersCount::MAX,
819 "Default worker count should be {}",
820 WorkersCount::MAX.get()
821 );
822 },
823 );
824 }
825
826 #[test]
827 fn test_jwks_refresh_interval_from_env_var() {
828 with_env_vars(
829 &[
830 ("CEDARLING_JWKS_REFRESH_INTERVAL", "30"),
831 ("CEDARLING_JWKS_REFRESH_MIN_INTERVAL", "60"),
832 ],
833 || {
834 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
835
836 assert_eq!(
837 config.jwks_refresh_interval,
838 Some(30),
839 "JWKS refresh interval should match environment value"
840 );
841 assert_eq!(
842 config.jwks_refresh_min_interval, 60,
843 "JWKS refresh min interval should match environment value"
844 );
845 },
846 );
847 }
848
849 #[test]
850 fn test_jwks_refresh_interval_clamps_min() {
851 with_env_vars(
852 &[
853 ("CEDARLING_JWKS_REFRESH_INTERVAL", "0"),
854 ("CEDARLING_JWKS_REFRESH_MIN_INTERVAL", "0"),
855 ],
856 || {
857 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
858
859 assert_eq!(
860 config.jwks_refresh_interval,
861 Some(MIN_JWKS_REFRESH_SECS),
862 "JWKS refresh interval should be clamped to minimum"
863 );
864 assert_eq!(
865 config.jwks_refresh_min_interval, MIN_JWKS_REFRESH_SECS,
866 "JWKS refresh min interval should be clamped to minimum"
867 );
868 },
869 );
870 }
871
872 #[test]
873 fn test_jwks_refresh_interval_clamps_below_min() {
874 with_env_vars(
875 &[
876 ("CEDARLING_JWKS_REFRESH_INTERVAL", "3"),
877 ("CEDARLING_JWKS_REFRESH_MIN_INTERVAL", "3"),
878 ],
879 || {
880 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
881
882 assert_eq!(
883 config.jwks_refresh_interval,
884 Some(MIN_JWKS_REFRESH_SECS),
885 "JWKS refresh interval should be clamped to minimum"
886 );
887 assert_eq!(
888 config.jwks_refresh_min_interval, MIN_JWKS_REFRESH_SECS,
889 "JWKS refresh min interval should be clamped to minimum"
890 );
891 },
892 );
893 }
894
895 #[test]
896 fn test_status_list_refresh_interval_max_default() {
897 with_env_vars(&[], || {
898 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
899 assert_eq!(
900 config.status_list_refresh_interval_max,
901 JwtConfig::DEFAULT_STATUS_LIST_REFRESH_INTERVAL_MAX_SECS,
902 "missing env var should resolve to the JwtConfig default"
903 );
904 });
905 }
906
907 #[test]
908 fn test_status_list_refresh_interval_max_from_env() {
909 with_env_vars(
910 &[("CEDARLING_JWT_STATUS_LIST_REFRESH_INTERVAL_MAX", "120")],
911 || {
912 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
913 assert_eq!(
914 config.status_list_refresh_interval_max, 120,
915 "status list refresh max should match the value supplied via env var"
916 );
917 },
918 );
919 }
920
921 #[test]
922 fn test_status_list_refresh_interval_max_zero_uses_default() {
923 with_env_vars(
924 &[("CEDARLING_JWT_STATUS_LIST_REFRESH_INTERVAL_MAX", "0")],
925 || {
926 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
927 assert_eq!(
928 config.status_list_refresh_interval_max,
929 JwtConfig::DEFAULT_STATUS_LIST_REFRESH_INTERVAL_MAX_SECS,
930 "0 should be treated as 'use the default'"
931 );
932 },
933 );
934 }
935
936 #[test]
937 fn test_status_list_refresh_interval_max_clamps_below_min() {
938 with_env_vars(
939 &[("CEDARLING_JWT_STATUS_LIST_REFRESH_INTERVAL_MAX", "2")],
940 || {
941 let config = BootstrapConfigRaw::from_raw_config_and_env(None).unwrap();
942 assert_eq!(
943 config.status_list_refresh_interval_max, MIN_STATUS_LIST_REFRESH_SECS,
944 "non-zero values below the minimum should be clamped"
945 );
946 },
947 );
948 }
949}