Skip to main content

fakecloud_core/
service.rs

1use async_trait::async_trait;
2use bytes::Bytes;
3use http::{HeaderMap, Method, StatusCode};
4use std::collections::HashMap;
5
6/// A parsed AWS request.
7#[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    /// The raw URI path, before splitting into segments.
19    pub raw_path: String,
20    pub method: Method,
21    /// Whether this request came via Query (form-encoded) or JSON protocol.
22    pub is_query_protocol: bool,
23    /// The access key ID from the SigV4 Authorization header, if present.
24    pub access_key_id: Option<String>,
25}
26
27/// A response from a service handler.
28pub 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/// Error returned by service handlers.
56#[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        /// Additional key-value pairs to include in the error XML (e.g., BucketName, Key, Condition).
70        extra_fields: Vec<(String, String)>,
71        /// Additional HTTP headers to include in the error response.
72        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/// Trait that every AWS service implements.
170#[async_trait]
171pub trait AwsService: Send + Sync {
172    /// The AWS service identifier (e.g., "sqs", "sns", "sts", "events", "ssm").
173    fn service_name(&self) -> &str;
174
175    /// Handle an incoming request.
176    async fn handle(&self, request: AwsRequest) -> Result<AwsResponse, AwsServiceError>;
177
178    /// List of actions this service supports (for introspection).
179    fn supported_actions(&self) -> &[&str];
180}