oci_spec/distribution/
error.rs

1//! Error types of the distribution spec.
2
3use crate::error::OciSpecError;
4use derive_builder::Builder;
5use getset::Getters;
6use serde::{Deserialize, Serialize};
7use std::fmt::{self, Display, Formatter};
8use strum_macros::{Display as StrumDisplay, EnumString};
9use thiserror::Error;
10
11/// The string returned by and ErrorResponse error.
12pub const ERR_REGISTRY: &str = "distribution: registry returned error";
13
14/// Unique identifier representing error code.
15#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, StrumDisplay, EnumString)]
16#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
17#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
18pub enum ErrorCode {
19    /// Blob unknown to registry.
20    BlobUnknown,
21    /// Blob upload invalid.
22    BlobUploadInvalid,
23    /// Blob upload unknown to registry.
24    BlobUploadUnknown,
25    /// Provided digest did not match uploaded content.
26    DigestInvalid,
27    /// Blob unknown to registry.
28    ManifestBlobUnknown,
29    /// Manifest invalid.
30    ManifestInvalid,
31    /// Manifest unknown.
32    ManifestUnknown,
33    /// Invalid repository name.
34    NameInvalid,
35    /// Repository name not known to registry.
36    NameUnknown,
37    /// Provided length did not match content length.
38    SizeInvalid,
39    /// Authentication required.
40    Unauthorized,
41    /// Requested access to the resource is denied.
42    Denied,
43    /// The operation is unsupported.
44    Unsupported,
45    /// Too many requests.
46    #[serde(rename = "TOOMANYREQUESTS")]
47    TooManyRequests,
48}
49
50#[derive(Builder, Clone, Debug, Deserialize, Eq, Error, Getters, PartialEq, Serialize)]
51#[builder(
52    pattern = "owned",
53    setter(into, strip_option),
54    build_fn(error = "OciSpecError")
55)]
56#[getset(get = "pub")]
57/// ErrorResponse is returned by a registry on an invalid request.
58pub struct ErrorResponse {
59    /// Available errors within the response.
60    errors: Vec<ErrorInfo>,
61}
62
63impl Display for ErrorResponse {
64    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
65        write!(f, "{ERR_REGISTRY}")
66    }
67}
68
69impl ErrorResponse {
70    /// Returns the ErrorInfo slice for the response.
71    pub fn detail(&self) -> &[ErrorInfo] {
72        &self.errors
73    }
74}
75
76#[derive(Builder, Clone, Debug, Deserialize, Eq, Getters, PartialEq, Serialize)]
77#[builder(
78    pattern = "owned",
79    setter(into, strip_option),
80    build_fn(error = "OciSpecError")
81)]
82#[getset(get = "pub")]
83/// Describes a server error returned from a registry.
84pub struct ErrorInfo {
85    /// The code field MUST be a unique identifier, containing only uppercase alphabetic
86    /// characters and underscores.
87    code: ErrorCode,
88
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    #[builder(default = "None")]
91    /// The message field is OPTIONAL, and if present, it SHOULD be a human readable string or
92    /// MAY be empty.
93    message: Option<String>,
94
95    #[serde(default, skip_serializing_if = "Option::is_none", with = "json_string")]
96    #[builder(default = "None")]
97    /// The detail field is OPTIONAL and MAY contain arbitrary JSON data providing information
98    /// the client can use to resolve the issue.
99    detail: Option<String>,
100}
101
102mod json_string {
103    use std::str::FromStr;
104
105    use serde::{Deserialize, Deserializer, Serialize, Serializer};
106
107    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
108    where
109        D: Deserializer<'de>,
110    {
111        use serde::de::Error;
112
113        let opt = Option::<serde_json::Value>::deserialize(deserializer)?;
114
115        if let Some(data) = opt {
116            let data = serde_json::to_string(&data).map_err(Error::custom)?;
117            return Ok(Some(data));
118        }
119
120        Ok(None)
121    }
122
123    pub fn serialize<S>(target: &Option<String>, serializer: S) -> Result<S::Ok, S::Error>
124    where
125        S: Serializer,
126    {
127        use serde::ser::Error;
128
129        match target {
130            Some(data) => {
131                if let Ok(json_value) = serde_json::Value::from_str(data) {
132                    json_value.serialize(serializer)
133                } else {
134                    Err(Error::custom("invalid JSON"))
135                }
136            }
137            _ => unreachable!(),
138        }
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::error::Result;
146
147    #[test]
148    fn error_response_success() -> Result<()> {
149        let response = ErrorResponseBuilder::default().errors(vec![]).build()?;
150        assert!(response.detail().is_empty());
151        assert_eq!(response.to_string(), ERR_REGISTRY);
152        Ok(())
153    }
154
155    #[test]
156    fn error_response_failure() {
157        assert!(ErrorResponseBuilder::default().build().is_err());
158    }
159
160    #[test]
161    fn error_info_success() -> Result<()> {
162        let info = ErrorInfoBuilder::default()
163            .code(ErrorCode::BlobUnknown)
164            .build()?;
165        assert_eq!(info.code(), &ErrorCode::BlobUnknown);
166        assert!(info.message().is_none());
167        assert!(info.detail().is_none());
168        Ok(())
169    }
170
171    #[test]
172    fn error_info_failure() {
173        assert!(ErrorInfoBuilder::default().build().is_err());
174    }
175
176    #[test]
177    fn error_info_serialize_success() -> Result<()> {
178        let error_info = ErrorInfoBuilder::default()
179            .code(ErrorCode::Unauthorized)
180            .detail(String::from("{ \"key\": \"value\" }"))
181            .build()?;
182
183        assert!(serde_json::to_string(&error_info).is_ok());
184        Ok(())
185    }
186
187    #[test]
188    fn error_info_serialize_failure() -> Result<()> {
189        let error_info = ErrorInfoBuilder::default()
190            .code(ErrorCode::Unauthorized)
191            .detail(String::from("abcd"))
192            .build()?;
193
194        assert!(serde_json::to_string(&error_info).is_err());
195        Ok(())
196    }
197
198    #[test]
199    fn error_info_deserialize_success() -> Result<()> {
200        let error_info_str = r#"
201        {
202            "code": "MANIFEST_UNKNOWN",
203            "message": "manifest tagged by \"lates\" is not found",
204            "detail": {
205                "Tag": "lates"
206            }
207        }"#;
208
209        let error_info: ErrorInfo = serde_json::from_str(error_info_str)?;
210        assert_eq!(error_info.detail().as_ref().unwrap(), "{\"Tag\":\"lates\"}");
211
212        Ok(())
213    }
214}