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

마이그레이션

masterrorthiserror(파생 구문)와 anyhow(사용 편의성) 모두의 대체재로 설계되었습니다. 대부분의 마이그레이션은 의존성 교체와 타입 기반 기능의 점진적 도입으로 이루어집니다. 실행 가능한 워크스루: examples/migrate_from_thiserror.rsexamples/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) 생성; messageDisplay를 공개 메시지로 전달
#[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 매크로 · 컨텍스트와 메타데이터 · 모범 사례