cruxi
A transport-agnostic core framework for building hexagonal Rust services.
Purpose
cruxi defines the core ports and building blocks for a 4-layer architecture:
Handler: inbound adapter boundaryService: application orchestrationRepository: domain persistence boundaryProvider: infrastructure boundaryValidator: reusable validation abstractionContext: request-scoped metadata, cancellation, deadlines
┌──────────────────────────────────────┐
│ INBOUND TRANSPORTS │
│ (HTTP, gRPC, MQTT, TCP) │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ HANDLER (Inbound Adapter) │
│ • Receives requests │
│ • Validates transport format │
│ • Delegates to Service │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ SERVICE (Application Layer) │
│ • Business logic orchestration │
│ • Authorization checks │
│ • Coordinates Repositories │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ REPOSITORY (Domain Layer) │
│ • Domain validation │
│ • Transactional integrity │
│ • Calls Providers │
└──────────────────────────────────────┘
↓
┌──────────────────────────────────────┐
│ PROVIDER (Infrastructure) │
│ • Database I/O │
│ • HTTP API calls │
│ • Message queues │
└──────────────────────────────────────┘
Design Principles
Zero external dependencies in the core (only std + thiserror)
Generic type safety via trait generics on Req/Resp
Pattern matching for all control flow and error handling
No .unwrap() - explicit error handling throughout
Features
async - Enables async trait variants (requires async-trait)
Usage
Use function adapters (HandlerFn, ServiceFn, RepositoryFn, ProviderFn, ValidatorFn) for quick composition, or implement traits directly for richer behavior.
Enable the async feature to use async trait variants (AsyncHandler, AsyncService, etc.).
Sync/Async Method Disambiguation
When both sync and async traits are in scope under all-features, use
Universal Function Call Syntax (UFCS) style calls to disambiguate method resolution:
use ;
let handler = new;
let ctx = new;
// Sync call
let sync_result = handle;
assert_eq!;
// Async call
let async_future = handle;
drop;
This avoids multiple applicable items in scope errors and makes call intent explicit.
Context done reasons (short example)
Use done_reason() to make cancellation/timeout handling explicit:
use ;
use Duration;
let mut cancelled = new;
cancelled.cancel_with_reason;
assert_eq!;
let expired = with_timeout;
sleep;
assert_eq!;
Use is_cancelled() when you only want explicit cancellation/shutdown,
not deadline expiry:
use ;
use Duration;
let mut cancelled = new;
cancelled.cancel_with_reason;
assert!;
let expired = with_timeout;
sleep;
assert!;
Use cancellation_handle() when cancellation must be triggered from a shared owner:
use ;
let ctx = new;
let handle = ctx.cancellation_handle;
handle.cancel_with_reason;
assert_eq!;
assert!;
assert!;
assert!;
Deadline propagation (short example)
Use child contexts to propagate the earliest deadline across layers:
use Context;
use Duration;
let parent = with_timeout;
let child = parent.child_with_timeout;
assert!;
assert!;
assert!;
Typed metadata accessors (short example)
Prefer typed metadata helpers for request context consistency:
use ContextBuilder;
let ctx = new
.with_request_id
.with_trace_id
.with_principal
.with_tenant
.with_scopes
.build;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert_eq!;
assert!;
assert!;
let scopes = ctx
.scopes_value
.map
.unwrap_or_default;
assert_eq!;
Typed wrappers also work directly:
with_request_id(RequestId::new(...)), with_tenant(Tenant::new(...)),
with_scopes(Scopes::from_values([...])).
For scope processing, Scopes also exposes:
iter() for borrowed traversal and into_vec() when ownership is needed.
You can also construct it via Scopes::from(vec![...]) or
Scopes::from([\"scope:a\", \"scope:b\"]).
Identity wrappers expose into_inner() when owned String extraction is needed.
Principal and Tenant are intended for authorization and data-isolation
checks in service/repository layers. See runnable example:
cargo run --example context_identity_scoping.
Error mapping contract (short example)
Map transport-agnostic error classes to adapter-specific decisions:
use ;
;
let err = new.with_class;
assert_eq!;
Example
use ;
;
let handler: = new;
let result = handler.handle;
assert_eq!;