Skip to main content

kcl_lib/
runtime_flags.rs

1use std::sync::RwLock;
2
3use serde::Deserialize;
4use serde::Serialize;
5
6/// Runtime representation for feature flags that can be set by the TS app.
7///
8/// TS currently provides a two-state feature answer: `true` means the feature is
9/// on, while `false` covers both explicit off/default behavior and a missing
10/// feature entry. Rust keeps a third state so code that was not initialized
11/// through the TS/wasm path can still fall back to Rust-side defaults, such as
12/// env-based configuration.
13#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
14#[ts(export)]
15pub enum RuntimeFlag {
16    /// No TS/wasm runtime flag has been installed; fall back to Rust defaults.
17    #[default]
18    Unset,
19    /// TS observed the feature as on; use the new feature behavior.
20    On,
21    /// TS observed the feature as false or missing; use default behavior.
22    Off,
23}
24
25/// Maps 1-1 to the KCL related flags added to the Admin portal and TS.
26///
27/// Fields missing from a deserialized payload become [`RuntimeFlag::Unset`],
28/// so a sender built before a flag existed falls back to Rust-side defaults
29/// instead of failing to parse.
30#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
31#[ts(export)]
32pub struct KclRuntimeFlags {
33    #[serde(default)]
34    pub enable_z0006_lint: RuntimeFlag,
35    #[serde(default)]
36    pub use_cek_executor: RuntimeFlag,
37    #[serde(default)]
38    pub use_new_lexer_parser: RuntimeFlag,
39}
40
41impl KclRuntimeFlags {
42    pub const DEFAULT: Self = Self {
43        enable_z0006_lint: RuntimeFlag::Unset,
44        use_cek_executor: RuntimeFlag::Unset,
45        use_new_lexer_parser: RuntimeFlag::Unset,
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
74pub(crate) fn z0006_refactor_metadata_enabled() -> bool {
75    kcl_runtime_flags().enable_z0006_lint == RuntimeFlag::On
76}
77
78pub(crate) trait RuntimeFlagResolve {
79    fn on() -> Self;
80    fn off() -> Self;
81    /// Not named `default()` so that it doesn't collide with
82    /// `Default::default()`.
83    fn resolve_default() -> Self;
84    fn parse_env_var(value: &str) -> Self;
85}
86
87pub(crate) fn resolve_from_sources<T: RuntimeFlagResolve>(
88    runtime_flag: RuntimeFlag,
89    test_override: Option<T>,
90    env_value: Option<&str>,
91) -> T {
92    match runtime_flag {
93        RuntimeFlag::On => return T::on(),
94        RuntimeFlag::Off => return T::off(),
95        RuntimeFlag::Unset => {}
96    }
97
98    if let Some(mode) = test_override {
99        return mode;
100    }
101
102    env_value.map(T::parse_env_var).unwrap_or_else(T::resolve_default)
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn deserializing_empty_flags_defaults_to_unset() {
111        let flags: KclRuntimeFlags = serde_json::from_str("{}").unwrap();
112        assert_eq!(flags, KclRuntimeFlags::DEFAULT);
113    }
114
115    #[test]
116    fn deserializing_partial_flags_defaults_missing_fields_to_unset() {
117        let flags: KclRuntimeFlags = serde_json::from_str(r#"{"use_new_lexer_parser":"On"}"#).unwrap();
118        assert_eq!(
119            flags,
120            KclRuntimeFlags {
121                enable_z0006_lint: RuntimeFlag::Unset,
122                use_cek_executor: RuntimeFlag::Unset,
123                use_new_lexer_parser: RuntimeFlag::On,
124            }
125        );
126    }
127}