Skip to main content

aether_evals/containers/
image.rs

1use super::ImageParseError;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4pub struct Image {
5    pub name: String,
6    pub tag: String,
7}
8
9impl Image {
10    pub fn new(name: impl Into<String>, tag: impl Into<String>) -> Self {
11        Self { name: name.into(), tag: tag.into() }
12    }
13
14    pub fn parse(reference: &str) -> Result<Self, ImageParseError> {
15        if reference.is_empty() || reference.starts_with(':') || reference.ends_with(':') {
16            return Err(ImageParseError { reference: reference.to_string() });
17        }
18
19        let last_slash = reference.rfind('/');
20        let last_colon = reference.rfind(':');
21        let image = match last_colon.filter(|colon| last_slash.is_none_or(|slash| *colon > slash)) {
22            Some(colon) => Image::new(&reference[..colon], &reference[colon + 1..]),
23            None => Image::new(reference, "latest"),
24        };
25        Ok(image)
26    }
27}
28
29#[cfg(test)]
30mod tests {
31    use super::*;
32
33    #[test]
34    fn docker_image_parse_handles_name_tag() {
35        assert_eq!(Image::parse("aether-sandbox:dev").unwrap(), Image::new("aether-sandbox", "dev"));
36    }
37
38    #[test]
39    fn docker_image_parse_handles_registry_path() {
40        assert_eq!(Image::parse("ghcr.io/org/aether:sha").unwrap(), Image::new("ghcr.io/org/aether", "sha"));
41    }
42
43    #[test]
44    fn docker_image_parse_defaults_latest() {
45        assert_eq!(Image::parse("aether-sandbox").unwrap(), Image::new("aether-sandbox", "latest"));
46    }
47
48    #[test]
49    fn docker_image_parse_rejects_invalid_reference() {
50        assert!(Image::parse(":latest").is_err());
51        assert!(Image::parse("aether:").is_err());
52    }
53}