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 method: Method,
21 pub is_query_protocol: bool,
23 pub access_key_id: Option<String>,
25}
26
27pub struct AwsResponse {
29 pub status: StatusCode,
30 pub content_type: String,
31 pub body: Bytes,
32 pub headers: HeaderMap,
33}
34
35impl AwsResponse {
36 pub fn xml(status: StatusCode, body: impl Into<Bytes>) -> Self {
37 Self {
38 status,
39 content_type: "text/xml".to_string(),
40 body: body.into(),
41 headers: HeaderMap::new(),
42 }
43 }
44
45 pub fn json(status: StatusCode, body: impl Into<Bytes>) -> Self {
46 Self {
47 status,
48 content_type: "application/x-amz-json-1.1".to_string(),
49 body: body.into(),
50 headers: HeaderMap::new(),
51 }
52 }
53}
54
55#[derive(Debug, thiserror::Error)]
57pub enum AwsServiceError {
58 #[error("service not found: {service}")]
59 ServiceNotFound { service: String },
60
61 #[error("action {action} not implemented for service {service}")]
62 ActionNotImplemented { service: String, action: String },
63
64 #[error("{code}: {message}")]
65 AwsError {
66 status: StatusCode,
67 code: String,
68 message: String,
69 extra_fields: Vec<(String, String)>,
71 headers: Vec<(String, String)>,
73 },
74}
75
76impl AwsServiceError {
77 pub fn action_not_implemented(service: &str, action: &str) -> Self {
78 Self::ActionNotImplemented {
79 service: service.to_string(),
80 action: action.to_string(),
81 }
82 }
83
84 pub fn aws_error(
85 status: StatusCode,
86 code: impl Into<String>,
87 message: impl Into<String>,
88 ) -> Self {
89 Self::AwsError {
90 status,
91 code: code.into(),
92 message: message.into(),
93 extra_fields: Vec::new(),
94 headers: Vec::new(),
95 }
96 }
97
98 pub fn aws_error_with_fields(
99 status: StatusCode,
100 code: impl Into<String>,
101 message: impl Into<String>,
102 extra_fields: Vec<(String, String)>,
103 ) -> Self {
104 Self::AwsError {
105 status,
106 code: code.into(),
107 message: message.into(),
108 extra_fields,
109 headers: Vec::new(),
110 }
111 }
112
113 pub fn aws_error_with_headers(
114 status: StatusCode,
115 code: impl Into<String>,
116 message: impl Into<String>,
117 headers: Vec<(String, String)>,
118 ) -> Self {
119 Self::AwsError {
120 status,
121 code: code.into(),
122 message: message.into(),
123 extra_fields: Vec::new(),
124 headers,
125 }
126 }
127
128 pub fn extra_fields(&self) -> &[(String, String)] {
129 match self {
130 Self::AwsError { extra_fields, .. } => extra_fields,
131 _ => &[],
132 }
133 }
134
135 pub fn status(&self) -> StatusCode {
136 match self {
137 Self::ServiceNotFound { .. } => StatusCode::BAD_REQUEST,
138 Self::ActionNotImplemented { .. } => StatusCode::NOT_IMPLEMENTED,
139 Self::AwsError { status, .. } => *status,
140 }
141 }
142
143 pub fn code(&self) -> &str {
144 match self {
145 Self::ServiceNotFound { .. } => "UnknownService",
146 Self::ActionNotImplemented { .. } => "InvalidAction",
147 Self::AwsError { code, .. } => code,
148 }
149 }
150
151 pub fn message(&self) -> String {
152 match self {
153 Self::ServiceNotFound { service } => format!("service not found: {service}"),
154 Self::ActionNotImplemented { service, action } => {
155 format!("action {action} not implemented for service {service}")
156 }
157 Self::AwsError { message, .. } => message.clone(),
158 }
159 }
160
161 pub fn response_headers(&self) -> &[(String, String)] {
162 match self {
163 Self::AwsError { headers, .. } => headers,
164 _ => &[],
165 }
166 }
167}
168
169#[async_trait]
171pub trait AwsService: Send + Sync {
172 fn service_name(&self) -> &str;
174
175 async fn handle(&self, request: AwsRequest) -> Result<AwsResponse, AwsServiceError>;
177
178 fn supported_actions(&self) -> &[&str];
180}