1use async_trait::async_trait;
2use bytes::Bytes;
3use http::{HeaderMap, Method, StatusCode};
4use std::collections::HashMap;
5
6use crate::auth::Principal;
7
8#[derive(Debug)]
10pub struct AwsRequest {
11 pub service: String,
12 pub action: String,
13 pub region: String,
14 pub account_id: String,
15 pub request_id: String,
16 pub headers: HeaderMap,
17 pub query_params: HashMap<String, String>,
18 pub body: Bytes,
19 pub path_segments: Vec<String>,
20 pub raw_path: String,
22 pub raw_query: String,
24 pub method: Method,
25 pub is_query_protocol: bool,
27 pub access_key_id: Option<String>,
29 pub principal: Option<Principal>,
36}
37
38impl AwsRequest {
39 pub fn json_body(&self) -> serde_json::Value {
41 serde_json::from_slice(&self.body).unwrap_or(serde_json::Value::Null)
42 }
43}
44
45#[derive(Debug)]
54pub enum ResponseBody {
55 Bytes(Bytes),
56 File { file: tokio::fs::File, size: u64 },
57}
58
59impl ResponseBody {
60 pub fn len(&self) -> u64 {
61 match self {
62 ResponseBody::Bytes(b) => b.len() as u64,
63 ResponseBody::File { size, .. } => *size,
64 }
65 }
66
67 pub fn is_empty(&self) -> bool {
68 self.len() == 0
69 }
70
71 pub fn expect_bytes(&self) -> &[u8] {
75 match self {
76 ResponseBody::Bytes(b) => b,
77 ResponseBody::File { .. } => {
78 panic!("expect_bytes called on ResponseBody::File")
79 }
80 }
81 }
82}
83
84impl Default for ResponseBody {
85 fn default() -> Self {
86 ResponseBody::Bytes(Bytes::new())
87 }
88}
89
90impl From<Bytes> for ResponseBody {
91 fn from(b: Bytes) -> Self {
92 ResponseBody::Bytes(b)
93 }
94}
95
96impl From<Vec<u8>> for ResponseBody {
97 fn from(v: Vec<u8>) -> Self {
98 ResponseBody::Bytes(Bytes::from(v))
99 }
100}
101
102impl From<&'static [u8]> for ResponseBody {
103 fn from(s: &'static [u8]) -> Self {
104 ResponseBody::Bytes(Bytes::from_static(s))
105 }
106}
107
108impl From<String> for ResponseBody {
109 fn from(s: String) -> Self {
110 ResponseBody::Bytes(Bytes::from(s))
111 }
112}
113
114impl From<&'static str> for ResponseBody {
115 fn from(s: &'static str) -> Self {
116 ResponseBody::Bytes(Bytes::from_static(s.as_bytes()))
117 }
118}
119
120impl PartialEq<Bytes> for ResponseBody {
121 fn eq(&self, other: &Bytes) -> bool {
122 match self {
123 ResponseBody::Bytes(b) => b == other,
124 ResponseBody::File { .. } => false,
125 }
126 }
127}
128
129pub struct AwsResponse {
131 pub status: StatusCode,
132 pub content_type: String,
133 pub body: ResponseBody,
134 pub headers: HeaderMap,
135}
136
137impl AwsResponse {
138 pub fn xml(status: StatusCode, body: impl Into<Bytes>) -> Self {
139 Self {
140 status,
141 content_type: "text/xml".to_string(),
142 body: ResponseBody::Bytes(body.into()),
143 headers: HeaderMap::new(),
144 }
145 }
146
147 pub fn json(status: StatusCode, body: impl Into<Bytes>) -> Self {
148 Self {
149 status,
150 content_type: "application/x-amz-json-1.1".to_string(),
151 body: ResponseBody::Bytes(body.into()),
152 headers: HeaderMap::new(),
153 }
154 }
155
156 pub fn ok_json(value: serde_json::Value) -> Self {
158 Self::json(StatusCode::OK, serde_json::to_vec(&value).unwrap())
159 }
160}
161
162#[derive(Debug, thiserror::Error)]
164pub enum AwsServiceError {
165 #[error("service not found: {service}")]
166 ServiceNotFound { service: String },
167
168 #[error("action {action} not implemented for service {service}")]
169 ActionNotImplemented { service: String, action: String },
170
171 #[error("{code}: {message}")]
172 AwsError {
173 status: StatusCode,
174 code: String,
175 message: String,
176 extra_fields: Vec<(String, String)>,
178 headers: Vec<(String, String)>,
180 },
181}
182
183impl AwsServiceError {
184 pub fn action_not_implemented(service: &str, action: &str) -> Self {
185 Self::ActionNotImplemented {
186 service: service.to_string(),
187 action: action.to_string(),
188 }
189 }
190
191 pub fn aws_error(
192 status: StatusCode,
193 code: impl Into<String>,
194 message: impl Into<String>,
195 ) -> Self {
196 Self::AwsError {
197 status,
198 code: code.into(),
199 message: message.into(),
200 extra_fields: Vec::new(),
201 headers: Vec::new(),
202 }
203 }
204
205 pub fn aws_error_with_fields(
206 status: StatusCode,
207 code: impl Into<String>,
208 message: impl Into<String>,
209 extra_fields: Vec<(String, String)>,
210 ) -> Self {
211 Self::AwsError {
212 status,
213 code: code.into(),
214 message: message.into(),
215 extra_fields,
216 headers: Vec::new(),
217 }
218 }
219
220 pub fn aws_error_with_headers(
221 status: StatusCode,
222 code: impl Into<String>,
223 message: impl Into<String>,
224 headers: Vec<(String, String)>,
225 ) -> Self {
226 Self::AwsError {
227 status,
228 code: code.into(),
229 message: message.into(),
230 extra_fields: Vec::new(),
231 headers,
232 }
233 }
234
235 pub fn extra_fields(&self) -> &[(String, String)] {
236 match self {
237 Self::AwsError { extra_fields, .. } => extra_fields,
238 _ => &[],
239 }
240 }
241
242 pub fn status(&self) -> StatusCode {
243 match self {
244 Self::ServiceNotFound { .. } => StatusCode::BAD_REQUEST,
245 Self::ActionNotImplemented { .. } => StatusCode::NOT_IMPLEMENTED,
246 Self::AwsError { status, .. } => *status,
247 }
248 }
249
250 pub fn code(&self) -> &str {
251 match self {
252 Self::ServiceNotFound { .. } => "UnknownService",
253 Self::ActionNotImplemented { .. } => "InvalidAction",
254 Self::AwsError { code, .. } => code,
255 }
256 }
257
258 pub fn message(&self) -> String {
259 match self {
260 Self::ServiceNotFound { service } => format!("service not found: {service}"),
261 Self::ActionNotImplemented { service, action } => {
262 format!("action {action} not implemented for service {service}")
263 }
264 Self::AwsError { message, .. } => message.clone(),
265 }
266 }
267
268 pub fn response_headers(&self) -> &[(String, String)] {
269 match self {
270 Self::AwsError { headers, .. } => headers,
271 _ => &[],
272 }
273 }
274}
275
276#[async_trait]
278pub trait AwsService: Send + Sync {
279 fn service_name(&self) -> &str;
281
282 async fn handle(&self, request: AwsRequest) -> Result<AwsResponse, AwsServiceError>;
284
285 fn supported_actions(&self) -> &[&str];
287
288 fn iam_enforceable(&self) -> bool {
303 false
304 }
305
306 fn iam_action_for(&self, _request: &AwsRequest) -> Option<crate::auth::IamAction> {
319 None
320 }
321}