use crate::location::FilePath;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ContentHash([u8; 32]);
impl ContentHash {
#[must_use]
pub const fn new(bytes: [u8; 32]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; 32] {
&self.0
}
}
impl std::fmt::Display for ContentHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for byte in &self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TrackedRead {
pub path: FilePath,
pub hash: Option<ContentHash>,
}
impl TrackedRead {
#[must_use]
pub const fn found(path: FilePath, hash: ContentHash) -> Self {
Self {
path,
hash: Some(hash),
}
}
#[must_use]
pub const fn absent(path: FilePath) -> Self {
Self { path, hash: None }
}
}
pub fn sort(reads: &mut [TrackedRead]) {
reads.sort_by(|a, b| a.path.cmp(&b.path));
}
#[cfg(test)]
mod tests {
use super::*;
fn hash(seed: u8) -> ContentHash {
ContentHash::new([seed; 32])
}
#[test]
fn a_hash_renders_as_hex() {
let mut bytes = [0u8; 32];
bytes[0] = 0x0a;
bytes[31] = 0xff;
let rendered = ContentHash::new(bytes).to_string();
assert_eq!(rendered.len(), 64, "a blake3 digest is 64 hex characters");
assert!(rendered.starts_with("0a"), "{rendered}");
assert!(rendered.ends_with("ff"), "{rendered}");
}
#[test]
fn an_absent_read_is_still_a_dependency() {
let read = TrackedRead::absent(FilePath::new("tsconfig.json"));
assert_eq!(read.hash, None);
assert_ne!(
read,
TrackedRead::found(FilePath::new("tsconfig.json"), hash(0)),
"absence and presence must not compare equal"
);
}
#[test]
fn dependencies_sort_by_path() {
let mut reads = vec![
TrackedRead::found(FilePath::new("b.json"), hash(1)),
TrackedRead::absent(FilePath::new("a.json")),
TrackedRead::found(FilePath::new("c.json"), hash(2)),
];
sort(&mut reads);
assert_eq!(
reads.iter().map(|r| r.path.as_str()).collect::<Vec<_>>(),
vec!["a.json", "b.json", "c.json"]
);
}
#[test]
fn the_order_reads_happened_in_does_not_survive() {
let one = {
let mut reads = vec![
TrackedRead::found(FilePath::new("b.json"), hash(1)),
TrackedRead::found(FilePath::new("a.json"), hash(2)),
];
sort(&mut reads);
reads
};
let other = {
let mut reads = vec![
TrackedRead::found(FilePath::new("a.json"), hash(2)),
TrackedRead::found(FilePath::new("b.json"), hash(1)),
];
sort(&mut reads);
reads
};
assert_eq!(one, other);
}
}