Skip to main content

soar_registry/
error.rs

1//! Error types for the registry crate.
2//!
3//! This module defines [`RegistryError`], the error type used throughout
4//! the crate, along with helper traits for error context.
5
6use miette::Diagnostic;
7use thiserror::Error;
8
9/// Errors that can occur during registry operations.
10///
11/// This enum covers all error conditions that can arise when fetching,
12/// processing, or storing package metadata.
13#[derive(Error, Diagnostic, Debug)]
14pub enum RegistryError {
15    #[error(
16        "repository index is format {found}, but this soar understands up to \
17         {supported}; upgrade soar to use this repository"
18    )]
19    #[diagnostic(code(soar_registry::unsupported_format))]
20    UnsupportedFormat { found: u32, supported: u32 },
21
22    #[error("Error while {action}: {source}")]
23    #[diagnostic(code(soar_registry::io))]
24    IoError {
25        action: String,
26        source: std::io::Error,
27    },
28
29    #[error(transparent)]
30    #[diagnostic(code(soar_registry::system_time))]
31    SystemTimeError(#[from] std::time::SystemTimeError),
32
33    #[error(transparent)]
34    #[diagnostic(
35        code(soar_registry::http),
36        help("Check your network connection and the repository URL")
37    )]
38    UreqError(#[from] ureq::Error),
39
40    #[error(transparent)]
41    #[diagnostic(code(soar_registry::download))]
42    DownloadError(#[from] soar_dl::error::DownloadError),
43
44    #[error("Failed to fetch from remote source: {0}")]
45    #[diagnostic(
46        code(soar_registry::fetch_remote),
47        help("Verify the repository URL is correct and accessible")
48    )]
49    FailedToFetchRemote(String),
50
51    #[error(transparent)]
52    #[diagnostic(
53        code(soar_registry::json),
54        help("The metadata file may be corrupted or in an invalid format")
55    )]
56    JsonError(#[from] serde_json::Error),
57
58    #[error("Invalid URL: {0}")]
59    #[diagnostic(
60        code(soar_registry::invalid_url),
61        help("Ensure the URL is valid and properly formatted")
62    )]
63    InvalidUrl(String),
64
65    #[error("Metadata content is too short")]
66    #[diagnostic(
67        code(soar_registry::metadata_too_short),
68        help("The metadata file appears to be corrupted or incomplete")
69    )]
70    MetadataTooShort,
71
72    #[error("ETag not found in metadata response")]
73    #[diagnostic(
74        code(soar_registry::missing_etag),
75        help("The server did not return an ETag header")
76    )]
77    MissingEtag,
78
79    #[error("Insecure repository URL: {0}")]
80    #[diagnostic(
81        code(soar_registry::insecure_url),
82        help("Repository metadata must be served over https")
83    )]
84    InsecureUrl(String),
85
86    #[error("Could not fetch metadata signature for {repo}: {reason}")]
87    #[diagnostic(
88        code(soar_registry::signature_missing),
89        help(
90            "Signature verification is enabled but the detached signature could not be retrieved"
91        )
92    )]
93    MetadataSignatureMissing { repo: String, reason: String },
94
95    #[error("Metadata signature verification failed for {repo}: {reason}")]
96    #[diagnostic(
97        code(soar_registry::signature_invalid),
98        help("The metadata may be tampered with or signed by a different key")
99    )]
100    MetadataSignatureInvalid { repo: String, reason: String },
101
102    #[error("Metadata exceeds the maximum decompressed size of {limit} bytes")]
103    #[diagnostic(
104        code(soar_registry::metadata_too_large),
105        help("The metadata file is unexpectedly large and may be a decompression bomb")
106    )]
107    MetadataTooLarge { limit: u64 },
108
109    #[error("{0}")]
110    #[diagnostic(code(soar_registry::custom))]
111    Custom(String),
112}
113
114/// A specialized Result type for registry operations.
115pub type Result<T> = std::result::Result<T, RegistryError>;
116
117/// Extension trait for adding context to I/O errors.
118///
119/// This trait provides a convenient way to convert `std::io::Result` into
120/// [`Result`] with descriptive context about what operation failed.
121pub trait ErrorContext<T> {
122    /// Adds context to an error, describing what action was being performed.
123    ///
124    /// # Arguments
125    ///
126    /// * `context` - A closure that returns a description of the failed action
127    fn with_context<C>(self, context: C) -> Result<T>
128    where
129        C: FnOnce() -> String;
130}
131
132impl<T> ErrorContext<T> for std::io::Result<T> {
133    fn with_context<C>(self, context: C) -> Result<T>
134    where
135        C: FnOnce() -> String,
136    {
137        self.map_err(|err| {
138            RegistryError::IoError {
139                action: context(),
140                source: err,
141            }
142        })
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_error_display() {
152        let err = RegistryError::MetadataTooShort;
153        assert_eq!(err.to_string(), "Metadata content is too short");
154
155        let err = RegistryError::MissingEtag;
156        assert_eq!(err.to_string(), "ETag not found in metadata response");
157
158        let err = RegistryError::InvalidUrl("bad-url".to_string());
159        assert_eq!(err.to_string(), "Invalid URL: bad-url");
160    }
161}