use std::collections::BTreeMap;
use std::path::PathBuf;
use serde::Serialize;
use crate::cli::host_context_status::{self, HostContextStatus};
use crate::cli::{CliError, print_json, renewal};
use lifeloop::{ConformanceLevel, RenewalAutomationStatus, manifest_registry};
#[derive(Debug)]
struct StatusArgs {
path: PathBuf,
state_dir: Option<PathBuf>,
}
impl StatusArgs {
fn parse<I: Iterator<Item = String>>(mut args: I) -> Result<Self, CliError> {
let mut parsed = Self {
path: PathBuf::from("."),
state_dir: None,
};
while let Some(arg) = args.next() {
match arg.as_str() {
"--path" => parsed.path = PathBuf::from(require_value(&arg, args.next())?),
"--state-dir" => {
parsed.state_dir = Some(PathBuf::from(require_value(&arg, args.next())?));
}
other => {
return Err(CliError::Usage(format!("status: unknown flag `{other}`")));
}
}
}
Ok(parsed)
}
}
fn require_value(flag: &str, value: Option<String>) -> Result<String, CliError> {
value.ok_or_else(|| CliError::Usage(format!("flag `{flag}` requires a value")))
}
pub fn run<I: Iterator<Item = String>>(args: I) -> Result<(), CliError> {
let args = StatusArgs::parse(args)?;
let state_dir = args
.state_dir
.unwrap_or_else(|| renewal::default_state_dir(&args.path));
let renewal = match renewal::read_status_if_present(&state_dir)? {
Some(snapshot) => Some(renewal::current_status(
&args.path,
Some(&state_dir),
&snapshot.adapter_id,
&snapshot.client_id,
renewal::epoch_s(),
)?),
None => None,
};
let report = StatusReport {
adapters: AdaptersSummary::from_registry(),
state_dir: state_dir.display().to_string(),
host_context: host_context_status::read_status(&state_dir)?,
renewal,
};
print_json(&report)
}
#[derive(Serialize)]
struct StatusReport {
adapters: AdaptersSummary,
state_dir: String,
#[serde(skip_serializing_if = "Option::is_none")]
host_context: Option<HostContextStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
renewal: Option<RenewalAutomationStatus>,
}
#[derive(Serialize)]
struct AdaptersSummary {
count: usize,
by_conformance: BTreeMap<String, usize>,
adapters: Vec<AdapterLine>,
}
#[derive(Serialize)]
struct AdapterLine {
adapter_id: String,
display_name: String,
adapter_version: String,
conformance: ConformanceLevel,
}
impl AdaptersSummary {
fn from_registry() -> Self {
let registry = manifest_registry();
let mut by_conformance: BTreeMap<String, usize> = BTreeMap::new();
let mut adapters = Vec::with_capacity(registry.len());
for entry in registry {
*by_conformance
.entry(conformance_label(entry.conformance))
.or_insert(0) += 1;
adapters.push(AdapterLine {
adapter_id: entry.manifest.adapter_id,
display_name: entry.manifest.display_name,
adapter_version: entry.manifest.adapter_version,
conformance: entry.conformance,
});
}
Self {
count: adapters.len(),
by_conformance,
adapters,
}
}
}
fn conformance_label(level: ConformanceLevel) -> String {
match level {
ConformanceLevel::V1Conformance => "v1_conformance",
ConformanceLevel::PreConformance => "pre_conformance",
}
.to_string()
}