1use std::sync::RwLock;
2
3use serde::Deserialize;
4use serde::Serialize;
5
6#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
14#[ts(export)]
15pub enum RuntimeFlag {
16 #[default]
18 Unset,
19 On,
21 Off,
23}
24
25#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
37#[serde(default)]
38#[ts(export)]
39pub struct KclRuntimeFlags {
40 pub use_new_parser: RuntimeFlag,
41}
42
43impl KclRuntimeFlags {
44 pub const DEFAULT: Self = Self {
45 use_new_parser: RuntimeFlag::Off,
46 };
47}
48
49impl Default for KclRuntimeFlags {
50 fn default() -> Self {
51 Self::DEFAULT
52 }
53}
54
55static KCL_RUNTIME_FLAGS: RwLock<KclRuntimeFlags> = RwLock::new(KclRuntimeFlags::DEFAULT);
56
57pub fn set_kcl_runtime_flags(flags: KclRuntimeFlags) {
58 match KCL_RUNTIME_FLAGS.write() {
59 Ok(mut guard) => *guard = flags,
60 Err(poisoned) => {
61 let mut guard = poisoned.into_inner();
62 *guard = flags;
63 }
64 }
65}
66
67pub fn kcl_runtime_flags() -> KclRuntimeFlags {
68 match KCL_RUNTIME_FLAGS.read() {
69 Ok(guard) => *guard,
70 Err(poisoned) => *poisoned.into_inner(),
71 }
72}
73
74#[allow(dead_code, reason = "Retained for future runtime flag consumers.")]
75pub(crate) trait RuntimeFlagResolve {
76 fn on() -> Self;
77 fn off() -> Self;
78 fn resolve_default() -> Self;
81 fn parse_env_var(value: &str) -> Self;
82}
83
84#[allow(dead_code, reason = "Retained for future runtime flag consumers.")]
85pub(crate) fn resolve_from_sources<T: RuntimeFlagResolve>(
86 runtime_flag: RuntimeFlag,
87 test_override: Option<T>,
88 env_value: Option<&str>,
89) -> T {
90 match runtime_flag {
91 RuntimeFlag::On => return T::on(),
92 RuntimeFlag::Off => return T::off(),
93 RuntimeFlag::Unset => {}
94 }
95
96 if let Some(mode) = test_override {
97 return mode;
98 }
99
100 env_value.map(T::parse_env_var).unwrap_or_else(T::resolve_default)
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn deserializing_empty_flags_preserves_rust_defaults() {
109 let flags: KclRuntimeFlags = serde_json::from_str("{}").unwrap();
110 assert_eq!(flags.use_new_parser, RuntimeFlag::Off);
111 }
112
113 #[test]
114 fn deserializing_explicit_parser_flag_preserves_its_value() {
115 let flags: KclRuntimeFlags = serde_json::from_str(r#"{"use_new_parser":"On"}"#).unwrap();
116 assert_eq!(flags.use_new_parser, RuntimeFlag::On);
117 }
118
119 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
120 enum TestMode {
121 Current,
122 New,
123 }
124
125 impl RuntimeFlagResolve for TestMode {
126 fn on() -> Self {
127 Self::New
128 }
129
130 fn off() -> Self {
131 Self::Current
132 }
133
134 fn resolve_default() -> Self {
135 Self::Current
136 }
137
138 fn parse_env_var(value: &str) -> Self {
139 match value {
140 "new" => Self::New,
141 _ => Self::Current,
142 }
143 }
144 }
145
146 #[test]
147 fn runtime_flag_takes_priority_over_test_override_and_env() {
148 assert_eq!(
149 resolve_from_sources(RuntimeFlag::Off, Some(TestMode::New), Some("new")),
150 TestMode::Current
151 );
152 assert_eq!(
153 resolve_from_sources(RuntimeFlag::On, Some(TestMode::Current), Some("current")),
154 TestMode::New
155 );
156 }
157
158 #[test]
159 fn test_override_takes_priority_over_env() {
160 assert_eq!(
161 resolve_from_sources(RuntimeFlag::Unset, Some(TestMode::Current), Some("new")),
162 TestMode::Current
163 );
164 assert_eq!(
165 resolve_from_sources(RuntimeFlag::Unset, Some(TestMode::New), Some("current")),
166 TestMode::New
167 );
168 }
169
170 #[test]
171 fn unset_runtime_flag_allows_env_to_select_mode() {
172 assert_eq!(
173 resolve_from_sources::<TestMode>(RuntimeFlag::Unset, None, Some("new")),
174 TestMode::New
175 );
176 assert_eq!(
177 resolve_from_sources::<TestMode>(RuntimeFlag::Unset, None, Some("current")),
178 TestMode::Current
179 );
180 }
181
182 #[test]
183 fn unset_runtime_flag_and_missing_env_select_default_mode() {
184 assert_eq!(
185 resolve_from_sources::<TestMode>(RuntimeFlag::Unset, None, None),
186 TestMode::Current
187 );
188 }
189}