#Outcome::from_result Cookbook

버전: 0.44.0 | 업데이트: 2026-03-29 | 적용 대상: ranvier-core 0.42+ | 카테고리: 쿡북


#개요

Outcome::from_result()은 임의의 Result<T, E> (E: Display)를 Outcome<T, String>으로 변환합니다. sqlx, serde_json, std::io 등 외부 라이브러리의 에러를 Ranvier의 Outcome 타입으로 변환하는 표준 패턴입니다 — 해당 라이브러리에 프레임워크 전용 trait를 추가하지 않습니다.


#1. `Outcome::from_result` — 기본 변환

use ranvier_core::prelude::*;

// E: Display인 모든 Result<T, E>
let result: Result<Vec<User>, sqlx::Error> = sqlx::query_as("SELECT * FROM users")
    .fetch_all(&*pool).await;

let outcome: Outcome<Vec<User>, String> = Outcome::from_result(result);
// Ok(users)  → Outcome::Next(users)
// Err(e)     → Outcome::Fault(e.to_string())

#2. `Outcome::from_result_ctx` — 컨텍스트 접두사

let outcome = Outcome::from_result_ctx(
    sqlx::query_as("SELECT * FROM departments")
        .fetch_all(&*pool).await,
    "dept query failed"
);
// Err(e) → Outcome::Fault("dept query failed: <에러 메시지>")

#3. `try_outcome!`과 함께 — 조기 반환

에러 시 즉시 반환하려면 try_outcome!을 사용합니다:

#[transition]
async fn get_dept(_: (), _: &(), bus: &mut Bus) -> Outcome<Department, String> {
    let pool = try_outcome!(bus.get_cloned::<PgPool>(), "PgPool");
    let id: String = try_outcome!(bus.path_param("id"), "path param");

    let dept = try_outcome!(
        sqlx::query_as("SELECT * FROM departments WHERE id = $1")
            .bind(&id)
            .fetch_optional(&*pool).await,
        "dept query"
    );

    match dept {
        Some(d) => Outcome::Next(d),
        None => Outcome::Fault(format!("Department {id} not found")),
    }
}

#4. 선택 가이드: `from_result` vs `try_outcome!`

상황 사용 예시
Outcome 값을 후속 처리에 사용 from_result let o = Outcome::from_result(...)
에러 시 조기 반환 try_outcome! let val = try_outcome!(expr, "ctx")
Outcome 콤비네이터 체이닝 from_result from_result(x).and_then(...)
Transition 본문의 단순 추출 try_outcome! 대부분의 transition 코드

#5. 다양한 에러 타입

from_result는 sqlx뿐 아니라 E: Display인 모든 타입에 동작합니다:

// serde_json
let data = Outcome::from_result_ctx(
    serde_json::from_str::<Config>(&raw),
    "JSON parse"
);

// std::io
let content = Outcome::from_result_ctx(
    std::fs::read_to_string("config.toml"),
    "file read"
);

// reqwest
let response = Outcome::from_result_ctx(
    reqwest::get("https://api.example.com/data").await,
    "HTTP call"
);

#6. `IntoOutcome` trait를 사용하지 않는 이유

Result에 trait를 구현하면 체이닝이 가능합니다: db_call.into_outcome(). 하지만 래핑 패턴을 선택했습니다:

패턴 코드 가시성
Outcome::from_result(expr) 래핑 변환 지점이 명시적
expr.into_outcome() 체이닝 변환이 trait 뒤에 숨음

래핑 패턴은 Result → Outcome 경계가 어디서 넘어가는지 명확하게 보여줍니다. 또한 from_resultE: Display 제약만으로 모든 에러 타입에 범용 동작합니다 — 다른 크레이트와 충돌할 수 있는 blanket trait 구현이 필요 없습니다.


#관련 문서

  • Outcome 패턴 Cookbookand_then, map_fault, 콤비네이터
  • 명시적 검색 파라미터 CookbookPageParams + 검색 구조체