#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

  1. Framework guarantees it? β†’ require()
  2. Optional feature? β†’ read()
  3. 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.


  • examples/bus-capability-demo β€” Bus access policy demonstration
  • examples/outcome-variants-demo β€” Outcome control flow patterns
  • ranvier-core/src/bus.rs β€” Bus implementation