use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::context::CollectionPolicy;
use crate::error::TelemetryError;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ConsentState {
Enabled,
Disabled,
#[default]
Unset,
}
impl From<ConsentState> for CollectionPolicy {
fn from(state: ConsentState) -> Self {
match state {
ConsentState::Enabled => Self::Enabled,
ConsentState::Disabled | ConsentState::Unset => Self::Disabled,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Consent {
pub version: u32,
pub state: ConsentState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub decided_at: Option<String>,
}
impl Consent {
pub const SCHEMA_VERSION: u32 = 1;
#[must_use]
pub const fn unset() -> Self {
Self { version: Self::SCHEMA_VERSION, state: ConsentState::Unset, decided_at: None }
}
#[must_use]
pub fn enabled_now() -> Self {
Self::with_state_now(ConsentState::Enabled)
}
#[must_use]
pub fn disabled_now() -> Self {
Self::with_state_now(ConsentState::Disabled)
}
fn with_state_now(state: ConsentState) -> Self {
let decided_at = time::OffsetDateTime::now_utc()
.format(&time::format_description::well_known::Rfc3339)
.ok();
Self { version: Self::SCHEMA_VERSION, state, decided_at }
}
}
pub fn read(path: &Path) -> Result<Option<Consent>, TelemetryError> {
let body = match std::fs::read_to_string(path) {
Ok(body) => body,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(err) => return Err(TelemetryError::Io(err)),
};
let consent: Consent =
toml::from_str(&body).map_err(|e| TelemetryError::Serde(format!("consent: {e}")))?;
if consent.version != Consent::SCHEMA_VERSION {
return Err(TelemetryError::Serde(format!(
"consent: unsupported schema version {} (expected {})",
consent.version,
Consent::SCHEMA_VERSION
)));
}
Ok(Some(consent))
}
pub fn write(path: &Path, consent: &Consent) -> Result<(), TelemetryError> {
let body = toml::to_string_pretty(consent)
.map_err(|e| TelemetryError::Serde(format!("consent: {e}")))?;
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent).map_err(TelemetryError::Io)?;
}
}
std::fs::write(path, body).map_err(TelemetryError::Io)?;
Ok(())
}
pub fn reset(path: &Path) -> Result<(), TelemetryError> {
match std::fs::remove_file(path) {
Ok(()) => Ok(()),
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(err) => Err(TelemetryError::Io(err)),
}
}