Skip to main content

assay_registry/
error.rs

1//! Error types for the registry client.
2
3use std::time::Duration;
4
5/// Registry errors.
6#[derive(Debug, thiserror::Error)]
7pub enum RegistryError {
8    /// Pack not found in registry.
9    #[error("pack not found: {name}@{version}")]
10    NotFound { name: String, version: String },
11
12    /// Authentication failed or token invalid.
13    #[error("unauthorized: {message}")]
14    Unauthorized { message: String },
15
16    /// Rate limit exceeded.
17    #[error("rate limited: retry after {retry_after:?}")]
18    RateLimited { retry_after: Option<Duration> },
19
20    /// Pack has been revoked (410 Gone).
21    #[error("pack revoked: {name}@{version} - {reason}")]
22    Revoked {
23        name: String,
24        version: String,
25        reason: String,
26        /// Suggested safe version to use instead (if available).
27        safe_version: Option<String>,
28    },
29
30    /// Digest verification failed.
31    #[error("digest mismatch for {name}@{version}: expected {expected}, got {actual}")]
32    DigestMismatch {
33        name: String,
34        version: String,
35        expected: String,
36        actual: String,
37    },
38
39    /// Signature verification failed.
40    #[error("signature verification failed: {reason}")]
41    SignatureInvalid { reason: String },
42
43    /// Key not trusted.
44    #[error("key not trusted: {key_id}")]
45    KeyNotTrusted { key_id: String },
46
47    /// Pack is unsigned but unsigned packs are not allowed.
48    #[error("pack is unsigned: {name}@{version}")]
49    Unsigned { name: String, version: String },
50
51    /// Invalid pack reference format.
52    #[error("invalid pack reference: {reference} - {reason}")]
53    InvalidReference { reference: String, reason: String },
54
55    /// Network error.
56    #[error("network error: {message}")]
57    Network { message: String },
58
59    /// Cache error.
60    #[error("cache error: {message}")]
61    Cache { message: String },
62
63    /// Invalid response from registry.
64    #[error("invalid response: {message}")]
65    InvalidResponse { message: String },
66
67    /// Configuration error.
68    #[error("configuration error: {message}")]
69    Config { message: String },
70
71    /// Lockfile error.
72    #[error("lockfile error: {message}")]
73    Lockfile { message: String },
74}
75
76impl RegistryError {
77    /// Exit code for CLI.
78    pub fn exit_code(&self) -> i32 {
79        match self {
80            // Not found / config issues
81            Self::NotFound { .. } => 1,
82            Self::Config { .. } => 1,
83            Self::InvalidReference { .. } => 1,
84
85            // Auth issues
86            Self::Unauthorized { .. } => 2,
87
88            // Security issues (higher priority)
89            Self::Revoked { .. } => 3,
90            Self::DigestMismatch { .. } => 4,
91            Self::SignatureInvalid { .. } => 4,
92            Self::KeyNotTrusted { .. } => 4,
93            Self::Unsigned { .. } => 4,
94
95            // Network/transient
96            Self::RateLimited { .. } => 5,
97            Self::Network { .. } => 5,
98
99            // Other
100            Self::Cache { .. } => 6,
101            Self::InvalidResponse { .. } => 6,
102            Self::Lockfile { .. } => 7,
103        }
104    }
105
106    /// Whether the error is retryable.
107    pub fn is_retryable(&self) -> bool {
108        matches!(self, Self::RateLimited { .. } | Self::Network { .. })
109    }
110}
111
112impl From<reqwest::Error> for RegistryError {
113    fn from(err: reqwest::Error) -> Self {
114        Self::Network {
115            message: err.to_string(),
116        }
117    }
118}
119
120/// Result type for registry operations.
121pub type RegistryResult<T> = Result<T, RegistryError>;