microsandbox_image/
digest.rs1use std::{fmt, str::FromStr};
4
5use crate::error::ImageError;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
13pub struct Digest {
14 algorithm: String,
16 hex: String,
18}
19
20impl Digest {
25 pub fn new(algorithm: impl Into<String>, hex: impl Into<String>) -> Self {
27 Self {
28 algorithm: algorithm.into(),
29 hex: hex.into(),
30 }
31 }
32
33 pub fn algorithm(&self) -> &str {
35 &self.algorithm
36 }
37
38 pub fn hex(&self) -> &str {
40 &self.hex
41 }
42
43 pub fn to_path_safe(&self) -> String {
47 format!("{}_{}", self.algorithm, self.hex)
48 }
49}
50
51impl FromStr for Digest {
56 type Err = ImageError;
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 let (algo, hex) = s.split_once(':').ok_or_else(|| {
60 ImageError::ManifestParse(format!("invalid digest (missing ':'): {s}"))
61 })?;
62
63 if algo.is_empty() || hex.is_empty() {
64 return Err(ImageError::ManifestParse(format!(
65 "invalid digest (empty component): {s}"
66 )));
67 }
68
69 Ok(Self {
70 algorithm: algo.to_string(),
71 hex: hex.to_string(),
72 })
73 }
74}
75
76impl fmt::Display for Digest {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{}:{}", self.algorithm, self.hex)
79 }
80}
81
82#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_parse_valid_digest() {
92 let d: Digest = "sha256:abc123".parse().unwrap();
93 assert_eq!(d.algorithm(), "sha256");
94 assert_eq!(d.hex(), "abc123");
95 }
96
97 #[test]
98 fn test_display() {
99 let d = Digest::new("sha256", "abc123");
100 assert_eq!(d.to_string(), "sha256:abc123");
101 }
102
103 #[test]
104 fn test_path_safe() {
105 let d = Digest::new("sha256", "abc123");
106 assert_eq!(d.to_path_safe(), "sha256_abc123");
107 }
108
109 #[test]
110 fn test_parse_missing_colon() {
111 assert!("sha256abc123".parse::<Digest>().is_err());
112 }
113
114 #[test]
115 fn test_parse_empty_components() {
116 assert!(":abc123".parse::<Digest>().is_err());
117 assert!("sha256:".parse::<Digest>().is_err());
118 }
119}