use std::path::Path;
use super::config::{read_consent, ConsentFile};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConsentState {
Enabled,
Disabled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecidedBy {
DoNotTrackEnv,
OpenlatchDisabledEnv,
CiEnvironment,
ConfigFile,
DefaultUnconsented,
NoBakedKey,
}
#[derive(Debug, Clone, Copy)]
pub struct Resolved {
pub state: ConsentState,
pub decided_by: DecidedBy,
}
impl Resolved {
pub fn enabled(&self) -> bool {
self.state == ConsentState::Enabled
}
}
pub fn resolve(config_path: &Path) -> Resolved {
if is_truthy_env("DO_NOT_TRACK") {
return Resolved {
state: ConsentState::Disabled,
decided_by: DecidedBy::DoNotTrackEnv,
};
}
if is_truthy_env("OPENLATCH_TELEMETRY_DISABLED") {
return Resolved {
state: ConsentState::Disabled,
decided_by: DecidedBy::OpenlatchDisabledEnv,
};
}
if in_ci() {
return Resolved {
state: ConsentState::Disabled,
decided_by: DecidedBy::CiEnvironment,
};
}
match read_consent(config_path) {
Ok(Some(ConsentFile { enabled: true, .. })) => Resolved {
state: ConsentState::Enabled,
decided_by: DecidedBy::ConfigFile,
},
Ok(Some(ConsentFile { enabled: false, .. })) => Resolved {
state: ConsentState::Disabled,
decided_by: DecidedBy::ConfigFile,
},
Ok(None) => Resolved {
state: ConsentState::Disabled,
decided_by: DecidedBy::DefaultUnconsented,
},
Err(_) => Resolved {
state: ConsentState::Disabled,
decided_by: DecidedBy::ConfigFile,
},
}
}
fn is_truthy_env(name: &str) -> bool {
match std::env::var(name) {
Ok(v) => {
let v = v.trim().to_ascii_lowercase();
!matches!(v.as_str(), "" | "0" | "false" | "no" | "off")
}
Err(_) => false,
}
}
fn in_ci() -> bool {
const CI_VARS: &[&str] = &[
"CI",
"GITHUB_ACTIONS",
"GITLAB_CI",
"CIRCLECI",
"JENKINS_URL",
"BUILDKITE",
"TF_BUILD",
"TEAMCITY_VERSION",
"BITBUCKET_BUILD_NUMBER",
];
CI_VARS
.iter()
.any(|v| is_truthy_env(v) || std::env::var(v).is_ok_and(|x| !x.is_empty()))
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
use tempfile::TempDir;
static ENV_LOCK: Mutex<()> = Mutex::new(());
fn clear_env() {
for v in [
"DO_NOT_TRACK",
"OPENLATCH_TELEMETRY_DISABLED",
"CI",
"GITHUB_ACTIONS",
"GITLAB_CI",
"CIRCLECI",
"JENKINS_URL",
"BUILDKITE",
"TF_BUILD",
"TEAMCITY_VERSION",
"BITBUCKET_BUILD_NUMBER",
] {
std::env::remove_var(v);
}
}
#[test]
fn test_do_not_track_hard_overrides_enabled_config() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
clear_env();
std::env::set_var("DO_NOT_TRACK", "1");
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("telemetry.json");
super::super::config::write_consent(&path, true).unwrap();
let r = resolve(&path);
assert_eq!(r.state, ConsentState::Disabled);
assert_eq!(r.decided_by, DecidedBy::DoNotTrackEnv);
clear_env();
}
#[test]
fn test_openlatch_disabled_env_wins_over_config() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
clear_env();
std::env::set_var("OPENLATCH_TELEMETRY_DISABLED", "1");
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("telemetry.json");
super::super::config::write_consent(&path, true).unwrap();
let r = resolve(&path);
assert_eq!(r.state, ConsentState::Disabled);
assert_eq!(r.decided_by, DecidedBy::OpenlatchDisabledEnv);
clear_env();
}
#[test]
fn test_ci_environment_disables_even_when_enabled() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
clear_env();
std::env::set_var("GITHUB_ACTIONS", "true");
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("telemetry.json");
super::super::config::write_consent(&path, true).unwrap();
let r = resolve(&path);
assert_eq!(r.state, ConsentState::Disabled);
assert_eq!(r.decided_by, DecidedBy::CiEnvironment);
clear_env();
}
#[test]
fn test_missing_file_defaults_to_disabled_unconsented() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
clear_env();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("telemetry.json");
let r = resolve(&path);
assert_eq!(r.state, ConsentState::Disabled);
assert_eq!(r.decided_by, DecidedBy::DefaultUnconsented);
}
#[test]
fn test_enabled_config_with_no_env_overrides_is_enabled() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
clear_env();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("telemetry.json");
super::super::config::write_consent(&path, true).unwrap();
let r = resolve(&path);
assert_eq!(r.state, ConsentState::Enabled);
assert_eq!(r.decided_by, DecidedBy::ConfigFile);
}
#[test]
fn test_falsy_env_values_do_not_disable() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
clear_env();
std::env::set_var("DO_NOT_TRACK", "0");
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("telemetry.json");
super::super::config::write_consent(&path, true).unwrap();
let r = resolve(&path);
assert_eq!(r.state, ConsentState::Enabled);
assert_eq!(r.decided_by, DecidedBy::ConfigFile);
clear_env();
}
#[test]
fn test_corrupt_config_file_resolves_disabled() {
let _g = ENV_LOCK.lock().unwrap_or_else(|e| e.into_inner());
clear_env();
let tmp = TempDir::new().unwrap();
let path = tmp.path().join("telemetry.json");
std::fs::write(&path, b"{ not json").unwrap();
let r = resolve(&path);
assert_eq!(r.state, ConsentState::Disabled);
assert_eq!(r.decided_by, DecidedBy::ConfigFile);
}
}