#Bus Access Patterns - Decision Guide
Version: 0.51.0 Updated: 2026-07-16 Applies to: ranvier-core Category: Guides
#Overview
Bus is Ranvier's per-execution, type-indexed runtime context. The requested
Rust type is checked by the compiler, but presence and authorization are runtime
properties. Mandatory application dependencies should use
Transition::Resources; an adapter may inject request context such as auth or
tenant identity into a fresh Bus. This guide selects the appropriate runtime
access method.
#Decision Tree
graph TD
Q{"Must the resource\nexist in the Bus?"}
Q -->|"YES: guaranteed\nby prior step"| REQ["require::<T>()\nReturns: &T\nOn missing: panic"]
Q -->|"MAYBE: can\nproceed without"| READ["read::<T>()\nReturns: Option<&T>\nOn missing: None"]
Q -->|"YES: must return\nerror if missing"| GET["get::<T>()\nReturns: Result<&T, BusAccessError>\nDefault for production"]#Method Comparison
| Method | Return Type | On Missing | Use When |
|---|---|---|---|
require::<T>() |
&T |
panic | A tested framework invariant guarantees insertion and policy access |
read::<T>() |
Option<&T> |
None |
Optional resource -- can proceed without it |
get::<T>() |
Result<&T, BusAccessError> |
Err(NotFound) |
Production code that needs an error message |
try_require::<T>() |
Option<&T> |
None |
Semantic alias for read() |
#Mutable Variants
| Method | Return Type | On Missing |
|---|---|---|
read_mut::<T>() |
Option<&mut T> |
None |
get_mut::<T>() |
Result<&mut T, BusAccessError> |
Err(NotFound) |
#Usage Examples
#`require()` - Guaranteed by Framework
#[transition]
async fn use_runtime_clock(
state: Job,
_resources: &(),
bus: &mut Bus,
) -> Outcome<Request, AppError> {
// The application harness inserts Clock in every execution and tests that
// invariant. User configuration and request auth should use get() instead.
let clock = bus.require::<Clock>();
clock.record_start();
Outcome::Next(state)
}#`read()` - Optional Enhancement
#[transition]
async fn filter_by_tenant(
state: Vec<Item>,
_resources: &(),
bus: &mut Bus,
) -> Outcome<Vec<Item>, AppError> {
// TenantId may or may not be present
// If absent, return all items (no filtering)
let filtered = match bus.get_cloned::<TenantId>() {
Ok(tenant) => state.into_iter()
.filter(|item| item.tenant_id == tenant.0)
.collect(),
Err(_) => state,
};
Outcome::Next(filtered)
}#`get()` - Production Default
#[transition]
async fn load_config(
state: Request,
_resources: &(),
bus: &mut Bus,
) -> Outcome<Request, AppError> {
// AppConfig should be in the Bus, but we want a clear error if missing
let config = match bus.get::<AppConfig>() {
Ok(config) => config,
Err(error) => {
return Outcome::Fault(AppError::Internal(format!(
"Config unavailable: {error}"
)));
}
};
// Use config...
Outcome::Next(state)
}#Inserting Resources
Resources are typically inserted during application setup:
let mut bus = Bus::new();
// Database pool
bus.insert(db_pool);
// Application config
bus.insert(app_config);
// Request context (insert only into this execution Bus)
bus.insert(auth_context);#Helper Methods
| Method | Description |
|---|---|
insert::<T>(value) |
Insert or replace a resource by type |
provide::<T>(value) |
Semantic alias for insert |
remove::<T>() |
Remove and return a resource (Option<T>) |
has::<T>() |
Check if a resource exists (bool) |
#Choosing the Right Pattern
#Simple Rule
- Framework guarantees it? Use
require() - Optional feature? Use
read() - Everything else? Use
get()
#Anti-Patterns
| Pattern | Problem | Fix |
|---|---|---|
require() for user-provided config |
Panics in production if config missing | Use get() with proper error message |
read() silently ignoring missing deps |
Bugs go unnoticed | Use get() and handle the error |
Multiple require() calls for same type |
Redundant lookups | Read once, store in a local variable |
#Bus Access Policy (Advanced)
bus_allow / bus_deny attributes declare which types a Transition may access.
The macro validates the declared type list at compile time. Actual Bus lookup is
runtime-enforced because the macro does not inspect every expression in the
transition body.
#[transition(bus_allow = [AuthContext, AppConfig])]
async fn handle(
input: Request,
bus: &mut Bus,
) -> Outcome<Response, AppError> {
// get() returns Unauthorized when the active runtime policy rejects T.
// ...
}get() and get_mut() reject policy violations with
BusAccessError::Unauthorized. Convenience methods such as read() log the
violation and collapse it to None; require() panics. Use get() in
production paths that must preserve missing-versus-unauthorized diagnostics.
Bus::new() remains unrestricted for 0.51.x compatibility. M419 may require an
explicit policy when a production profile is selected, without changing the
development constructor silently.
#Related
examples/bus-capability-demo-- Bus access policy demonstrationexamples/outcome-variants-demo-- Outcome control flow patternsranvier-core/src/bus.rs-- Bus implementation- Outcome and Bus Typed-Boundary Decision