Skip to main content

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}