1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
//! Errors related to interacting with an OCI compliant remote store

/// The OCI specification defines a specific error format.
///
/// This struct represents that error format, which is formally described here:
/// https://github.com/opencontainers/distribution-spec/blob/master/spec.md#errors-2
#[derive(serde::Deserialize, Debug)]
pub struct OciError {
    /// The error code
    pub code: OciErrorCode,
    /// A message associated with the error
    pub message: String,
    /// Unstructured data associated with the error
    pub detail: serde_json::Value,
}

impl std::error::Error for OciError {
    fn description(&self) -> &str {
        self.message.as_str()
    }
}
impl std::fmt::Display for OciError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "OCI API error: {}", self.message.as_str())
    }
}

#[derive(serde::Deserialize)]
pub(crate) struct OciEnvelope {
    pub(crate) errors: Vec<OciError>,
}

/// OCI error codes
///
/// Outlined here: https://github.com/opencontainers/distribution-spec/blob/master/spec.md#errors-2
#[derive(serde::Deserialize, Debug, PartialEq)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum OciErrorCode {
    /// Blob unknown to registry
    ///
    /// This error MAY be returned when a blob is unknown to the registry in a specified
    /// repository. This can be returned with a standard get or if a manifest
    /// references an unknown layer during upload.
    BlobUnknown,
    /// Blob upload is invalid
    ///
    /// The blob upload encountered an error and can no longer proceed.
    BlobUploadInvalid,
    /// Blob upload is unknown to registry
    BlobUploadUnknown,
    /// Provided digest did not match uploaded content.
    DigestInvalid,
    /// Blob is unknown to registry
    ManifestBlobUnknown,
    /// Manifest is invalid
    ///
    /// During upload, manifests undergo several checks ensuring validity. If
    /// those checks fail, this error MAY be returned, unless a more specific
    /// error is included. The detail will contain information the failed
    /// validation.
    ManifestInvalid,
    /// Manifest unknown
    ///
    /// This error is returned when the manifest, identified by name and tag is unknown to the repository.
    ManifestUnknown,
    /// Manifest failed signature validation
    ManifestUnverified,
    /// Invalid repository name
    NameInvalid,
    /// Repository name is not known
    NameUnknown,
    /// Provided length did not match content length
    SizeInvalid,
    /// Manifest tag did not match URI
    TagInvalid,
    /// Authentication required.
    Unauthorized,
    /// Requested access to the resource is denied
    Denied,
    /// This operation is unsupported
    Unsupported,
}

#[cfg(test)]
mod test {
    use super::*;

    const EXAMPLE_ERROR: &str = r#"
      {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":[{"Type":"repository","Name":"hello-wasm","Action":"pull"}]}]}
      "#;
    #[test]
    fn test_deserialize() {
        let envelope: OciEnvelope =
            serde_json::from_str(EXAMPLE_ERROR).expect("parse example error");
        let e = &envelope.errors[0];
        assert_eq!(OciErrorCode::Unauthorized, e.code);
        assert_eq!("authentication required", e.message);
    }
}