oci-image 0.1.1

OCI container images on disk: image references, sha256 digests, one-pass layer unpack (whiteouts and opaque directories), gzip (all cores via lgz one-pass streaming decode; small layers on one core) and zstd layers, a content-addressed store of blobs and per-layer directories in overlayfs format (each layer unpacked once, shared between images, refcounted, collected), overlay snapshot prep, and an optional anonymous registry pull.
Documentation
use crate::{Error, Result};

/// A parsed image reference such as `busybox`, `nginx:1.27` or
/// `ghcr.io/org/app@sha256:...`, with Docker's defaults filled in.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImageRef {
    pub registry: String,
    pub repository: String,
    /// A tag, or a digest (`sha256:...`).
    pub reference: String,
}

impl ImageRef {
    pub fn parse(image: &str) -> Result<Self> {
        let image = image.trim();
        if image.is_empty() {
            return Err(Error::BadReference(image.to_string()));
        }
        let (name, digest) = match image.split_once('@') {
            Some((n, d)) => (n, Some(d)),
            None => (image, None),
        };
        let (registry, rest) = match name.split_once('/') {
            Some((first, rest))
                if first.contains('.') || first.contains(':') || first == "localhost" =>
            {
                (first.to_string(), rest.to_string())
            }
            _ => ("docker.io".to_string(), name.to_string()),
        };
        // A tag is a ':' after the last '/'.
        let (repository, tag) = match rest.rfind(':') {
            Some(i) if !rest[i..].contains('/') => {
                (rest[..i].to_string(), rest[i + 1..].to_string())
            }
            _ => (rest, "latest".to_string()),
        };
        let repository = if registry == "docker.io" && !repository.contains('/') {
            format!("library/{repository}")
        } else {
            repository
        };
        if repository.is_empty() || repository.ends_with('/') {
            return Err(Error::BadReference(image.to_string()));
        }
        Ok(Self {
            registry,
            repository,
            reference: digest.map(str::to_string).unwrap_or(tag),
        })
    }

    /// The fully qualified name, e.g. `docker.io/library/busybox:latest`.
    pub fn canonical(&self) -> String {
        if self.reference.contains(':') {
            format!("{}/{}@{}", self.registry, self.repository, self.reference)
        } else {
            format!("{}/{}:{}", self.registry, self.repository, self.reference)
        }
    }

    /// The registry API base for this repository, `https://<host>/v2/<repo>`
    /// (`http` for localhost).
    pub fn api_base(&self) -> String {
        let host = if self.registry == "docker.io" {
            "registry-1.docker.io"
        } else {
            &self.registry
        };
        let scheme = if host.starts_with("localhost") || host.starts_with("127.0.0.1") {
            "http"
        } else {
            "https"
        };
        format!("{scheme}://{host}/v2/{}", self.repository)
    }
}