use rite_model::{ErrorClass, StepFact};
use crate::protocol::{Icon, UiSignal};
const UNSUMMARISED: &str = "unknown fact variant";
#[must_use]
pub fn fact_summary(fact: &StepFact) -> Option<(Icon, String)> {
match fact {
StepFact::CeremonyStarted { name, .. } => Some((Icon::Info, format!("Ceremony: {name}"))),
StepFact::ActStarted { label, .. } => Some((Icon::Info, format!("Act: {label}"))),
StepFact::StepStarted { id, label, role } => {
Some((Icon::Info, format!("Step {label} ({id}), role: {role}")))
}
StepFact::PromptAnswered { .. }
| StepFact::BackendOperation { .. }
| StepFact::AttestationRecorded { .. }
| StepFact::MachineInfoRecorded { .. }
| StepFact::RoleDeclared { .. }
| StepFact::CeremonyCompleted { .. } => None,
StepFact::RoleAssigned { role, person } => {
Some((Icon::Info, format!("Role {role}: {person}")))
}
StepFact::ParameterBound { name, value } => {
Some((Icon::Info, format!("Parameter {name} = {value}")))
}
StepFact::MaterialLoaded { name, identifier } => Some((
Icon::Checkmark,
match identifier {
Some(identifier) => format!("Material loaded: {name} ({identifier})"),
None => format!("Material loaded: {name}"),
},
)),
StepFact::MaterialDigest { name, digest } => {
Some((Icon::Info, format!("Material {name}: {digest}")))
}
StepFact::BackendBound { name, identity, .. } => {
Some((Icon::Info, format!("Backend {name}: {identity}")))
}
StepFact::ArtifactWritten { file, .. } => Some((
Icon::Checkmark,
format!("Artifact written: artifacts/{file}"),
)),
StepFact::DeviationRecorded { text, .. } => {
Some((Icon::Warning, format!("Deviation: {text}")))
}
StepFact::StepCompleted { outcome, .. } => match outcome {
rite_model::StepOutcome::Completed { message } => {
Some((Icon::Checkmark, message.clone()))
}
_ => Some((Icon::Checkmark, "Step completed".to_string())),
},
StepFact::StepAttemptFailed { attempt, error, .. } => Some((
Icon::Cross,
format!("Attempt {attempt} failed: {}", error.message),
)),
StepFact::CeremonyFailed { error, .. } => match error.class {
ErrorClass::Abort => Some((Icon::Info, error.message.clone())),
_ => Some((Icon::Cross, format!("Ceremony failed: {}", error.message))),
},
StepFact::EntropySeeded { source, .. } => {
Some((Icon::Info, format!("Entropy source seeded ({source})")))
}
StepFact::EntropyContributed { epoch, .. } => Some((
Icon::Info,
format!("Entropy contribution folded (epoch {epoch})"),
)),
StepFact::EntropyDrawn { path, .. } => {
Some((Icon::Info, format!("Random value drawn: {path}")))
}
StepFact::WrapRecipientRecorded {
source,
fingerprint,
declared,
..
} => Some((
Icon::Info,
if *declared {
format!("Recipient {source} matches the declared {fingerprint}")
} else {
format!("Recipient {source} is {fingerprint}, undeclared")
},
)),
_ => Some((Icon::Info, UNSUMMARISED.to_string())),
}
}
#[must_use]
pub fn signal_summary(signal: &UiSignal) -> Option<(Icon, String)> {
match signal {
UiSignal::LogLine { icon, text, .. } => Some((*icon, text.clone())),
UiSignal::Progress {
phase, fraction, ..
} => {
let text = match fraction {
Some(f) => format!("{phase}: {:>5.1}%", f * 100.0),
None => format!("{phase}…"),
};
Some((Icon::Spinner, text))
}
UiSignal::CeremonyOverview { .. } | UiSignal::SystemInfo(_) | UiSignal::Environment(_) => {
None
}
}
}
#[must_use]
pub fn truncate_for_display(s: &str, max_chars: usize) -> String {
match s.char_indices().nth(max_chars) {
Some((boundary, _)) => format!("{}...", &s[..boundary]),
None => s.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::{UNSUMMARISED, fact_summary, truncate_for_display};
use rite_model::{StepFact, StepId};
#[test]
fn the_wrap_recipient_carries_its_fingerprint() {
let (_, line) = fact_summary(&StepFact::WrapRecipientRecorded {
step: StepId::new("wrap"),
source: "escrow_pubkey".to_string(),
fingerprint: "sha256:abcd".to_string(),
declared: false,
})
.expect("the live feed surfaces the recipient");
assert_ne!(line, UNSUMMARISED);
assert!(line.contains("escrow_pubkey"), "{line}");
assert!(line.contains("sha256:abcd"), "{line}");
}
#[test]
fn truncate_passes_through_short_input() {
assert_eq!(truncate_for_display("short", 10), "short");
}
#[test]
fn truncate_adds_ellipsis_when_too_long() {
assert_eq!(
truncate_for_display("this is a long string", 10),
"this is a ..."
);
}
#[test]
fn truncate_respects_utf8_code_point_boundaries() {
let s = "🦀🦀🦀🦀🦀";
assert_eq!(truncate_for_display(s, 2), "🦀🦀...");
assert_eq!(truncate_for_display(s, 5), "🦀🦀🦀🦀🦀");
}
}