1use miette::Diagnostic;
7use thiserror::Error;
8
9#[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
114pub type Result<T> = std::result::Result<T, RegistryError>;
116
117pub trait ErrorContext<T> {
122 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}