awsim_core/lib.rs
1pub mod auth;
2pub mod authz;
3pub mod body;
4pub mod body_store;
5pub mod error;
6pub mod events;
7pub mod gateway;
8pub mod pagination;
9pub mod persistence;
10pub mod protocol;
11pub mod request_detail;
12pub mod request_event;
13pub mod router;
14pub mod state;
15
16pub use authz::{
17 AuthzEngine, GrantLookup, NoopPrincipalLookup, PrincipalLookup, ResolvedPrincipal,
18 ResourcePolicyLookup, ScpLookup,
19};
20// `HandlerByteStream` and `HandlerResult` are defined further down in
21// this file; re-exported here for crate consumers.
22pub use body::Body;
23pub use body_store::{BlobInventory, BodyStore};
24pub use error::AwsError;
25pub use events::{EventBus, InternalEvent};
26pub use gateway::{AppState, BodyStoreHandle};
27pub use pagination::{Page, cap_max_results, decode_token, encode_token, paginate};
28pub use persistence::PersistenceManager;
29pub use protocol::{Protocol, RouteDefinition};
30pub use request_detail::{
31 CapturedBody, CapturedHeader, DEFAULT_BODY_CAP, DEFAULT_RING_CAPACITY, RequestDetail,
32 RequestDetailStore, capture_body, capture_headers,
33};
34pub use request_event::{RequestEvent, RequestEventBus};
35pub use router::RequestContext;
36pub use state::{AccountRegionStore, Snapshottable};
37
38use bytes::Bytes;
39use futures::stream::BoxStream;
40use serde_json::Value;
41
42/// Boxed byte stream a handler may return when it wants to drive an
43/// HTTP response chunk-by-chunk (e.g. Bedrock's event-stream APIs)
44/// instead of buffering the whole response into a single `Value`.
45pub type HandlerByteStream = BoxStream<'static, Result<Bytes, AwsError>>;
46
47/// What `ServiceHandler::handle_streaming` returns. Most operations
48/// produce a single JSON `Value` (the existing path); a small set —
49/// notably Bedrock's `ConverseStream` and
50/// `InvokeModelWithResponseStream` — produce a continuous stream of
51/// already-encoded body bytes plus a content-type the gateway puts
52/// straight on the wire.
53pub enum HandlerResult {
54 /// Conventional single-shot response. The gateway runs it
55 /// through the normal protocol serializer.
56 Json(Value),
57 /// Streamed binary body. The gateway sends it via axum's
58 /// chunked-transfer body so the client sees bytes as they're
59 /// produced — no buffering on our side.
60 Streaming {
61 body: HandlerByteStream,
62 content_type: &'static str,
63 },
64}
65
66impl From<Value> for HandlerResult {
67 fn from(v: Value) -> Self {
68 HandlerResult::Json(v)
69 }
70}
71
72/// Trait that every AWS service crate must implement.
73///
74/// Each service (S3, SQS, DynamoDB, etc.) implements this trait in its own crate.
75/// The main `awsim` binary registers all service handlers with the gateway router.
76#[async_trait::async_trait]
77pub trait ServiceHandler: Send + Sync {
78 /// The AWS service name (e.g., "s3", "sqs", "dynamodb").
79 fn service_name(&self) -> &str;
80
81 /// The signing name used in SigV4 Authorization headers.
82 /// Usually the same as service_name, but not always.
83 fn signing_name(&self) -> &str {
84 self.service_name()
85 }
86
87 /// The primary protocol this service uses.
88 fn protocol(&self) -> Protocol;
89
90 /// Route definitions for REST-protocol services.
91 /// Not needed for RPC-style protocols (awsJson, awsQuery).
92 fn routes(&self) -> Vec<RouteDefinition> {
93 Vec::new()
94 }
95
96 /// Handle an AWS API operation.
97 async fn handle(
98 &self,
99 operation: &str,
100 input: Value,
101 ctx: &RequestContext,
102 ) -> Result<Value, AwsError>;
103
104 /// Streaming-aware variant. The default delegates to `handle` and
105 /// wraps the JSON result so existing services don't need to do
106 /// anything; services that genuinely stream (Bedrock data plane)
107 /// override this and return `HandlerResult::Streaming`.
108 async fn handle_streaming(
109 &self,
110 operation: &str,
111 input: Value,
112 ctx: &RequestContext,
113 ) -> Result<HandlerResult, AwsError> {
114 self.handle(operation, input, ctx)
115 .await
116 .map(HandlerResult::from)
117 }
118
119 /// Serialize the service's state to bytes for persistence.
120 ///
121 /// Return `None` if this service does not support snapshots.
122 fn snapshot(&self) -> Option<Vec<u8>> {
123 None
124 }
125
126 /// Restore the service's state from a previous snapshot.
127 ///
128 /// The default implementation is a no-op and always succeeds.
129 fn restore(&self, _data: &[u8]) -> Result<(), String> {
130 Ok(())
131 }
132
133 fn iam_action(&self, _operation: &str) -> Option<String> {
134 None
135 }
136
137 fn iam_resource(
138 &self,
139 _operation: &str,
140 _input: &serde_json::Value,
141 _ctx: &router::RequestContext,
142 ) -> Option<String> {
143 None
144 }
145
146 /// Periodic tick. The gateway spawns a single 1-second loop that
147 /// calls `tick` on every registered service after the server is up.
148 /// Use this hook for time-driven behavior that doesn't fit into the
149 /// request path: SQS visibility-timeout reclamation, DynamoDB TTL
150 /// expiry, Lambda event-source-mapping polling, S3 lifecycle
151 /// transitions, EventBridge schedule firing, SecretsManager
152 /// rotation, etc.
153 ///
154 /// **Contract:**
155 /// - `tick` must be idempotent — it may be called repeatedly, and
156 /// missing a tick must not lose state. Use absolute deadlines
157 /// (`Instant`/`SystemTime`) rather than per-call deltas.
158 /// - `tick` must return quickly (target <10 ms). Slow work
159 /// (HTTP fan-out, subprocess invocation, large iterations)
160 /// should be enqueued onto an internal worker the service spawns
161 /// from elsewhere — `tick` enqueues, doesn't block.
162 /// - The default implementation is a no-op so existing services
163 /// don't need to opt in.
164 async fn tick(&self) {}
165}