use std::{
fmt::{Display, Formatter},
ops::Not,
};
use chrono::{DateTime, Utc};
use serde::Serialize;
use crate::model::status::CertStatus;
pub use crate::model::status::Status;
#[derive(Debug, Serialize)]
pub struct CertStatusSummary {
pub primary: ComponentKeySummary,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub subkeys: Vec<ComponentKeySummary>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub user_ids: Vec<UserIdSummary>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub user_attributes: Vec<UserAttributeSummary>,
}
#[derive(Debug, Serialize)]
pub struct ComponentKeySummary {
pub fingerprint: String,
pub version: u8,
pub created: DateTime<Utc>,
pub algorithm: String,
pub status: Status,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_flags: Option<Vec<String>>,
}
#[derive(Debug, Serialize)]
pub struct UserIdSummary {
pub id: String,
#[serde(skip_serializing_if = "<&bool>::not")]
pub primary: bool,
pub status: Status,
}
#[derive(Debug, Serialize)]
pub struct UserAttributeSummary {
pub id: String,
#[serde(skip_serializing_if = "<&bool>::not")]
pub primary: bool,
pub status: Status,
}
impl From<CertStatus> for CertStatusSummary {
fn from(value: CertStatus) -> Self {
let primary = ComponentKeySummary {
fingerprint: value.primary.0.fingerprint,
version: value.primary.0.version,
created: value.primary.0.created,
algorithm: value.primary.0.algorithm,
status: value.primary.1,
key_flags: value.primary.2,
};
let mut subkeys = vec![];
for (ki, status, key_flags) in value.subkeys {
let sk = ComponentKeySummary {
fingerprint: ki.fingerprint,
version: ki.version,
created: ki.created,
algorithm: ki.algorithm,
status,
key_flags,
};
subkeys.push(sk);
}
let mut user_ids = vec![];
for (uid, status, primary) in value.users {
user_ids.push(UserIdSummary {
id: uid.id,
primary,
status,
});
}
let mut user_attributes = vec![];
for (ua, status, primary) in value.user_attributes {
user_attributes.push(UserAttributeSummary {
id: ua.id,
primary,
status,
});
}
CertStatusSummary {
primary,
subkeys,
user_ids,
user_attributes,
}
}
}
impl Display for &CertStatusSummary {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"🔐 {} v{} {}",
self.primary.algorithm, self.primary.version, self.primary.fingerprint
)?;
writeln!(f, " ⏱️ Created {}", self.primary.created)?;
writeln!(f, " {}", &self.primary.status)?;
if let Some(key_flags) = &self.primary.key_flags {
writeln!(f, " 🏴 Key flags: {}", key_flags.join(", "))?;
}
writeln!(f)?;
for subkey in &self.subkeys {
writeln!(
f,
" 🔑 {} v{} {}",
subkey.algorithm, subkey.version, subkey.fingerprint
)?;
writeln!(f, " ⏱️ Created {}", subkey.created)?;
writeln!(f, " {}", &subkey.status)?;
if let Some(key_flags) = &subkey.key_flags {
writeln!(f, " 🏴 Key flags: {}", key_flags.join(", "))?;
}
writeln!(f)?;
}
for user in &self.user_ids {
let pri = if user.primary { " (primary)" } else { "" };
writeln!(f, " 🪪 ID {:?}{pri}", user.id)?;
writeln!(f, " {}", &user.status)?;
writeln!(f)?;
}
for ua in &self.user_attributes {
writeln!(f, " 🪪 ATTR [{}]", ua.id)?;
writeln!(f, " {}", &ua.status)?;
writeln!(f)?;
}
Ok(())
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use std::fs::File;
use rstest::rstest;
use crate::certificate::{Certificate, Checked};
fn load_checked(file: &str) -> Checked {
let certs = Certificate::load(&mut File::open(file).expect("cert open")).expect("load");
assert_eq!(certs.len(), 1);
Checked::from(certs.into_iter().next().expect("asserted"))
}
#[rstest]
#[case("tests/certs/hal_v2_1992.cert", "tests/certs/hal_v2_1992.txt")]
#[case("tests/certs/prz_v4_1997.cert", "tests/certs/prz_v4_1997.txt")]
#[case("tests/certs/dkg_v4_2007.cert", "tests/certs/dkg_v4_2007.txt")]
#[case("tests/certs/alice_v6.cert", "tests/certs/alice_v6.txt")]
fn status_summary_display(#[case] cert: &str, #[case] text: &str) {
let checked = load_checked(cert);
let summary = crate::model::status_summary(&checked);
let text_out = format!("{}", &summary);
let nominal_text = std::fs::read_to_string(text).expect("read text");
assert_eq!(text_out, nominal_text);
}
#[rstest]
#[case("tests/certs/hal_v2_1992.cert", "tests/certs/hal_v2_1992.json")]
#[case("tests/certs/prz_v4_1997.cert", "tests/certs/prz_v4_1997.json")]
#[case("tests/certs/dkg_v4_2007.cert", "tests/certs/dkg_v4_2007.json")]
#[case("tests/certs/alice_v6.cert", "tests/certs/alice_v6.json")]
fn status_summary_json(#[case] cert: &str, #[case] json: &str) {
let checked = load_checked(cert);
let summary = crate::model::status_summary(&checked);
let json_out = serde_json::to_string_pretty(&summary).expect("json");
let nominal_json = std::fs::read_to_string(json).expect("read json");
assert_eq!(format!("{json_out}\n"), nominal_json);
}
}