use std::{fmt, str::FromStr};
use crate::error::ImageError;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Digest {
algorithm: String,
hex: String,
}
impl Digest {
pub fn new(algorithm: impl Into<String>, hex: impl Into<String>) -> Self {
Self {
algorithm: algorithm.into(),
hex: hex.into(),
}
}
pub fn algorithm(&self) -> &str {
&self.algorithm
}
pub fn hex(&self) -> &str {
&self.hex
}
pub fn to_path_safe(&self) -> String {
format!("{}_{}", self.algorithm, self.hex)
}
}
impl FromStr for Digest {
type Err = ImageError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (algo, hex) = s.split_once(':').ok_or_else(|| {
ImageError::ManifestParse(format!("invalid digest (missing ':'): {s}"))
})?;
if algo.is_empty() || hex.is_empty() {
return Err(ImageError::ManifestParse(format!(
"invalid digest (empty component): {s}"
)));
}
Ok(Self {
algorithm: algo.to_string(),
hex: hex.to_string(),
})
}
}
impl fmt::Display for Digest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.algorithm, self.hex)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_valid_digest() {
let d: Digest = "sha256:abc123".parse().unwrap();
assert_eq!(d.algorithm(), "sha256");
assert_eq!(d.hex(), "abc123");
}
#[test]
fn test_display() {
let d = Digest::new("sha256", "abc123");
assert_eq!(d.to_string(), "sha256:abc123");
}
#[test]
fn test_path_safe() {
let d = Digest::new("sha256", "abc123");
assert_eq!(d.to_path_safe(), "sha256_abc123");
}
#[test]
fn test_parse_missing_colon() {
assert!("sha256abc123".parse::<Digest>().is_err());
}
#[test]
fn test_parse_empty_components() {
assert!(":abc123".parse::<Digest>().is_err());
assert!("sha256:".parse::<Digest>().is_err());
}
}