Skip to content

Architecture

Roto is one Rust process with a single HTTP endpoint and a set of in-process AWS service handlers. It uses Axum for HTTP routing, Tokio for asynchronous I/O, and SQLite for persistent resource state. Service handlers use Diesel for typed SQL queries. Large S3 object bodies are stored as files rather than SQLite blobs.

Technology and crate map

Part Technology Role in Roto
HTTP server Axum 0.8, running on Tokio 1 Accepts AWS SDK requests and serves the inspection API. Axum uses Hyper underneath.
Async runtime Tokio Owns the listener and background tasks. Synchronous service and SQLite work runs on Tokio's blocking pool.
Relational storage SQLite, accessed by Diesel 2.3 Stores service resources in a separate database per service. rusqlite remains in the core storage layer for migrations and inspection.
Object storage Local filesystem Stores S3 object bodies; SQLite stores their metadata and paths.
AWS protocol types roto-protocol Shared encoders and decoders for AWS JSON, Query/XML, and REST-XML formats.
Generated bindings roto-codegen and botocore service models Produces operation types, serialization, and route tables checked into service crates.
CLI and logs Clap and tracing Configures the server and reports requests and runtime events.

The crate boundaries follow those responsibilities:

Crate Responsibility
roto-server CLI, Axum router, request dispatch, server-wide state, inspection endpoints, tracing, and service startup.
roto-core The ServiceHandler interface, request context, errors, credential-scope parsing, IDs, and SQLite storage.
roto-protocol Shared protocol parsing and serialization.
roto-codegen Converts botocore service models into Rust service bindings.
roto-svc-* AWS service behavior, schemas, migrations, and service-specific protocol bindings.

The server currently wires handlers for CloudFormation, CloudWatch Logs, DynamoDB, EventBridge, IAM, Kinesis, KMS, Lambda, S3, Secrets Manager, SNS, SQS, SSM, and STS. A shared in-memory WebSocket hub handles IoT topic fan-out and API Gateway management callbacks to connected local clients.

Request path

The server registers explicit Axum routes for its inspection and reset APIs. The router's fallback receives AWS API calls and the local /roto-http/ gateway. For an AWS call, the server reads the HTTP request into the protocol-neutral RawRequest, then selects a handler using the service in the SigV4 credential scope, with the host name as a fallback. Unsigned calls can be claimed by a handler. The parsed credential scope supplies the region; IAM and STS handlers can map issued access keys to an account.

Credential parsing is for routing and account selection. Roto does not verify SigV4 signatures or enforce IAM policies, bucket policies, ACLs, or trust policies.

Each handler implements ServiceHandler: it decodes its wire protocol, applies service behavior, accesses its store, and returns a protocol response. The server runs this synchronous work on Tokio's blocking pool, then converts the raw response into HTTP. Shared response handling applies HTTP status and transport behavior uniformly across AWS services.

flowchart LR
    C[AWS SDK or local client] --> H[Axum on Tokio]
    H --> D[Credential and host dispatch]
    D --> S[ServiceHandler]
    S --> P[Protocol decode and encode]
    S --> DB[(Per-service SQLite)]
    S --> FS[(S3 object files)]
    P --> H
    H --> C
    I[Inspection API] --> H
    I -. reads .-> DB

AWS Query/XML is used by STS, IAM, SNS, and CloudFormation. AWS JSON protocols are used by services including SQS. S3 uses REST-XML routes. Botocore's models/<service>/service-2.json definitions feed the generator; edit the generator and regenerate bindings instead of hand-editing generated.rs files. Service behavior and compatibility details remain ordinary Rust code.

Botocore models and Rust code generation

Botocore is the Python AWS SDK component that knows how each AWS API is shaped on the wire. Its service-2.json model describes a service's identity and protocol, the available operation names, each operation's input and output shapes, and the shapes' members, types, required fields, and wire names. REST models also describe HTTP methods, URI templates, and whether members belong in the URI, query string, headers, or body. For example, the SQS SendMessage operation points to SendMessageRequest and SendMessageResult shapes and has a POST / HTTP binding. Botocore uses this metadata to construct SDK clients and serialize their calls; the model describes the API contract, not AWS's business logic or stored state.

Roto keeps model files under models/ and reads them with its Rust roto-codegen program. The generator follows operation references into their input and output shapes, maps supported shape types to Rust types, and emits a generated.rs module in the corresponding service crate. Depending on the declared protocol, that module contains request and response structs, serialization and parsing code, operation dispatch, and the service trait that the handwritten handler implements. REST-JSON and REST-XML generation also build route tables from the model's HTTP bindings. Protocol helpers live in roto-protocol and are called by the generated code.

Regenerate every service's checked-in bindings after changing a model or the generator:

scripts/gen.sh

That script runs roto-codegen once for each service model and formats the result. The server compiles the checked-in Rust modules; it does not need to load the model JSON or run Python botocore at runtime.

The generated OPERATIONS table reports whether the generator can represent an operation's request and response shapes. A true entry means its codec and dispatcher were generated; it does not mean Roto has implemented the AWS operation's behavior. The generated service trait provides default NotImplemented methods, and service crates add behavior in hand-written Rust. Some model features are outside the generator's supported shape subset and cause the corresponding generated operation to remain unsupported. API model updates can therefore add types and routes automatically, while behavioral compatibility still requires service code and tests.

State and transactions

Persistent mode creates a {service}.db SQLite file for each service in the data directory. Each service applies its ordered SQL migrations when its store is opened. SQLite runs in WAL mode with foreign keys enabled; normal mode uses synchronous=NORMAL, while --durable switches to FULL. The current store serializes access to each service database rather than using a connection pool. --ephemeral uses in-memory SQLite databases and temporary directories for object bodies.

Service handlers use Diesel's typed ORM/query API and SQLite connection. The core storage layer also uses rusqlite to apply migrations and to support the inspection API's existing read path. The two connections for a service share the storage lock, so inspection and service transactions are serialized.

S3 object bodies live under the data directory in persistent mode; SQLite holds the bucket, object-version, upload, and file-path metadata. This keeps large payloads out of the relational database. Restarting a persistent server preserves both the metadata and object files.

Each service operation generally commits or rolls back its own SQLite transaction. Cross-service workflows compose existing handlers in-process: for example, CloudFormation creates resources by calling their service handlers, and SNS/EventBridge/S3 notifications can dispatch to queues or Lambda. Those workflows are not a distributed transaction. CloudFormation stack metadata is stored separately from resource state, so a multi-resource update can partially apply; rollback is a best-effort reconciliation and cannot restore resource data that was deleted.

Background work and inspection

The Tokio runtime also starts background workers for Lambda invocations, EventBridge deliveries, and S3 notification retries. These workers call service logic in-process and use the same persistent stores as API requests.

The /roto-api/ inspection UI and its JSON endpoints share the server and stores but are not AWS APIs. They expose resource records, request history, unsupported calls, and selected delivery or invocation history. Request traces record metadata such as operation, account, region, result, and duration; they omit credentials, headers, query strings, and request/response bodies.

See Server reference for listener options and inspection endpoints, Storage for persistence details, and Service coverage for the current implementation baseline.