use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unknown value: {0:?}")]
pub struct ParseError(String);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SystemInfo {
pub build: BuildInfo,
pub host: HostInfo,
pub backends: Vec<BackendVersion>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BuildInfo {
pub version: String,
pub commit: String,
pub commit_date: String,
pub build_date: String,
pub target: String,
pub profile: String,
pub features: String,
pub rustc: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackendVersion {
pub provider: String,
pub version: String,
pub source: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HostInfo {
pub arch: String,
pub os: Option<String>,
pub os_version: Option<String>,
pub kernel_version: Option<String>,
pub hostname: Option<String>,
pub machine_id: Option<String>,
pub cpu_model: Option<String>,
pub hardening: Option<Hardening>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hardening {
pub ram_encryption: FeatureCheck,
pub dma_protection: FeatureCheck,
pub freed_page_zeroing: FeatureCheck,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureCheck {
pub status: FeatureStatus,
pub detail: Option<String>,
}
impl FeatureCheck {
#[must_use]
pub fn new(status: FeatureStatus) -> Self {
Self {
status,
detail: None,
}
}
#[must_use]
pub fn with_detail(status: FeatureStatus, detail: impl Into<String>) -> Self {
Self {
status,
detail: Some(detail.into()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(into = "String", try_from = "String")]
#[non_exhaustive]
pub enum FeatureStatus {
Active,
Inactive,
Unavailable,
NotApplicable,
Unknown,
}
impl fmt::Display for FeatureStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
FeatureStatus::Active => "active",
FeatureStatus::Inactive => "inactive",
FeatureStatus::Unavailable => "unavailable",
FeatureStatus::NotApplicable => "not_applicable",
FeatureStatus::Unknown => "unknown",
};
f.write_str(s)
}
}
impl std::str::FromStr for FeatureStatus {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"active" => Ok(Self::Active),
"inactive" => Ok(Self::Inactive),
"unavailable" => Ok(Self::Unavailable),
"not_applicable" => Ok(Self::NotApplicable),
"unknown" => Ok(Self::Unknown),
_ => Err(ParseError(s.to_owned())),
}
}
}
impl From<FeatureStatus> for String {
fn from(s: FeatureStatus) -> String {
s.to_string()
}
}
impl TryFrom<String> for FeatureStatus {
type Error = ParseError;
fn try_from(s: String) -> Result<Self, Self::Error> {
s.parse()
}
}
#[derive(Debug, Clone, Default)]
pub struct Environment {
pub disks: Vec<Disk>,
}
#[derive(Debug, Clone)]
pub struct StartupSnapshot {
pub system: SystemInfo,
pub environment: Environment,
}
impl StartupSnapshot {
#[cfg(test)]
pub(crate) fn placeholder() -> Self {
let unknown = || "unknown".to_string();
Self {
system: SystemInfo {
build: BuildInfo {
version: unknown(),
commit: unknown(),
commit_date: unknown(),
build_date: unknown(),
target: unknown(),
profile: unknown(),
features: String::new(),
rustc: unknown(),
},
host: HostInfo {
arch: std::env::consts::ARCH.to_string(),
os: None,
os_version: None,
kernel_version: None,
hostname: None,
machine_id: None,
cpu_model: None,
hardening: None,
},
backends: Vec::new(),
},
environment: Environment::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct Disk {
pub name: String,
pub mount_point: String,
pub file_system: Option<String>,
pub total_bytes: u64,
pub available_bytes: u64,
pub removable: bool,
pub kind: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn feature_status_round_trips_through_display_and_serde() {
for status in [
FeatureStatus::Active,
FeatureStatus::Inactive,
FeatureStatus::Unavailable,
FeatureStatus::NotApplicable,
FeatureStatus::Unknown,
] {
let text = status.to_string();
assert_eq!(text.parse::<FeatureStatus>().expect("parses"), status);
let json = serde_json::to_string(&status).expect("serializes");
assert_eq!(json, format!("\"{text}\""));
let back: FeatureStatus = serde_json::from_str(&json).expect("deserializes");
assert_eq!(back, status);
}
}
#[test]
fn unknown_feature_status_string_is_rejected() {
assert!("bogus".parse::<FeatureStatus>().is_err());
}
}