use std::sync::RwLock;
use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, Copy, Default, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[ts(export)]
pub enum RuntimeFlag {
#[default]
Unset,
On,
Off,
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[serde(default)]
#[ts(export)]
pub struct KclRuntimeFlags {
pub use_new_parser: RuntimeFlag,
}
impl KclRuntimeFlags {
pub const DEFAULT: Self = Self {
use_new_parser: RuntimeFlag::Off,
};
}
impl Default for KclRuntimeFlags {
fn default() -> Self {
Self::DEFAULT
}
}
static KCL_RUNTIME_FLAGS: RwLock<KclRuntimeFlags> = RwLock::new(KclRuntimeFlags::DEFAULT);
pub fn set_kcl_runtime_flags(flags: KclRuntimeFlags) {
match KCL_RUNTIME_FLAGS.write() {
Ok(mut guard) => *guard = flags,
Err(poisoned) => {
let mut guard = poisoned.into_inner();
*guard = flags;
}
}
}
pub fn kcl_runtime_flags() -> KclRuntimeFlags {
match KCL_RUNTIME_FLAGS.read() {
Ok(guard) => *guard,
Err(poisoned) => *poisoned.into_inner(),
}
}
#[allow(dead_code, reason = "Retained for future runtime flag consumers.")]
pub(crate) trait RuntimeFlagResolve {
fn on() -> Self;
fn off() -> Self;
fn resolve_default() -> Self;
fn parse_env_var(value: &str) -> Self;
}
#[allow(dead_code, reason = "Retained for future runtime flag consumers.")]
pub(crate) fn resolve_from_sources<T: RuntimeFlagResolve>(
runtime_flag: RuntimeFlag,
test_override: Option<T>,
env_value: Option<&str>,
) -> T {
match runtime_flag {
RuntimeFlag::On => return T::on(),
RuntimeFlag::Off => return T::off(),
RuntimeFlag::Unset => {}
}
if let Some(mode) = test_override {
return mode;
}
env_value.map(T::parse_env_var).unwrap_or_else(T::resolve_default)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserializing_empty_flags_preserves_rust_defaults() {
let flags: KclRuntimeFlags = serde_json::from_str("{}").unwrap();
assert_eq!(flags.use_new_parser, RuntimeFlag::Off);
}
#[test]
fn deserializing_explicit_parser_flag_preserves_its_value() {
let flags: KclRuntimeFlags = serde_json::from_str(r#"{"use_new_parser":"On"}"#).unwrap();
assert_eq!(flags.use_new_parser, RuntimeFlag::On);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TestMode {
Current,
New,
}
impl RuntimeFlagResolve for TestMode {
fn on() -> Self {
Self::New
}
fn off() -> Self {
Self::Current
}
fn resolve_default() -> Self {
Self::Current
}
fn parse_env_var(value: &str) -> Self {
match value {
"new" => Self::New,
_ => Self::Current,
}
}
}
#[test]
fn runtime_flag_takes_priority_over_test_override_and_env() {
assert_eq!(
resolve_from_sources(RuntimeFlag::Off, Some(TestMode::New), Some("new")),
TestMode::Current
);
assert_eq!(
resolve_from_sources(RuntimeFlag::On, Some(TestMode::Current), Some("current")),
TestMode::New
);
}
#[test]
fn test_override_takes_priority_over_env() {
assert_eq!(
resolve_from_sources(RuntimeFlag::Unset, Some(TestMode::Current), Some("new")),
TestMode::Current
);
assert_eq!(
resolve_from_sources(RuntimeFlag::Unset, Some(TestMode::New), Some("current")),
TestMode::New
);
}
#[test]
fn unset_runtime_flag_allows_env_to_select_mode() {
assert_eq!(
resolve_from_sources::<TestMode>(RuntimeFlag::Unset, None, Some("new")),
TestMode::New
);
assert_eq!(
resolve_from_sources::<TestMode>(RuntimeFlag::Unset, None, Some("current")),
TestMode::Current
);
}
#[test]
fn unset_runtime_flag_and_missing_env_select_default_mode() {
assert_eq!(
resolve_from_sources::<TestMode>(RuntimeFlag::Unset, None, None),
TestMode::Current
);
}
}