Thank you for building truss with us. Every report, patch, test, and review directly improves the image toolkit for Rust developers. Let's keep truss fast, safe, and reliable together.
- Rust stable toolchain (
rustup default stable) - just task runner (
cargo install just) - Docker (for integration tests and container builds)
Optional:
cargo-auditfor security checks (cargo install cargo-audit)cargo-llvm-cov+llvm-tools-previewfor coverage (just setupinstalls these)wasm32-unknown-unknowntarget andwasm-bindgen-clifor WASM builds- Node.js 22.x + npm for the official npm packages, consumer smoke tests, and example app (
.nvmrcmatches CI)
Run just setup to install the optional development tools.
git clone https://github.com/nao1215/truss.git
cd truss
just test # Run unit tests and doc tests
just lint # Run clippy
just fmt-check # Check formatting- Bug report: Use the issue template and include reproducible steps, expected behavior, and actual behavior.
- New feature: Open an issue first so we can agree on direction before implementation.
- Bug fix or improvement: Open a PR with a clear problem statement and solution summary.
- Add or update unit tests when you add features or fix bugs.
- Add doc tests for new or changed public APIs.
- Keep CLI behavior and error messages clear and consistent.
- Follow the structured error format:
error:,usage:,hint:.
The fastest way is just ci, which runs all checks at once:
just ciThis is equivalent to:
just test # cargo test --all-targets && cargo test --doc
just lint # cargo clippy --all-targets -- -D warnings
just fmt-check # cargo fmt --all -- --check
just doc # RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
just audit # cargo auditIf your change touches an official npm package or browser-facing integration, also run:
just url-signer-package-typecheck # Compile-check the package TypeScript definitions
just url-signer-package-test # Run the Node.js package unit tests
just url-signer-package-pack # Verify the URL signer tarball can be assembled
just wasm-package-pack # Verify the npm tarball can be assembled
just wasm-package-consumer-smoke # Install the tarball into a throwaway app and run one transform
just wasm-vite-example-smoke # Build the Vite example against the local tarball
just wasm-vite-example-runtime-smoke # Verify the checked-in Vite example in headless ChromeIntegration tests use Docker and are separate from cargo test:
just integration-cli # CLI tests with ShellSpec
just integration-api # API server tests with runn
just integration # Both- CLI tests (
integration/cli/): Written in ShellSpec. These specs also serve as user-facing CLI documentation. - API tests (
integration/api/): Written as runn runbooks. These runbooks also serve as API documentation. - Test fixtures: Shared images in
integration/fixtures/.
- Follow standard Rust conventions and idioms.
- Write documentation comments (
///) for public APIs. - Write comments in English.
- Keep
unsafecode to an absolute minimum and document the safety invariants. - GIF support is out of scope; do not add or reintroduce it.
src/
├── main.rs # Entry point
├── lib.rs # Public API exports
├── core.rs # Core types, validation, media sniffing
├── adapters/
│ ├── cli/ # CLI parser and command execution
│ │ ├── mod.rs # Routing and shared utilities
│ │ ├── convert.rs # convert subcommand
│ │ ├── inspect.rs # inspect subcommand
│ │ ├── serve.rs # serve subcommand
│ │ └── sign.rs # sign / completions / validate
│ ├── server/ # HTTP server
│ │ ├── mod.rs # Orchestrator and public API
│ │ ├── routing.rs # Route dispatch
│ │ ├── handler.rs # Request handlers
│ │ ├── lifecycle.rs # Server startup, shutdown, draining
│ │ ├── auth.rs # HMAC signing and bearer tokens
│ │ ├── cache.rs # Transform and origin caches
│ │ ├── config.rs # ServerConfig and env parsing
│ │ ├── http_parse.rs # HTTP request parsing
│ │ ├── metrics.rs # Prometheus metrics
│ │ ├── multipart.rs # Multipart form parsing
│ │ ├── negotiate.rs # Content negotiation (Accept)
│ │ ├── remote.rs # Remote URL fetching (SSRF protection)
│ │ ├── response.rs # Response builders (RFC 7807)
│ │ ├── s3.rs # AWS S3 backend
│ │ ├── gcs.rs # Google Cloud Storage backend
│ │ └── azure.rs # Azure Blob Storage backend
│ └── wasm.rs # Browser WASM adapter
└── codecs/
├── raster.rs # JPEG, PNG, WebP, AVIF, BMP codec
└── svg.rs # SVG sanitization and rasterization
integration/
├── fixtures/ # Shared test images
├── cli/ # ShellSpec CLI specs
│ ├── Dockerfile
│ └── spec/*.sh
└── api/ # runn API runbooks
├── compose.yml
└── runbooks/*.yml
docs/ # Design documents, API specs, and guides
examples/ # Runnable consumer examples (for example Vite + @nao1215/truss-wasm)
packages/truss-wasm/ # Official npm package published as @nao1215/truss-wasm
packages/truss-url-signer/ # Official npm package published as @nao1215/truss-url-signer
truss follows a three-layer architecture:
┌──────────────────────────────────────────────┐
│ Adapters (CLI / HTTP Server / WASM) │ Runtime-specific I/O
├──────────────────────────────────────────────┤
│ Core (core.rs) │ Types, validation, media sniffing
├──────────────────────────────────────────────┤
│ Codecs (raster.rs / svg.rs) │ Image decode, transform, encode
└──────────────────────────────────────────────┘
- Core defines domain types (
Artifact,TransformOptions,MediaType, etc.) and all validation logic. It has no I/O dependencies. - Codecs perform the actual image processing.
transform_raster()is the main entry point. SVG input is handled separately bytransform_svg(). - Adapters translate external interfaces into core operations:
- CLI parses command-line arguments and drives transforms via stdin/stdout/files.
- Server provides an HTTP API with signed URLs, caching, content negotiation, and cloud storage backends. It uses synchronous I/O by design for simplicity and predictable resource usage.
- WASM exposes a browser-friendly JS API via
wasm-bindgen.
Key design decisions:
- Synchronous I/O (server): The HTTP server uses one thread per connection with blocking I/O. This avoids async complexity and gives predictable memory usage.
MAX_CONCURRENT_TRANSFORMS(configurable viaTRUSS_MAX_CONCURRENT_TRANSFORMS) bounds resource consumption. - DNS pinning (remote fetch): Remote URL fetching resolves DNS once, then pins the connection to validated IPs. This prevents SSRF via DNS rebinding attacks.
- Sharded cache layout: The transform cache stores files under
<root>/ab/cd/ef/<sha256>to avoid inode exhaustion on large caches. Writes use atomic temp-file-then-rename.
How to add a new transform operation:
- Add the option field to
TransformOptionsincore.rs - Add normalization logic in
TransformOptions::normalize() - Implement the operation in
transform_raster()incodecs/raster.rs - Add CLI flag parsing in
cli/convert.rs - Add HTTP query parameter parsing in
server/http_parse.rs - Update the OpenAPI spec in
docs/openapi.yaml
How to add a new storage backend:
- Create
src/adapters/server/<backend>.rs - Add the feature flag to
Cargo.toml - Add the variant to
StorageBackendenum inconfig.rs - Add configuration parsing in
config.rs - Add the
resolve_storage_source_bytesmatch arm inremote.rs
Run just with no arguments to see the full list. Key recipes:
| Recipe | Description |
|---|---|
just test |
Unit + integration + doc tests |
just test-unit |
Unit tests only (fast) |
just lint |
Clippy with strict warnings |
just fmt |
Format all code |
just ci |
All CI checks locally |
just coverage |
Code coverage summary |
just integration |
Docker-based CLI + API tests |
just url-signer-package-typecheck |
Compile-check the TypeScript URL signer package definitions |
just url-signer-package-test |
Run the TypeScript URL signer package tests |
just url-signer-package-pack |
Pack the TypeScript URL signer package without publishing |
just wasm-package-build |
Build the official npm package bindings |
just wasm-package-pack |
Pack the npm package without publishing |
just wasm-package-consumer-smoke |
Install the local tarball into a throwaway consumer and run one transform |
just wasm-vite-example-smoke |
Build the Vite example against the local tarball |
just wasm-vite-example-runtime-smoke |
Serve the checked-in Vite example and verify runtime output in headless Chrome |
just serve |
Start dev server |
just docker-build |
Build Docker image |
For browser integration work, start from examples/vite-truss-wasm/ to verify a real consumer can import @nao1215/truss-wasm with a modern bundler.
The CLI uses structured exit codes. When changing CLI error handling, keep these consistent:
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Usage error (bad arguments) |
| 2 | I/O error (file not found, network failure) |
| 3 | Input error (unsupported format, corrupt file) |
| 4 | Transform error (encode failure, size limit) |
The first GHCR package publish is private by default. To allow anonymous pulls (e.g. from ECS), change the package visibility to Public in GitHub Packages settings once after the first publish.
You can still make a huge impact even if you are not writing code:
- Give truss a GitHub Star
- Share truss with your team and community
- Open issues with clear reproduction steps
- Sponsor the project