Rullst Core ⚙️
rullst-core encapsulates the foundational primitives, routing engines, state management, and configuration layers of the Rullst Framework. It acts as the beating heart that orchestrates HTTP handlers, middleware, and backend worker systems.
✨ Features
- Typed Axum Integration: Wraps Axum routing while retaining access to the underlying router and middleware ecosystem.
- Bounded Server Functions:
#[server_function]shares the versionedrullst.clientv1 envelope between native Axum routes and Wasm callers, with a 256 KiB body ceiling, correlation checks, same-origin paths, CSRF header forwarding, and message-free typed failures. Identity and domain policy stay server-owned. - Runtime Telemetry: Exposes process/runtime snapshots and tracing-span collection for consumers such as Rullst Studio.
- Lifecycle-aware Readiness: An opt-in process lifecycle gates new requests
during startup, dependency unavailability, and graceful drain. It accepts at
most 32 immutable component labels and exposes counts—not labels or errors—on
/ready; dependency checks and multi-replica coordination remain host work. - Bounded Rate Limiting:
RateLimiterkeys IPv4 peers per address and IPv6 peers per /64, and bounds its process-local bucket map to 100,000 keys by dropping refilled buckets and evicting the least recently used ones.Server's limiter and Traffic Shield exempt exactGET/HEAD /healthand/readyprobes. - Typed Failures: Server, scheduler, queue, storage, and resilience APIs expose structured errors for fallible paths. The repository's zero-panic policy is CI-scoped, not an absolute runtime guarantee.
- Dependency Injection: Type-safe, intuitive global state management across routes and background workers.
- Environment Management: Native
dotenvand TOML configuration loaders for different deployment targets (Staging, Production, Local).Serverand the Artisandb:*/studiocommands resolve the database the same way: processDATABASE_URL, then./.env(never overriding the process), then[database].url. Database commands without a configured database fail instead of creating a SQLite file. - Local Edge Emulator:
edge::EdgeServer::runbinds127.0.0.1unlessHOST/RULLST_HOSTis set, serves every path including/, and rejects a body over 2 MiB with413(or an unreadable one with400) instead of passing the handler an empty body. It applies no security baseline. - Durable Scheduled Queues: SQLite and Redis persist bounded
dispatch_attimestamps and never claim a job before its millisecond due time. Delivery is poll-dependent and at-least-once. - Fenced Queue Leases: SQLite and Redis complete, fail or requeue a claimed
job only under the attempt number
popreturned, so a stale worker whose lease was recovered and claimed again cannot finish the newer claim. - Queue-wide Stalled-lease Recovery: each worker periodically requeues
every processing lease older than its
stalled_after, including other workers' leases, so every worker sharing a queue needs astalled_afterlonger than the longestjob_timeoutamong them. SQLite and Redis fail a job whose fifth lease stalls (for example because it keeps crashing its worker) instead of requeuing it forever. - Rolling-deploy Safe Dispatch: A worker without a handler for a job's name hands the claim back with a five-second delay (SQLite and Redis) instead of failing it, so a worker that registered that name can run it.
- Bounded Redis Failure State: Failed jobs and dead letters are each
retained up to 10,000 entries (configurable with
RedisDriver::try_with_failure_retention), evicting the oldest atomically. - Swappable HTMX Validation Errors:
ValidatedForm/ValidatedJsonsend HTMX requests their error fragment with200 OKplusX-Rullst-Validation-Status: 400|422; other clients keep400/422JSON. - Cron Semantics:
Scheduler::taskevaluates five-field expressions in UTC with thecroncrate's rules: weekdays 1 (Sunday) to 7 (Saturday), 0 rejected, and restricted day fields must both match. Prefer weekday names. - Bounded Background Errors:
WorkerHandleandSchedulerHandlebuffer at most 256 undrained errors; overflow is dropped, counted bydropped_errors()and logged as atracingwarning. Drainnext_errorto observe every failure.Server::scheduledrains its own handle, logging each task failure; only a failed scheduler loop makesServer::runreturn an error. - Explicit Completion History: SQLite deletes successful payloads by
default.
Queue::sqlite_with_completed_historyopts into a bounded retained history for Studio/operations, with atomic pruning and an explicit purge API. - Atomic Local Writes:
Storage::local/LocalDriver::putwrites a unique temporary file beside the object, flushes it and renames it over the key. Readers and concurrent writers see one complete version, and a failed write leaves the previous object intact. Each put creates a new file with default permissions; the directory is not fsynced, so a power loss can roll a completed put back to the previous version. - Encoded Object URLs:
Storage::url/LocalDriver::urlpercent-encode each key segment. Local storage always returns/storage/<key>(serve the base directory there), never the filesystem base path. - Metadata-only Cache Inspection: Memory and Redis drivers can return a sorted snapshot of at most 200 logical keys, UTF-8 value lengths and TTLs without returning values. Custom drivers fail explicitly unless they opt in; authorization and safe rendering of logical keys remain caller policy.
- Offline Sync Foundation: The optional native
offline-syncfeature provides bounded replay-safe mutations, explicit conflict resolution, and account-bound AES-256-GCM snapshots. Its static-dispatch coordinator bounds requests, times them out, and detects stalled cursors. Applications still own Keychain/Keystore access, atomic platform persistence, authenticated HTTP, retry, and background scheduling.
🚀 Usage
Most developers will not depend on rullst-core directly, as it is re-exported seamlessly through the primary rullst crate.
If you are developing a plugin or advanced middleware for the Rullst ecosystem, you can add it explicitly:
Install the exact stable train with
cargo add rullst-core@12.2.0.
Core is runtime-only by default. Add just the database capabilities the application needs:
[]
= { = "12.1.0", = ["orm", "queue-sqlite", "offline-sync"] }
Enable orm, queue-sqlite, queue-redis, offline-sync, or telemetry only
when that integration is required. The primary rullst crate keeps orm and
queue-sqlite in its default feature set for application compatibility.
Both built-in queue drivers implement Queue::dispatch_at for schedules up to
366 days ahead. SQLite performs an automatic additive schema migration; Redis
uses a sorted set and server time, with a digest-pinned live CI contract. Custom
drivers fail with QueueError::Unsupported for future jobs until they implement
the scheduling method explicitly.
Successful SQLite jobs are removed unless the application explicitly calls
Queue::sqlite_with_completed_history(database_url, retained_jobs). The limit
must be between 1 and 100,000; completion and pruning share one transaction.
Retained rows include the original payload, so the application must restrict
Studio/inspection access and choose an appropriate retention policy. Use
Queue::purge_completed_history to remove them.
Cache::inspect(limit) is opt-in diagnostic data, not an administration
endpoint. The limit must be 1–200. CacheEntryMetadata deliberately redacts
the logical key from Debug and never carries the cached value, but
logical_key() still returns application data to an authorized caller. Rullst
Studio converts it into a process-bound opaque token before rendering it.
Cache::memory() stores a TTL too large for the monotonic clock (such as
u64::MAX) as non-expiring instead of panicking.
For orchestrated deployments, construct ApplicationLifecycle, mount
health_router_with_lifecycle(lifecycle.clone()), then pass the same value to
Server::with_lifecycle. The server marks startup complete after binding,
begins drain before Axum waits for accepted requests, and marks startup failure
or termination as stopped. run_with_shutdown permits a caller-owned trigger.
Admission follows the ordinary response body through completion, error or drop,
including streamed data/trailers. It does not prove client receipt or track
upgraded connections. wait_for_drain is bounded; supervisors separately own
process kill deadlines and application WebSocket/background-task termination.
The process-local registry does not probe dependencies, authorize application
requests, coordinate replicas, or guarantee load-balancer propagation.
Minimal HTTP Server
use ;
use get;
async
🔐 Security Audit
Repository workflows exercise Core with unit, integration, fuzz, and Miri jobs within their declared scopes. Consult the exact workflow run and commit for evidence; these tools do not prove the absence of every panic, leak, or vulnerability.
📚 Documentation
For an architectural deep-dive into Rullst Core's event loop and middleware lifecycle, please visit the Rullst Book.