#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(()).await

Outcome::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(()).await

put_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 라우트 완전한 CRUD
  • ranvier-fullstack-reference/ — 타입이 지정된 JSON을 활용한 프로덕션급 배포