1use rite_model::{ErrorClass, StepFact};
15
16use crate::protocol::{Icon, UiSignal};
17
18const UNSUMMARISED: &str = "unknown fact variant";
20
21#[must_use]
30pub fn fact_summary(fact: &StepFact) -> Option<(Icon, String)> {
31 match fact {
32 StepFact::CeremonyStarted { name, .. } => Some((Icon::Info, format!("Ceremony: {name}"))),
33 StepFact::ActStarted { label, .. } => Some((Icon::Info, format!("Act: {label}"))),
34 StepFact::StepStarted {
35 id,
36 label,
37 role_name,
38 ..
39 } => Some((
40 Icon::Info,
41 format!("Step {label} ({id}), role: {role_name}"),
42 )),
43 StepFact::PromptAnswered { .. }
44 | StepFact::BackendOperation { .. }
45 | StepFact::AttestationRecorded { .. }
46 | StepFact::CeremonyCompleted { .. } => None,
47 StepFact::ArtifactWritten { path, .. } => Some((
48 Icon::Checkmark,
49 format!("Artifact written: {}", path.display()),
50 )),
51 StepFact::DeviationRecorded { text, .. } => {
52 Some((Icon::Warning, format!("Deviation: {text}")))
53 }
54 StepFact::StepCompleted { outcome, .. } => match outcome {
55 rite_model::StepOutcome::Completed { message } => {
56 Some((Icon::Checkmark, message.clone()))
57 }
58 _ => Some((Icon::Checkmark, "Step completed".to_string())),
59 },
60 StepFact::StepAttemptFailed { attempt, error, .. } => Some((
61 Icon::Cross,
62 format!("Attempt {attempt} failed: {}", error.message),
63 )),
64 StepFact::CeremonyFailed { error, .. } => match error.class {
65 ErrorClass::Abort => Some((Icon::Info, error.message.clone())),
68 _ => Some((Icon::Cross, format!("Ceremony failed: {}", error.message))),
69 },
70 StepFact::EntropySeeded { source, .. } => {
71 Some((Icon::Info, format!("Entropy source seeded ({source})")))
72 }
73 StepFact::EntropyContributed { epoch, .. } => Some((
74 Icon::Info,
75 format!("Entropy contribution folded (epoch {epoch})"),
76 )),
77 StepFact::EntropyDrawn { path, .. } => {
78 Some((Icon::Info, format!("Random value drawn: {path}")))
79 }
80 StepFact::WrapRecipientRecorded {
84 source,
85 fingerprint,
86 declared,
87 ..
88 } => Some((
89 Icon::Info,
90 if *declared {
91 format!("Recipient {source} matches the declared {fingerprint}")
92 } else {
93 format!("Recipient {source} is {fingerprint}, undeclared")
94 },
95 )),
96 _ => Some((Icon::Info, UNSUMMARISED.to_string())),
101 }
102}
103
104#[must_use]
111pub fn signal_summary(signal: &UiSignal) -> Option<(Icon, String)> {
112 match signal {
113 UiSignal::LogLine { icon, text, .. } => Some((*icon, text.clone())),
114 UiSignal::Progress {
115 phase, fraction, ..
116 } => {
117 let text = match fraction {
118 Some(f) => format!("{phase}: {:>5.1}%", f * 100.0),
119 None => format!("{phase}…"),
120 };
121 Some((Icon::Spinner, text))
122 }
123 UiSignal::CeremonyOverview { .. } | UiSignal::SystemInfo(_) | UiSignal::Environment(_) => {
124 None
125 }
126 }
127}
128
129#[must_use]
134pub fn truncate_for_display(s: &str, max_chars: usize) -> String {
135 match s.char_indices().nth(max_chars) {
136 Some((boundary, _)) => format!("{}...", &s[..boundary]),
137 None => s.to_string(),
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::{UNSUMMARISED, fact_summary, truncate_for_display};
144 use rite_model::{StepFact, StepId};
145
146 #[test]
149 fn the_wrap_recipient_carries_its_fingerprint() {
150 let (_, line) = fact_summary(&StepFact::WrapRecipientRecorded {
151 step: StepId::new("wrap"),
152 source: "escrow_pubkey".to_string(),
153 fingerprint: "sha256:abcd".to_string(),
154 declared: false,
155 })
156 .expect("the live feed surfaces the recipient");
157 assert_ne!(line, UNSUMMARISED);
158 assert!(line.contains("escrow_pubkey"), "{line}");
159 assert!(line.contains("sha256:abcd"), "{line}");
160 }
161
162 #[test]
163 fn truncate_passes_through_short_input() {
164 assert_eq!(truncate_for_display("short", 10), "short");
165 }
166
167 #[test]
168 fn truncate_adds_ellipsis_when_too_long() {
169 assert_eq!(
170 truncate_for_display("this is a long string", 10),
171 "this is a ..."
172 );
173 }
174
175 #[test]
176 fn truncate_respects_utf8_code_point_boundaries() {
177 let s = "🦀🦀🦀🦀🦀";
179 assert_eq!(truncate_for_display(s, 2), "🦀🦀...");
180 assert_eq!(truncate_for_display(s, 5), "🦀🦀🦀🦀🦀");
181 }
182}