docker-registry-client 0.2.0

Communicate with Docker Registries to fetch image metadata.
Documentation
use either::Either;

pub mod digest;
pub mod tag;

use digest::Digest;
use tag::Tag;

#[derive(Debug)]
pub enum FromStrError {
    MissingNameDigest,
    MissingNameTag,
    MissingDigest,
    ParseDigest(digest::FromStrError),
    ParseTag(tag::FromStrError),
}

#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct ImageName {
    pub name: String,
    pub identifier: Either<Tag, Digest>,
}

impl std::fmt::Display for FromStrError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::MissingNameDigest => f.write_str("missing name and digest"),
            Self::MissingNameTag => f.write_str("missing name and tag"),
            Self::MissingDigest => f.write_str("missing digest"),
            Self::ParseDigest(e) => write!(f, "error parsing digest: {e}"),
            Self::ParseTag(e) => write!(f, "error parsing tag: {e}"),
        }
    }
}

impl std::error::Error for FromStrError {}

impl std::fmt::Display for ImageName {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{image_name}:{identifier}",
            image_name = self.name,
            identifier = match &self.identifier {
                Either::Left(tag) => tag.to_string(),
                Either::Right(digest) => digest.to_string(),
            }
        )
    }
}

impl std::str::FromStr for ImageName {
    type Err = FromStrError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let has_digest = s.contains('@');

        if has_digest {
            let parts: Vec<&str> = s.split('@').collect();
            let name = (*parts.first().ok_or(Self::Err::MissingNameDigest)?).to_string();

            let digest = parts
                .get(1)
                .ok_or(Self::Err::MissingDigest)?
                .parse()
                .map_err(Self::Err::ParseDigest)?;

            Ok(Self {
                name,
                identifier: Either::Right(digest),
            })
        } else {
            let parts: Vec<&str> = s.split(':').collect();
            let name = (*parts.first().ok_or(Self::Err::MissingNameTag)?).to_string();

            let tag = parts
                .get(1)
                .unwrap_or(&"latest")
                .parse()
                .map_err(Self::Err::ParseTag)?;

            Ok(Self {
                name,
                identifier: Either::Left(tag),
            })
        }
    }
}