1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum ProxyError {
8 #[error("bucket not found: {0}")]
10 BucketNotFound(String),
11
12 #[error("no such key: {0}")]
14 NoSuchKey(String),
15
16 #[error("access denied")]
18 AccessDenied,
19
20 #[error("signature mismatch")]
22 SignatureDoesNotMatch,
23
24 #[error("invalid request: {0}")]
26 InvalidRequest(String),
27
28 #[error("malformed XML: {0}")]
32 MalformedXml(String),
33
34 #[error("not implemented: {0}")]
38 NotImplemented(String),
39
40 #[error("entity too large")]
44 EntityTooLarge,
45
46 #[error("missing authentication")]
48 MissingAuth,
49
50 #[error("expired credentials")]
52 ExpiredCredentials,
53
54 #[error("invalid OIDC token: {0}")]
56 InvalidOidcToken(String),
57
58 #[error("role not found: {0}")]
60 RoleNotFound(String),
61
62 #[error("backend error: {0}")]
64 BackendError(String),
65
66 #[error("backend authentication failed: {0}")]
75 BackendAuthError(String),
76
77 #[error("precondition failed")]
79 PreconditionFailed,
80
81 #[error("not modified")]
83 NotModified,
84
85 #[error("config error: {0}")]
87 ConfigError(String),
88
89 #[error("internal error: {0}")]
91 Internal(String),
92}
93
94impl ProxyError {
95 pub fn s3_error_code(&self) -> &'static str {
97 match self {
98 Self::BucketNotFound(_) => "NoSuchBucket",
99 Self::NoSuchKey(_) => "NoSuchKey",
100 Self::AccessDenied => "AccessDenied",
101 Self::SignatureDoesNotMatch => "SignatureDoesNotMatch",
102 Self::InvalidRequest(_) => "InvalidRequest",
103 Self::MalformedXml(_) => "MalformedXML",
104 Self::NotImplemented(_) => "NotImplemented",
105 Self::EntityTooLarge => "EntityTooLarge",
106 Self::MissingAuth => "AccessDenied",
107 Self::ExpiredCredentials => "ExpiredToken",
108 Self::InvalidOidcToken(_) => "InvalidIdentityToken",
109 Self::RoleNotFound(_) => "AccessDenied",
110 Self::BackendError(_) => "ServiceUnavailable",
111 Self::BackendAuthError(_) => "BackendAuthenticationFailed",
112 Self::PreconditionFailed => "PreconditionFailed",
113 Self::NotModified => "NotModified",
114 Self::ConfigError(_) => "InternalError",
115 Self::Internal(_) => "InternalError",
116 }
117 }
118
119 pub fn status_code(&self) -> u16 {
121 match self {
122 Self::BucketNotFound(_) | Self::NoSuchKey(_) => 404,
123 Self::AccessDenied | Self::MissingAuth | Self::ExpiredCredentials => 403,
124 Self::SignatureDoesNotMatch => 403,
125 Self::InvalidRequest(_) => 400,
126 Self::MalformedXml(_) => 400,
127 Self::NotImplemented(_) => 501,
128 Self::EntityTooLarge => 400,
129 Self::InvalidOidcToken(_) => 400,
130 Self::RoleNotFound(_) => 403,
131 Self::PreconditionFailed => 412,
132 Self::NotModified => 304,
133 Self::BackendError(_) => 503,
134 Self::BackendAuthError(_) => 502,
135 Self::ConfigError(_) | Self::Internal(_) => 500,
136 }
137 }
138
139 pub fn safe_message(&self) -> String {
146 match self {
147 Self::BackendError(_) => "Service unavailable".to_string(),
148 Self::ConfigError(_) | Self::Internal(_) => "Internal server error".to_string(),
149 Self::BackendAuthError(code) => {
152 format!("Failed to obtain backend credentials: {code}")
153 }
154 other => other.to_string(),
155 }
156 }
157
158 pub fn from_object_store_error(e: object_store::Error) -> Self {
160 match e {
161 object_store::Error::NotFound { path, .. } => Self::NoSuchKey(path),
162 object_store::Error::Precondition { .. } => Self::PreconditionFailed,
163 object_store::Error::NotModified { .. } => Self::NotModified,
164 _ => Self::BackendError(e.to_string()),
165 }
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn backend_auth_error_is_502_and_not_opaque() {
175 let err = ProxyError::BackendAuthError("InvalidIdentityToken".into());
176 assert_eq!(err.status_code(), 502);
177 assert_eq!(err.s3_error_code(), "BackendAuthenticationFailed");
178 let msg = err.safe_message();
181 assert_ne!(msg, "Internal server error");
182 assert!(msg.contains("InvalidIdentityToken"), "got: {msg}");
183 }
184
185 #[test]
186 fn internal_error_stays_opaque_5xx() {
187 let err = ProxyError::Internal("secret backend detail".into());
189 assert_eq!(err.status_code(), 500);
190 assert_eq!(err.safe_message(), "Internal server error");
191 }
192}