#JSON Outcomes 쿡북
버전: 0.43.0 | 업데이트: 2026-03-25 | 적용 대상: ranvier-http 0.43+ | 카테고리: 쿡북
#개요
v0.43은 HTTP 라우트 경계에서 타입이 지정된 JSON 자동 직렬화를 도입합니다. Transition은 도메인 구조체를 반환하고, JSON 직렬화는 인프라가 담당합니다 — PHILOSOPHY.md 5절 "경계로서의 인프라"에 부합합니다.
예제: typed-json-api (examples/typed-json-api/)
#1. `get_json_out` — 타입이 지정된 JSON 응답의 GET
Outcome<T, E>가 자동으로 JSON 직렬화되는 GET 라우트 등록:
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 JSON 바디.
Outcome::Fault(e) → 500 Internal Server Error.
#2. `post_typed_json_out` — 타입된 입력 + 타입된 출력
post_typed() (JSON 바디 자동 역직렬화)와 JSON 자동 직렬화 결합:
#[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- 입력 타입 (
CreateItemInput):Deserialize + JsonSchema필요 - 출력 타입 (
Item):Serialize필요 - 수동
serde_json::to_string()이나serde_json::from_str()불필요
#3. `delete_json_out` — 타입된 응답의 DELETE
#[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(()).awaitput_typed_json_out(), patch_typed_json_out()도 사용 가능.
#4. `BusHttpExt` — Path/Query 파라미터 추출
v0.43은 HTTP 파라미터 접근을 위한 BusHttpExt 트레이트를 추가:
use ranvier_http::BusHttpExt; // 또는 prelude를 통해
// Path 파라미터: /api/items/:id
let id: Uuid = bus.path_param("id")?; // Result<T: FromStr, String>
// Query 파라미터: /api/items?page=2
let page: Option<i64> = bus.query_param("page"); // Option<T: FromStr>
// 기본값이 있는 Query: /api/items?per_page=10
let per_page: i64 = bus.query_param_or("per_page", 10); // T: FromStr#Before / After
// BEFORE (v0.42): 수동 PathParams 추출
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("유효하지 않은 ID".into()),
},
None => return Outcome::Fault("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()` — 간결한 리소스 추출
// BEFORE: read()는 Option<&T> 반환, .cloned() 필요
let pool = bus.read::<PgPool>().cloned()
.ok_or_else(|| "PgPool이 Bus에 없음")?;
// AFTER: get_cloned()는 Result<T, BusAccessError> 반환
let pool = bus.get_cloned::<PgPool>().expect("PgPool");
// Outcome 컨텍스트에서 try_outcome!과 함께:
let pool = try_outcome!(bus.get_cloned::<PgPool>(), "PgPool이 Bus에 없음");#6. `json_outcome()` — 수동 JSON이 필요할 때
.get() (get_json_out 아님)을 사용하는 라우트에서 수동 JSON 출력이 필요할 때:
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>
}#7. `outcome_to_json_response()` — 저수준 제어
커스텀 미들웨어나 수동 응답 구성:
use ranvier_http::outcome_to_json_response;
let response: HttpResponse = outcome_to_json_response(outcome);
// Next(T) → 200 OK JSON
// Fault(E) → 500 디버그 메시지#8. 라우트 메서드 요약
| 메서드 | 입력 | 출력 | 용도 |
|---|---|---|---|
get_json_out |
() |
Outcome<T, E> → JSON |
읽기 엔드포인트 |
post_typed_json_out |
T: JsonSchema |
Outcome<U, E> → JSON |
타입된 바디 생성 |
put_typed_json_out |
T: JsonSchema |
Outcome<U, E> → JSON |
타입된 바디 업데이트 |
patch_typed_json_out |
T: JsonSchema |
Outcome<U, E> → JSON |
부분 업데이트 |
delete_json_out |
() |
Outcome<T, E> → JSON |
삭제 엔드포인트 |
get / post |
가변 | Outcome<String, E> |
수동 직렬화 |
#참고
- Outcome 패턴 쿡북 —
try_outcome!, 컴비네이터 - Bus Access Patterns 쿡북 —
Bus::get_cloned(),BusHttpExt examples/typed-json-api/— 타입이 지정된 JSON 라우트 완전한 CRUDranvier-fullstack-reference/— 타입이 지정된 JSON을 활용한 프로덕션급 배포