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