aether-evals 0.2.8

Dockerized eval harness for Aether AI agents
Documentation
use super::ImageParseError;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Image {
    pub name: String,
    pub tag: String,
}

impl Image {
    pub fn new(name: impl Into<String>, tag: impl Into<String>) -> Self {
        Self { name: name.into(), tag: tag.into() }
    }

    pub fn parse(reference: &str) -> Result<Self, ImageParseError> {
        if reference.is_empty() || reference.starts_with(':') || reference.ends_with(':') {
            return Err(ImageParseError { reference: reference.to_string() });
        }

        let last_slash = reference.rfind('/');
        let last_colon = reference.rfind(':');
        let image = match last_colon.filter(|colon| last_slash.is_none_or(|slash| *colon > slash)) {
            Some(colon) => Image::new(&reference[..colon], &reference[colon + 1..]),
            None => Image::new(reference, "latest"),
        };
        Ok(image)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn docker_image_parse_handles_name_tag() {
        assert_eq!(Image::parse("aether-sandbox:dev").unwrap(), Image::new("aether-sandbox", "dev"));
    }

    #[test]
    fn docker_image_parse_handles_registry_path() {
        assert_eq!(Image::parse("ghcr.io/org/aether:sha").unwrap(), Image::new("ghcr.io/org/aether", "sha"));
    }

    #[test]
    fn docker_image_parse_defaults_latest() {
        assert_eq!(Image::parse("aether-sandbox").unwrap(), Image::new("aether-sandbox", "latest"));
    }

    #[test]
    fn docker_image_parse_rejects_invalid_reference() {
        assert!(Image::parse(":latest").is_err());
        assert!(Image::parse("aether:").is_err());
    }
}