oci_spec/distribution/
error.rs1use 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
11pub const ERR_REGISTRY: &str = "distribution: registry returned error";
13
14#[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 BlobUnknown,
21 BlobUploadInvalid,
23 BlobUploadUnknown,
25 DigestInvalid,
27 ManifestBlobUnknown,
29 ManifestInvalid,
31 ManifestUnknown,
33 NameInvalid,
35 NameUnknown,
37 SizeInvalid,
39 Unauthorized,
41 Denied,
43 Unsupported,
45 #[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")]
57pub struct ErrorResponse {
59 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 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")]
83pub struct ErrorInfo {
85 code: ErrorCode,
88
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 #[builder(default = "None")]
91 message: Option<String>,
94
95 #[serde(default, skip_serializing_if = "Option::is_none", with = "json_string")]
96 #[builder(default = "None")]
97 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}