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 #[serde(untagged)]
215 Other(String),
216}
217
218#[cfg(test)]
219mod test {
220 use super::*;
221
222 const EXAMPLE_ERROR: &str = r#"
223 {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Name":"hello-wasm","Action":"pull"}]}]}
224 "#;
225 #[test]
226 fn test_deserialize() {
227 let envelope: OciEnvelope =
228 serde_json::from_str(EXAMPLE_ERROR).expect("parse example error");
229 let e = &envelope.errors[0];
230 assert_eq!(OciErrorCode::Unauthorized, e.code);
231 assert_eq!("authentication required", e.message);
232 assert_ne!(serde_json::value::Value::Null, e.detail);
233 }
234
235 const EXAMPLE_ERROR_TOOMANYREQUESTS: &str = r#"
236 {"errors":[{"code":"TOOMANYREQUESTS","message":"pull request limit exceeded","detail":"You have reached your pull rate limit."}]}
237 "#;
238 #[test]
239 fn test_deserialize_toomanyrequests() {
240 let envelope: OciEnvelope =
241 serde_json::from_str(EXAMPLE_ERROR_TOOMANYREQUESTS).expect("parse example error");
242 let e = &envelope.errors[0];
243 assert_eq!(OciErrorCode::Toomanyrequests, e.code);
244 assert_eq!("pull request limit exceeded", e.message);
245 assert_ne!(serde_json::value::Value::Null, e.detail);
246 }
247
248 const EXAMPLE_ERROR_UNKNOWN_CODE: &str = r#"
249 {"errors":[{"code":"ARTIFACT_LOCKED","message":"artifact is locked"}]}
250 "#;
251 #[test]
252 fn test_deserialize_unknown_code() {
253 let envelope: OciEnvelope =
254 serde_json::from_str(EXAMPLE_ERROR_UNKNOWN_CODE).expect("parse example error");
255 let e = &envelope.errors[0];
256 assert_eq!(OciErrorCode::Other("ARTIFACT_LOCKED".to_string()), e.code);
257 assert_eq!("artifact is locked", e.message);
258 assert_eq!(
259 r#"{"code":"ARTIFACT_LOCKED","message":"artifact is locked","detail":null}"#,
260 serde_json::to_string(e).expect("serialize error")
261 );
262 }
263
264 const EXAMPLE_ERROR_MISSING_MESSAGE: &str = r#"
265 {"errors":[{"code":"UNAUTHORIZED","detail":[{"Type":"repository","Name":"hello-wasm","Action":"pull"}]}]}
266 "#;
267 #[test]
268 fn test_deserialize_without_message_field() {
269 let envelope: OciEnvelope =
270 serde_json::from_str(EXAMPLE_ERROR_MISSING_MESSAGE).expect("parse example error");
271 let e = &envelope.errors[0];
272 assert_eq!(OciErrorCode::Unauthorized, e.code);
273 assert_eq!(String::default(), e.message);
274 assert_ne!(serde_json::value::Value::Null, e.detail);
275 }
276
277 const EXAMPLE_ERROR_MISSING_DETAIL: &str = r#"
278 {"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}
279 "#;
280 #[test]
281 fn test_deserialize_without_detail_field() {
282 let envelope: OciEnvelope =
283 serde_json::from_str(EXAMPLE_ERROR_MISSING_DETAIL).expect("parse example error");
284 let e = &envelope.errors[0];
285 assert_eq!(OciErrorCode::Unauthorized, e.code);
286 assert_eq!("authentication required", e.message);
287 assert_eq!(serde_json::value::Value::Null, e.detail);
288 }
289}