#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_result는 E: Display 제약만으로 모든 에러 타입에 범용 동작합니다 — 다른 크레이트와 충돌할 수 있는 blanket trait 구현이 필요 없습니다.
#관련 문서
- Outcome 패턴 Cookbook —
and_then,map_fault, 콤비네이터 - 명시적 검색 파라미터 Cookbook —
PageParams+ 검색 구조체