starfield-datastore 0.1.3

Pull-through artifact cache with fail-closed validation, an S3 mirror served by presigned redirect, and a credential model that keeps upstream secrets in one place
Documentation
use crate::{ArtifactKey, CheckFailure};

#[derive(Debug, thiserror::Error)]
pub enum DatastoreError {
    #[error("no credential configured for host {host}")]
    NoCredential { host: String },
    #[error("credential rejected by {host} (HTTP {status}){}", if *.looks_expired { "; it looks expired" } else { "" })]
    CredentialRejected {
        host: String,
        status: u16,
        looks_expired: bool,
    },
    #[error("content rejected for {key}: failed {}; got {}", .failure.check, .failure.got)]
    ContentRejected {
        key: ArtifactKey,
        failure: CheckFailure,
    },
    #[error("offline, and {key} is not in the local cache")]
    OfflineMiss { key: ArtifactKey },
    #[error("{key} is not cached and the mirror is unreachable ({reason}); set STARFIELD_ALLOW_UPSTREAM=1 to fetch from the archive")]
    MirrorUnreachable { key: ArtifactKey, reason: String },
    #[error("{key} is not cached and has no sources; obtain it manually: {instructions}")]
    ManualRequired {
        key: ArtifactKey,
        instructions: String,
    },
    #[error("all {} sources failed for {key}: {}", .attempts.len(), .attempts.join("; "))]
    AllSourcesFailed {
        key: ArtifactKey,
        attempts: Vec<String>,
    },
    #[error("invalid artifact key: {0}")]
    InvalidKey(String),
    #[error("mirror error: {0}")]
    Mirror(String),
    #[error("configuration error: {0}")]
    Config(String),
    #[error("cold fill unavailable: {0}")]
    FillUnavailable(String),
    #[error("artifact transfer exceeds the reserved fill byte limit")]
    TransferLimit,
    #[error("manifest error: {0}")]
    Manifest(String),
    #[error(transparent)]
    Io(#[from] std::io::Error),
    #[error("HTTP request failed")]
    Http(#[source] reqwest::Error),
}

impl From<reqwest::Error> for DatastoreError {
    fn from(error: reqwest::Error) -> Self {
        Self::Http(error.without_url())
    }
}

pub type Result<T> = std::result::Result<T, DatastoreError>;