#JSON Outcomes Cookbook
Version: 0.43.0 | Updated: 2026-03-25 | Applies to: ranvier-http 0.43+ | Category: Cookbook
#Overview
v0.43 introduces typed JSON auto-serialization at the HTTP route boundary. Transitions return domain structs โ JSON serialization is infrastructure, aligned with PHILOSOPHY.md Section 5: "Infrastructure as Boundary."
Example: typed-json-api (in examples/typed-json-api/)
#1. `get_json_out` โ GET with Typed JSON Response
Register a GET route where Outcome<T, E> is auto-serialized to JSON:
use ranvier_http::prelude::*;
use ranvier_runtime::Axon;
#[derive(Serialize, Deserialize)]
struct HealthResponse {
status: String,
version: String,
}
let health = Axon::simple::<String>("health").then(HealthCheck);
Ranvier::http()
.get_json_out("/api/health", health) // Outcome<HealthResponse, String> โ JSON
.run(())
.awaitOutcome::Next(HealthResponse { .. }) โ 200 OK with JSON body.
Outcome::Fault(e) โ 500 Internal Server Error.
#2. `post_typed_json_out` โ Typed Input + Typed Output
Combines post_typed() (JSON body auto-deserialization) with JSON auto-serialization:
#[derive(Serialize, Deserialize, JsonSchema)]
struct CreateItemInput {
name: String,
price: f64,
}
#[derive(Serialize, Deserialize)]
struct Item {
id: String,
name: String,
price: f64,
}
let create = Axon::typed::<CreateItemInput, String>("create").then(CreateItem);
Ranvier::http()
.post_typed_json_out("/api/items", create)
.run(())
.await- Input type (
CreateItemInput): requiresDeserialize + JsonSchema - Output type (
Item): requiresSerialize - No manual
serde_json::to_string()orserde_json::from_str()anywhere
#3. `delete_json_out` โ DELETE with Typed Response
#[derive(Serialize, Deserialize)]
struct DeleteResult { deleted: bool, id: String }
let delete = Axon::simple::<String>("delete").then(DeleteItem);
Ranvier::http()
.delete_json_out("/api/items/:id", delete)
.run(())
.awaitAlso available: put_typed_json_out(), patch_typed_json_out().
#4. `BusHttpExt` โ Path and Query Parameter Extraction
v0.43 adds the BusHttpExt trait for ergonomic HTTP parameter access:
use ranvier_http::BusHttpExt; // or via prelude
// Path parameter: /api/items/:id
let id: Uuid = bus.path_param("id")?; // Result<T: FromStr, String>
// Query parameter: /api/items?page=2
let page: Option<i64> = bus.query_param("page"); // Option<T: FromStr>
// Query with default: /api/items?per_page=10
let per_page: i64 = bus.query_param_or("per_page", 10); // T: FromStr#Before / After
// BEFORE (v0.42): manual PathParams extraction
let id: u64 = match bus.read::<PathParams>().and_then(|p| p.get("id")) {
Some(raw) => match raw.parse() {
Ok(id) => id,
Err(_) => return Outcome::Fault("Invalid ID".into()),
},
None => return Outcome::Fault("Missing ID".into()),
};
// AFTER (v0.43): BusHttpExt::path_param()
let id: u64 = match bus.path_param("id") {
Ok(id) => id,
Err(e) => return Outcome::Fault(e),
};#5. `Bus::get_cloned()` โ Concise Resource Extraction
// BEFORE: read() returns Option<&T>, need .cloned()
let pool = bus.read::<PgPool>().cloned()
.ok_or_else(|| "PgPool not in Bus")?;
// AFTER: get_cloned() returns Result<T, BusAccessError>
let pool = bus.get_cloned::<PgPool>().expect("PgPool");
// Or with try_outcome! for Outcome context:
let pool = try_outcome!(bus.get_cloned::<PgPool>(), "PgPool not in Bus");#6. `json_outcome()` โ Manual JSON when Needed
When a route uses .get() (not get_json_out) and you need JSON output manually:
use ranvier_http::prelude::json_outcome;
async fn run(&self, _input: (), _res: &(), _bus: &mut Bus) -> Outcome<String, String> {
let report = Report { title: "demo".into(), count: 3 };
json_outcome(&report) // Outcome<String, String>
}json_outcome() serializes to Outcome::Next(json_string). On serialization error: Outcome::Fault(error_message).
#7. `outcome_to_json_response()` โ Low-Level Control
For custom middleware or manual response construction:
use ranvier_http::outcome_to_json_response;
let response: HttpResponse = outcome_to_json_response(outcome);
// Next(T) โ 200 OK with JSON
// Fault(E) โ 500 with debug message#8. Route Method Summary
| Method | Input | Output | Use Case |
|---|---|---|---|
get_json_out |
() |
Outcome<T, E> โ JSON |
Read endpoints |
post_typed_json_out |
T: JsonSchema |
Outcome<U, E> โ JSON |
Create with typed body |
put_typed_json_out |
T: JsonSchema |
Outcome<U, E> โ JSON |
Update with typed body |
patch_typed_json_out |
T: JsonSchema |
Outcome<U, E> โ JSON |
Partial update |
delete_json_out |
() |
Outcome<T, E> โ JSON |
Delete endpoints |
get / post |
varies | Outcome<String, E> |
Manual serialization |
#See Also
- Outcome Patterns Cookbook โ
try_outcome!, combinators - Bus Access Patterns Cookbook โ
Bus::get_cloned(),BusHttpExt examples/typed-json-api/โ complete CRUD with typed JSON routesranvier-fullstack-reference/โ production-like deployment with typed JSON