마이그레이션
masterror는 thiserror(파생 구문)와 anyhow(사용 편의성) 모두의 대체재로
설계되었습니다. 대부분의 마이그레이션은 의존성 교체와 타입 기반 기능의 점진적
도입으로 이루어집니다. 실행 가능한 워크스루:
examples/migrate_from_thiserror.rs
및
examples/migrate_from_anyhow.rs.
thiserror에서
1단계는 기계적입니다 — import만 변경하면 됩니다. 파생 구문은 호환됩니다:
-use thiserror::Error;
+use masterror::Error;
속성 호환성
| thiserror | masterror | 비고 |
|---|---|---|
{field} 플레이스홀더가 포함된 #[error("...")] | 동일 | 위치 기반 {0} 포함 1:1 |
포맷 스펙 :>8, :.3, :x, :p, :e | 동일 | TemplateFormatter가 thiserror의 포매터 감지를 미러링 |
#[error(transparent)] | 동일 | Display/source를 전달하는 단일 필드 래퍼 강제 |
#[from] | 동일 | From<...> 생성, 래퍼 형태 검증 |
#[source] | 동일 | source() 체인 연결 |
#[backtrace] | 동일 | 필드에서 존중됨 |
| — | #[app_error(kind = ..., code = ..., message)] | 추가: From<YourError> for AppError(및 AppCode) 생성; message는 Display를 공개 메시지로 전달 |
| — | #[provide(ref = T, value = T)] | 추가: std::error::Request를 통한 타입 기반 텔레메트리; Option<T> 필드는 Some일 때만 제공 |
| — | #[derive(Masterror)] + #[masterror(...)] | 추가: category, redact(message, fields(...)), telemetry(...), map.grpc, map.problem을 포함한 전체 매핑 |
기존 열거형은 변경 없이 계속 컴파일됩니다. 어노테이션을 추가하면 얻는 것:
use masterror::{AppCode, AppError, AppErrorKind, Error};
#[derive(Debug, Error)]
#[error("user {user_id} not found")]
#[app_error(kind = AppErrorKind::NotFound, code = AppCode::NotFound, message)]
struct UserMissing {
user_id: u64
}
let app: AppError = UserMissing { user_id: 42 }.into();
assert_eq!(app.kind, AppErrorKind::NotFound);
열거형은 변형별로 매핑됩니다 — 각 변형이 자체 #[app_error(...)]를 가지며,
파생은 단일 From<Enum> for AppError를 발행합니다.
권장 순서: (1) import 교체, (2) API 경계를 넘는 타입에 #[app_error] 추가,
(3) 손으로 작성한 From<DomainError> for AppError 구현을 생성된 것으로
교체, (4) 리덕션이나 메타데이터가 필요한 곳에 #[masterror(...)] 도입.
anyhow에서
| anyhow | masterror | 비고 |
|---|---|---|
anyhow::Result<T> | masterror::AppResult<T> | Result<T, AppError>의 별칭 |
anyhow::Error | masterror::AppError / Error | 불투명한 덩어리 대신 종류, 코드, 메타데이터를 전달 |
.context("msg") | .context("msg") | masterror::ResultExt를 통해 동일 |
.with_context(|| ...) | .ctx(|| Context::new(kind)...) | anyhow처럼 지연 평가되지만 타입 기반 Context(종류, 코드, 필드, 리덕션)를 빌드 |
bail!(err) | fail!(err) | 포맷 장치 없이 타입 기반 오류 표현식을 받음 |
ensure!(cond, "msg {x}") | ensure!(cond, AppError::...) | 조건 + 타입 기반 오류; 성공 경로에서 포매팅 없음 |
err.chain() | err.chain() | 소스 체인에 대한 동일한 이터레이터 |
err.root_cause() | err.root_cause() | 동일 |
err.is::<E>() / downcast / downcast_ref / downcast_mut | AppError에서 동일한 이름 | 다운캐스팅 동등성 |
#[from] 스타일 래핑 | 기능 플래그 뒤의 From<...> 구현 | sqlx/redis/reqwest/… 가 사전 분류된 상태로 도착 |
.context()는 기대한 그대로 동작합니다:
use masterror::{AppResult, ResultExt};
fn read_config(path: &str) -> AppResult<String> {
let content = std::fs::read_to_string(path).context("Failed to read config file")?;
Ok(content)
}
ensure!/fail!은 anyhow의 문자열 포매팅을 타입 기반 오류와 맞바꿉니다:
use masterror::{AppError, AppResult, ensure, fail, field};
fn parse(content: &str, path: &str) -> AppResult<()> {
ensure!(
!content.is_empty(),
AppError::bad_request("Config file is empty")
.with_field(field::str("path", path.to_owned()))
);
if content.starts_with("invalid") {
fail!(AppError::bad_request("Invalid config format"));
}
Ok(())
}
오류 표현식은 가드가 걸릴 때만 평가되므로 해피 패스는 할당이 없는 상태로 유지됩니다 — anyhow와 동일한 보장에 더해 기계 판독 가능한 코드까지 얻습니다.
anyhow에 대응물이 없는 것
마이그레이션하면 anyhow에 상응하는 것이 없는 기능들을 얻습니다:
- 타입 기반 분류 체계 — 문자열 기반 컨텍스트 대신
AppErrorKind(내부)와AppCode(공개, SCREAMING_SNAKE_CASE). 오류 종류와 코드를 참조하세요. - 전송 매핑 — 동일한 코드 테이블에서 파생되는 Axum/Actix용 RFC 7807
problem+json및 gRPC용tonic::Status. 웹 프레임워크를 참조하세요. - 텔레메트리 — 경계에서의 자동
tracing이벤트,error_total{code,category}메트릭 및 지연 백트레이스. 관측성을 참조하세요. - 리덕션 — 모든 전송에서 존중되는
redactable()메시지 및 필드별Hash/Last4/Redact정책. 모범 사례를 참조하세요. - 구조화된 메타데이터 — 값을 메시지에 포매팅하는 대신 타입 기반
field::str/u64/duration/ip/....
주의할 점
- anyhow의
ensure!(cond, "format {}", x)포맷 메시지 형태에는 직접적인 대응물이 없습니다: 오류를 명시적으로 구성하거나 (AppError::bad_request(format!(...))) — 더 좋게는 — 정적 메시지에 메타데이터 필드를 더하세요. anyhow::Error는 임의의E: Error + Send + Sync를 받습니다. masterror에서는 래핑 시점에 종류를 선택합니다(Context::new(kind)또는From변환) — 이 결정 지점은 마찰이 아니라 기능입니다: 바로 여기서 분류가 일어납니다.ensure!와fail!은 모두return Err(...)로 확장되므로, 표현식이 이미 오류 타입인Result<_, E>를 반환하는 모든 함수에서 동작합니다 —Into변환은 삽입되지 않습니다.
함께 보기: 시작하기 · Derive 매크로 · 컨텍스트와 메타데이터 · 모범 사례