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 SizeInvalid,
196 TagInvalid,
200 Unauthorized,
202 Denied,
204 Unsupported,
206 Toomanyrequests,
208}
209
210#[cfg(test)]
211mod test {
212 use super::*;
213
214 const EXAMPLE_ERROR: &str = r#"
215 {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Name":"hello-wasm","Action":"pull"}]}]}
216 "#;
217 #[test]
218 fn test_deserialize() {
219 let envelope: OciEnvelope =
220 serde_json::from_str(EXAMPLE_ERROR).expect("parse example error");
221 let e = &envelope.errors[0];
222 assert_eq!(OciErrorCode::Unauthorized, e.code);
223 assert_eq!("authentication required", e.message);
224 assert_ne!(serde_json::value::Value::Null, e.detail);
225 }
226
227 const EXAMPLE_ERROR_TOOMANYREQUESTS: &str = r#"
228 {"errors":[{"code":"TOOMANYREQUESTS","message":"pull request limit exceeded","detail":"You have reached your pull rate limit."}]}
229 "#;
230 #[test]
231 fn test_deserialize_toomanyrequests() {
232 let envelope: OciEnvelope =
233 serde_json::from_str(EXAMPLE_ERROR_TOOMANYREQUESTS).expect("parse example error");
234 let e = &envelope.errors[0];
235 assert_eq!(OciErrorCode::Toomanyrequests, e.code);
236 assert_eq!("pull request limit exceeded", e.message);
237 assert_ne!(serde_json::value::Value::Null, e.detail);
238 }
239
240 const EXAMPLE_ERROR_MISSING_MESSAGE: &str = r#"
241 {"errors":[{"code":"UNAUTHORIZED","detail":[{"Type":"repository","Name":"hello-wasm","Action":"pull"}]}]}
242 "#;
243 #[test]
244 fn test_deserialize_without_message_field() {
245 let envelope: OciEnvelope =
246 serde_json::from_str(EXAMPLE_ERROR_MISSING_MESSAGE).expect("parse example error");
247 let e = &envelope.errors[0];
248 assert_eq!(OciErrorCode::Unauthorized, e.code);
249 assert_eq!(String::default(), e.message);
250 assert_ne!(serde_json::value::Value::Null, e.detail);
251 }
252
253 const EXAMPLE_ERROR_MISSING_DETAIL: &str = r#"
254 {"errors":[{"code":"UNAUTHORIZED","message":"authentication required"}]}
255 "#;
256 #[test]
257 fn test_deserialize_without_detail_field() {
258 let envelope: OciEnvelope =
259 serde_json::from_str(EXAMPLE_ERROR_MISSING_DETAIL).expect("parse example error");
260 let e = &envelope.errors[0];
261 assert_eq!(OciErrorCode::Unauthorized, e.code);
262 assert_eq!("authentication required", e.message);
263 assert_eq!(serde_json::value::Value::Null, e.detail);
264 }
265}