#[derive(Debug, PartialEq, Eq)]
pub(super) struct HealthInfo {
pub(super) uptime_secs: u64,
pub(super) version: String,
pub(super) crontab_sync: Option<CrontabSyncInfo>,
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct CrontabSyncInfo {
pub(super) ok: bool,
pub(super) last_error: Option<String>,
pub(super) last_error_at: Option<u64>,
}
pub(super) const CRONTAB_SYNC_RECOVERY_HINT: &str =
"grant Full Disk Access to the moadim binary (or its launcher), then restart/update a routine to retry crontab sync";
pub(super) fn status_json(running: bool, pid: Option<u32>, health: Option<&HealthInfo>) -> String {
let uptime_secs = health.map(|info| info.uptime_secs);
let version = health.map(|info| info.version.as_str());
let crontab_sync = health.and_then(|info| info.crontab_sync.as_ref());
let crontab_sync_json = crontab_sync.map(|info| {
serde_json::json!({
"ok": info.ok,
"last_error": info.last_error,
"last_error_at": info.last_error_at,
"recovery_hint": (!info.ok).then_some(CRONTAB_SYNC_RECOVERY_HINT),
})
});
serde_json::json!({
"running": running,
"pid": pid,
"address": bind_addr(),
"uptime_secs": uptime_secs,
"version": version,
"crontab_sync": crontab_sync_json,
})
.to_string()
}
pub(super) fn fetch_health() -> Option<HealthInfo> {
let (status, body) = http_request_with_body("GET", "/api/v1/health").ok()?;
(status == 200).then(|| parse_health(&body)).flatten()
}
pub(super) fn parse_health(body: &str) -> Option<HealthInfo> {
let value: serde_json::Value = serde_json::from_str(body).ok()?;
let uptime_secs = value.get("uptime_secs")?.as_u64()?;
let version = value.get("version")?.as_str()?.to_string();
let crontab_sync = value.get("crontab_sync").and_then(parse_crontab_sync);
Some(HealthInfo {
uptime_secs,
version,
crontab_sync,
})
}
fn parse_crontab_sync(value: &serde_json::Value) -> Option<CrontabSyncInfo> {
Some(CrontabSyncInfo {
ok: value.get("ok")?.as_bool()?,
last_error: value
.get("last_error")
.and_then(|error| error.as_str().map(ToString::to_string)),
last_error_at: value.get("last_error_at").and_then(serde_json::Value::as_u64),
})
}
pub(super) fn cleanup_json(removed: usize, freed_bytes: u64, running: bool) -> String {
serde_json::json!({
"running": running,
"removed": removed,
"freed_bytes": freed_bytes,
"address": bind_addr(),
})
.to_string()
}