1use thiserror::Error;
4
5pub use crate::digest::DigestError;
6
7#[derive(Error, Debug)]
9pub enum OciDistributionError {
10 #[error("Authentication failure: {0}")]
12 AuthenticationFailure(String),
13 #[error("Failed to convert Config into ConfigFile: {0}")]
14 ConfigConversionError(String),
16 #[error("Digest error: {0}")]
18 DigestError(#[from] DigestError),
19 #[error("Generic error: {0:?}")]
21 GenericError(Option<String>),
22 #[error(transparent)]
24 HeaderValueError(#[from] reqwest::header::ToStrError),
25 #[error("Image manifest not found: {0}")]
27 ImageManifestNotFoundError(String),
28 #[error("Received Image Index/Manifest List, but platform_resolver was not defined on the client config. Consider setting platform_resolver")]
30 ImageIndexParsingNoPlatformResolverError,
31 #[error("Incompatible layer media type: {0}")]
33 IncompatibleLayerMediaTypeError(String),
34 #[error(transparent)]
36 IoError(#[from] std::io::Error),
37 #[error(transparent)]
38 JsonError(#[from] serde_json::error::Error),
40 #[error("Manifest is not valid UTF-8")]
42 ManifestEncodingError(#[from] std::str::Utf8Error),
43 #[error("Failed to parse manifest as Versioned object: {0}")]
45 ManifestParsingError(String),
46 #[error("cannot push a blob without data")]
48 PushNoDataError,
49 #[error("cannot push a layer without data")]
51 PushLayerNoDataError,
52 #[error("No layers to pull")]
54 PullNoLayersError,
55 #[error("Registry error: url {url}, envelope: {envelope}")]
57 RegistryError {
58 envelope: OciEnvelope,
60 url: String,
62 },
63 #[error("Registry did not return a digest header")]
65 RegistryNoDigestError,
66 #[error("Registry did not return a location header")]
68 RegistryNoLocationError,
69 #[error("Failed to decode registry token: {0}")]
71 RegistryTokenDecodeError(String),
72 #[error(transparent)]
74 RequestError(#[from] reqwest::Error),
75 #[error("Server error: url {url}, code: {code}, message: {message}")]
77 ServerError {
78 code: u16,
80 url: String,
82 message: String,
84 },
85 #[error("OCI distribution spec violation: {0}")]
88 SpecViolationError(String),
89 #[error("Not authorized: url {url}")]
91 UnauthorizedError {
92 url: String,
94 },
95 #[error("Error parsing Url {0}")]
97 UrlParseError(String),
98 #[error("Unsupported media type: {0}")]
100 UnsupportedMediaTypeError(String),
101 #[error("Unsupported schema version: {0}")]
103 UnsupportedSchemaVersionError(i32),
104 #[error("Failed to parse manifest: {0}")]
106 VersionedParsingError(String),
107}
108
109pub type Result<T> = std::result::Result<T, OciDistributionError>;
111
112#[derive(serde::Deserialize, serde::Serialize, Debug)]
117pub struct OciError {
118 pub code: OciErrorCode,
120 #[serde(default)]
122 pub message: String,
123 #[serde(default)]
125 pub detail: serde_json::Value,
126}
127
128impl std::error::Error for OciError {
129 fn description(&self) -> &str {
130 self.message.as_str()
131 }
132}
133impl std::fmt::Display for OciError {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 write!(f, "OCI API error: {}", self.message.as_str())
136 }
137}
138
139#[derive(serde::Deserialize, serde::Serialize, Debug)]
141pub struct OciEnvelope {
142 pub errors: Vec<OciError>,
144}
145
146impl std::fmt::Display for OciEnvelope {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 let errors: Vec<String> = self.errors.iter().map(|e| e.to_string()).collect();
149 write!(f, "OCI API errors: [{}]", errors.join("\n"))
150 }
151}
152
153#[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)]
157#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
158pub enum OciErrorCode {
159 BlobUnknown,
165 BlobUploadInvalid,
169 BlobUploadUnknown,
171 DigestInvalid,
173 ManifestBlobUnknown,
175 ManifestInvalid,
182 ManifestUnknown,
186 ManifestUnverified,
190 NameInvalid,
192 NameUnknown,
194 NotFound,
196 SizeInvalid,
198 TagInvalid,
202 Unauthorized,
204 Denied,
206 Unsupported,
208 Toomanyrequests,
210}
211
212#[cfg(test)]
213mod test {
214 use super::*;
215
216 const EXAMPLE_ERROR: &str = r#"
217 {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Name":"hello-wasm","Action":"pull"}]}]}
218 "#;
219 #[test]
220 fn test_deserialize() {
221 let envelope: OciEnvelope =
222 serde_json::from_str(EXAMPLE_ERROR).expect("parse example error");
223 let e = &envelope.errors[0];
224 assert_eq!(OciErrorCode::Unauthorized, e.code);
225 assert_eq!("authentication required", e.message);
226 assert_ne!(serde_json::value::Value::Null, e.detail);
227 }
228
229 const EXAMPLE_ERROR_TOOMANYREQUESTS: &str = r#"
230 {"errors":[{"code":"TOOMANYREQUESTS","message":"pull request limit exceeded","detail":"You have reached your pull rate limit."}]}
231 "#;
232 #[test]
233 fn test_deserialize_toomanyrequests() {
234 let envelope: OciEnvelope =
235 serde_json::from_str(EXAMPLE_ERROR_TOOMANYREQUESTS).expect("parse example error");
236 let e = &envelope.errors[0];
237 assert_eq!(OciErrorCode::Toomanyrequests, e.code);
238 assert_eq!("pull request limit exceeded", e.message);
239 assert_ne!(serde_json::value::Value::Null, e.detail);
240 }
241
242 const EXAMPLE_ERROR_MISSING_MESSAGE: &str = r#"
243 {"errors":[{"code":"UNAUTHORIZED","detail":[{"Type":"repository","Name":"hello-wasm","Action":"pull"}]}]}
244 "#;
245 #[test]
246 fn test_deserialize_without_message_field() {
247 let envelope: OciEnvelope =
248 serde_json::from_str(EXAMPLE_ERROR_MISSING_MESSAGE).expect("parse example error");
249 let e = &envelope.errors[0];
250 assert_eq!(OciErrorCode::Unauthorized, e.code);
251 assert_eq!(String::default(), e.message);
252 assert_ne!(serde_json::value::Value::Null, e.detail);
253 }
254
255 const EXAMPLE_ERROR_MISSING_DETAIL: &str = r#"
256 {"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}
257 "#;
258 #[test]
259 fn test_deserialize_without_detail_field() {
260 let envelope: OciEnvelope =
261 serde_json::from_str(EXAMPLE_ERROR_MISSING_DETAIL).expect("parse example error");
262 let e = &envelope.errors[0];
263 assert_eq!(OciErrorCode::Unauthorized, e.code);
264 assert_eq!("authentication required", e.message);
265 assert_eq!(serde_json::value::Value::Null, e.detail);
266 }
267}