use serde::{Deserialize, Serialize};
use crate::palette::Role;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ShortHash(pub String);
impl ShortHash {
pub fn from_blake3_hex(full: &str) -> Self {
Self(full.chars().take(7).collect())
}
}
impl std::fmt::Display for ShortHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum UiEvent {
Banner {
title: String,
subtitle: Option<String>,
},
Section { title: String },
Log { level: LogLevel, message: String },
PhaseBegin { phase: String },
PhaseEnd { phase: String, elapsed_ms: u64 },
Artifact {
name: String,
hash: ShortHash,
state: ArtifactState,
},
Summary {
root_hash: ShortHash,
total: usize,
built: usize,
cached: usize,
failed: usize,
},
Row { cells: Vec<Cell> },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LogLevel {
Info,
Success,
Warn,
Error,
Dim,
}
impl LogLevel {
pub fn role(self) -> Role {
match self {
Self::Info => Role::Info,
Self::Success => Role::Success,
Self::Warn => Role::Warn,
Self::Error => Role::Error,
Self::Dim => Role::Dim,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "state", rename_all = "kebab-case")]
pub enum ArtifactState {
Built { elapsed_ms: u64 },
Cached,
Pending,
Failed { reason: String },
}
impl ArtifactState {
pub fn label(&self) -> &'static str {
match self {
Self::Built { .. } => "built",
Self::Cached => "cached",
Self::Pending => "pending",
Self::Failed { .. } => "failed",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Cell {
pub text: String,
#[serde(default)]
pub role: Option<Role>,
}
impl Cell {
pub fn plain(text: impl Into<String>) -> Self {
Self {
text: text.into(),
role: None,
}
}
pub fn with_role(text: impl Into<String>, role: Role) -> Self {
Self {
text: text.into(),
role: Some(role),
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct EventStream {
pub events: Vec<UiEvent>,
}
impl EventStream {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, e: UiEvent) {
self.events.push(e);
}
pub fn run_hash(&self) -> String {
let bytes = serde_json::to_vec(self).unwrap_or_default();
hex::encode(blake3::hash(&bytes).as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn short_hash_is_seven_chars() {
let sh = ShortHash::from_blake3_hex("cxx3i50lvlprhlqclm1mxmnp77bawjbx-fake-ignored");
assert_eq!(sh.0.len(), 7);
assert_eq!(sh.to_string(), "cxx3i50");
}
#[test]
fn artifact_state_labels() {
assert_eq!(ArtifactState::Cached.label(), "cached");
assert_eq!(ArtifactState::Built { elapsed_ms: 100 }.label(), "built");
}
#[test]
fn stream_run_hash_is_deterministic() {
let mut s = EventStream::new();
s.push(UiEvent::Section {
title: "boot".into(),
});
s.push(UiEvent::Log {
level: LogLevel::Info,
message: "hello".into(),
});
let h1 = s.run_hash();
let h2 = s.run_hash();
assert_eq!(h1, h2);
assert_eq!(h1.len(), 64);
}
}