use miette::Diagnostic;
use thiserror::Error;
#[derive(Error, Diagnostic, Debug)]
pub enum RegistryError {
#[error("Error while {action}: {source}")]
#[diagnostic(code(soar_registry::io))]
IoError {
action: String,
source: std::io::Error,
},
#[error(transparent)]
#[diagnostic(code(soar_registry::system_time))]
SystemTimeError(#[from] std::time::SystemTimeError),
#[error(transparent)]
#[diagnostic(
code(soar_registry::http),
help("Check your network connection and the repository URL")
)]
UreqError(#[from] ureq::Error),
#[error(transparent)]
#[diagnostic(code(soar_registry::download))]
DownloadError(#[from] soar_dl::error::DownloadError),
#[error("Failed to fetch from remote source: {0}")]
#[diagnostic(
code(soar_registry::fetch_remote),
help("Verify the repository URL is correct and accessible")
)]
FailedToFetchRemote(String),
#[error(transparent)]
#[diagnostic(
code(soar_registry::json),
help("The metadata file may be corrupted or in an invalid format")
)]
JsonError(#[from] serde_json::Error),
#[error("Invalid URL: {0}")]
#[diagnostic(
code(soar_registry::invalid_url),
help("Ensure the URL is valid and properly formatted")
)]
InvalidUrl(String),
#[error("Metadata content is too short")]
#[diagnostic(
code(soar_registry::metadata_too_short),
help("The metadata file appears to be corrupted or incomplete")
)]
MetadataTooShort,
#[error("ETag not found in metadata response")]
#[diagnostic(
code(soar_registry::missing_etag),
help("The server did not return an ETag header")
)]
MissingEtag,
#[error("Insecure repository URL: {0}")]
#[diagnostic(
code(soar_registry::insecure_url),
help("Repository metadata must be served over https")
)]
InsecureUrl(String),
#[error("Could not fetch metadata signature for {repo}: {reason}")]
#[diagnostic(
code(soar_registry::signature_missing),
help(
"Signature verification is enabled but the detached signature could not be retrieved"
)
)]
MetadataSignatureMissing { repo: String, reason: String },
#[error("Metadata signature verification failed for {repo}: {reason}")]
#[diagnostic(
code(soar_registry::signature_invalid),
help("The metadata may be tampered with or signed by a different key")
)]
MetadataSignatureInvalid { repo: String, reason: String },
#[error("Metadata exceeds the maximum decompressed size of {limit} bytes")]
#[diagnostic(
code(soar_registry::metadata_too_large),
help("The metadata file is unexpectedly large and may be a decompression bomb")
)]
MetadataTooLarge { limit: u64 },
#[error("{0}")]
#[diagnostic(code(soar_registry::custom))]
Custom(String),
}
pub type Result<T> = std::result::Result<T, RegistryError>;
pub trait ErrorContext<T> {
fn with_context<C>(self, context: C) -> Result<T>
where
C: FnOnce() -> String;
}
impl<T> ErrorContext<T> for std::io::Result<T> {
fn with_context<C>(self, context: C) -> Result<T>
where
C: FnOnce() -> String,
{
self.map_err(|err| {
RegistryError::IoError {
action: context(),
source: err,
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = RegistryError::MetadataTooShort;
assert_eq!(err.to_string(), "Metadata content is too short");
let err = RegistryError::MissingEtag;
assert_eq!(err.to_string(), "ETag not found in metadata response");
let err = RegistryError::InvalidUrl("bad-url".to_string());
assert_eq!(err.to_string(), "Invalid URL: bad-url");
}
}