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는 전송 경계에서 오류를 HTTP로 매핑합니다. 도메인 코드는 AppResult<T>를 반환하고, 프레임워크 어댑터가 오류를 RFC 7807 application/problem+json 응답으로 변환하며, 텔레메트리를 플러시하고 리덕션을 적용합니다. 크레이트에는 AppError에 대한 IntoResponse / ResponseError 구현이 정확히 하나만 존재하므로, 매핑을 직접 작성할 일이 없습니다.

기능 플래그

기능활성화 내용
axumAppError, ProblemJson, ErrorResponse에 대한 IntoResponse; serde_json을 함께 가져옴
actixAppError에 대한 ResponseError; ProblemJson, ErrorResponse에 대한 Responder
multipartError에 대한 From<axum::extract::multipart::MultipartError> (axum을 함께 활성화)
openapiErrorResponse에 대한 utoipa 스키마
[dependencies]
masterror = { version = "0.28", features = ["axum"] }   # or ["actix"]

와이어 형식

두 어댑터 모두 ProblemJson을 직렬화합니다:

필드타입비고
typestring URI정규 문제 클래스, 예: https://errors.masterror.rs/not-found
titlestringAppErrorKind에서 파생된 짧은 요약
statusnumberHTTP 상태 코드
detailstring?공개 메시지; 오류가 리덕션 가능하면 생략됨
detailsobject?구조화된 세부 정보 (serde_json 기능)
codestring안정적인 기계 판독 가능 AppCode, 예: NOT_FOUND
grpcobject?멀티 프로토콜 클라이언트를 위한 { name, value } gRPC 매핑
metadataobject?Metadata에서 정제된 필드; 리덕션 시 생략됨

전송 힌트는 본문 필드가 아니라 헤더가 됩니다:

  • AppError::with_retry_after_secs(n)Retry-After: n
  • AppError::with_www_authenticate(challenge)WWW-Authenticate: challenge

내부 소스(std::error::Error 체인)는 로그에만 기록되며 클라이언트에게 직렬화되지 않습니다.

Axum

axum 기능은 AppError, ProblemJsonErrorResponse에 대해 IntoResponse를 구현하고, 오류 종류에서 파생된 axum::http::StatusCode를 반환하는 고유 메서드 AppError::http_status()를 추가합니다. 응답으로 변환하면 텔레메트리(tracing 이벤트, 메트릭 카운터, 지연 백트레이스)가 플러시됩니다 — 관측성을 참조하세요.

use axum::{Router, routing::get};
use masterror::{AppError, AppResult};

async fn handler() -> AppResult<&'static str> {
    Err(AppError::forbidden("no access"))
}

let app: Router = Router::new().route("/demo", get(handler));

힌트가 포함된 401:

use masterror::AppError;

let err = AppError::unauthorized("missing token")
    .with_retry_after_secs(7)
    .with_www_authenticate("Bearer realm=\"api\"");

위 코드는 상태 401, 헤더 Retry-After: 7WWW-Authenticate: Bearer realm="api", 그리고 다음 본문을 생성합니다:

{
  "type": "https://errors.masterror.rs/unauthorized",
  "title": "Unauthorized",
  "status": 401,
  "detail": "missing token",
  "code": "UNAUTHORIZED",
  "grpc": { "name": "UNAUTHENTICATED", "value": 16 }
}

핸들러의 도메인 오류

examples/axum-rest-api의 패턴: 도메인 열거형을 파생하고, 한 번만 AppError로 변환한 뒤, 크레이트의 IntoResponse를 재사용합니다.

use axum::response::{IntoResponse, Response};
use masterror::{AppError, Error};

#[derive(Debug, Error, Clone)]
pub enum UserError {
    #[error("user not found")]
    NotFound,
    #[error("email already exists")]
    DuplicateEmail,
    #[error("invalid email format")]
    InvalidEmail
}

impl From<UserError> for AppError {
    fn from(err: UserError) -> Self {
        match err {
            UserError::NotFound => AppError::not_found(err.to_string()),
            UserError::DuplicateEmail => AppError::conflict(err.to_string()),
            UserError::InvalidEmail => AppError::validation(err.to_string())
        }
    }
}

impl IntoResponse for UserError {
    fn into_response(self) -> Response {
        AppError::from(self).into_response()
    }
}

파생에 #[app_error(kind = ..., code = ...)]를 붙이면 From<UserError> for AppError 구현이 자동으로 생성됩니다 — Derive 매크로를 참조하세요.

Actix Web

actix 기능은 AppError에 대해 actix_web::ResponseError를 구현하므로, AppResult<T>를 반환하는 핸들러가 즉시 동작합니다. error_response()는 텔레메트리를 발행하고 ProblemJson::from_ref를 통해 동일한 problem+json 페이로드를 빌드합니다.

use actix_web::{App, HttpServer, get};
use masterror::{AppError, AppResult};

#[get("/forbidden")]
async fn forbidden() -> AppResult<&'static str> {
    Err(AppError::forbidden("no access"))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().service(forbidden))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

클라이언트는 다음과 함께 403을 받습니다:

{
  "type": "https://errors.masterror.rs/forbidden",
  "title": "Forbidden",
  "status": 403,
  "detail": "no access",
  "code": "FORBIDDEN",
  "grpc": { "name": "PERMISSION_DENIED", "value": 7 }
}

ProblemJsonErrorResponseResponder를 구현하므로 핸들러가 이를 직접 반환할 수 있습니다. 상태 매핑은 Axum과 동일한 안정적인 AppErrorKind → StatusCode 테이블을 사용합니다.

Multipart

multipart(axum을 함께 활성화)는 axum::extract::multipart::MultipartError를 파서 메시지를 보존하면서 AppErrorKind::BadRequestError로 변환합니다:

use axum::extract::multipart::Multipart;
use masterror::{AppErrorKind, Error};

async fn upload(mut multipart: Multipart) -> Result<(), Error> {
    while let Some(field) = multipart.next_field().await? {
        let _ = field.bytes().await?;
    }
    Ok(())
}

잘못된 형식의 클라이언트 페이로드는 500 대신 400 Bad Request로 표면화됩니다.

응답 수동 빌드

테스트나 커스텀 전송을 위해 프레임워크 없이 페이로드를 구성할 수 있습니다:

use masterror::{AppError, ProblemJson};

let problem = ProblemJson::from_app_error(AppError::not_found("resource not found"));
assert_eq!(problem.status, 404);
assert_eq!(problem.code.as_str(), "NOT_FOUND");

ProblemJson::from_ref(&err)는 소비하는 대신 빌려오고, ProblemJson::from_error_response(resp)는 레거시 ErrorResponse 와이어 타입을 업그레이드합니다.

함께 보기: 오류 종류와 코드 · 통합 · 관측성 · 기능 플래그