Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

마이그레이션

masterror는 thiserror(파생 구문)와 anyhow(사용 편의성) 모두의 대체재로 설계되었습니다. 대부분의 마이그레이션은 의존성 교체와 타입 기반 기능의 점진적 도입으로 이루어집니다. 실행 가능한 워크스루: examples/migrate_from_thiserror.rs 및 examples/migrate_from_anyhow.rs.

thiserror에서

1단계는 기계적입니다 — import만 변경하면 됩니다. 파생 구문은 호환됩니다:

-use thiserror::Error;
+use masterror::Error;

속성 호환성

thiserrormasterror비고
{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에서

anyhowmasterror비고
anyhow::Result<T>masterror::AppResult<T>Result<T, AppError>의 별칭
anyhow::Errormasterror::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_mutAppError에서 동일한 이름다운캐스팅 동등성
#[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 매크로 · 컨텍스트와 메타데이터 · 모범 사례