#Bus Access Patterns β Decision Guide
Version: 0.33.0 Updated: 2026-03-15 Applies to: ranvier-core Category: Guides
#Overview
Bus is Ranvier's type-safe resource container. Transitions access shared resources
(database pools, auth context, configuration) through the Bus using 4 access methods.
This guide helps you choose the right one.
#Decision Tree
Must the resource exist in the Bus?
β
βββ YES: A prior step structurally guarantees it
β β (e.g., AuthContext after with_iam())
β β
β βββ require::<T>()
β Returns: &T
β On missing: panic (invariant violation β programming error)
β Use for: Resources whose insertion is guaranteed by the framework
β
βββ MAYBE: Can proceed without it
β β
β βββ read::<T>()
β Returns: Option<&T>
β On missing: None (+ tracing warning on BusAccessPolicy violation)
β Use for: Optional features (e.g., TenantId-based filtering)
β
βββ YES: Must return an error if missing
β
βββ get::<T>() / try_require::<T>()
Returns: Result<&T, BusAccessError> / Option<&T>
On missing: Err(BusAccessError::NotFound) / None
Use for: Default choice for production code#Method Comparison
| Method | Return Type | On Missing | Use When |
|---|---|---|---|
require::<T>() |
&T |
panic | Framework guarantees insertion (e.g., AuthContext after with_iam()) |
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 check_permission(
state: Request,
_resources: &(),
bus: &mut Bus,
) -> Outcome<Request, AppError> {
// AuthContext is always present after with_iam() setup
// If it's missing, that's a programming error β panic is correct
let auth = bus.require::<AuthContext>();
if !auth.has_role("admin") {
return Outcome::Fault(AppError::Forbidden);
}
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.read::<TenantId>() {
Some(tenant) => state.into_iter()
.filter(|item| item.tenant_id == tenant.0)
.collect(),
None => 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 = bus.get::<AppConfig>()
.map_err(|e| AppError::Internal(format!("Config missing: {}", e)))?;
// 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);
// Auth context (usually inserted by framework via with_iam())
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? β
require() - Optional feature? β
read() - Everything else β
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 macros restrict which types a Transition can access.
This enforces dependency boundaries at the Transition level.
// This transition can only read AuthContext and AppConfig from the Bus
bus_allow!(MyTransition, AuthContext, AppConfig);Policy violations are logged via tracing::warn! but do not prevent access
(soft enforcement). This is useful for auditing Bus usage patterns during
development.
#Related
examples/bus-capability-demoβ Bus access policy demonstrationexamples/outcome-variants-demoβ Outcome control flow patternsranvier-core/src/bus.rsβ Bus implementation