Skip to content
github-actions[bot] edited this page Jul 5, 2026 · 1 revision

통합

선택적 기능 플래그는 널리 쓰이는 서드파티 오류 타입에서 masterror::Error로의 From<...> 변환을 추가하므로, 호출 지점의 ? 하나로 구조화된 메타데이터를 갖춘 분류된 오류가 생성됩니다. 모든 변환은 안정적인 AppErrorKind를 선택하고 관측성을 위한 텔레메트리 필드(비밀 정보는 절대 포함하지 않음)를 첨부합니다.

변환 매트릭스

기능 소스 타입 결과 AppErrorKind
sqlx sqlx_core::error::Error NotFound, Conflict, Validation, Timeout, DependencyUnavailable, Config, BadRequest, Serialization, Deserialization, Network, Database, Internal
sqlx-migrate sqlx::migrate::MigrateError Database (마이그레이션 단계 메타데이터 포함)
redis redis::RedisError Cache (기본값), Timeout, DependencyUnavailable
reqwest reqwest::Error Timeout, Network, RateLimited, DependencyUnavailable, ExternalApi
validator validator::ValidationErrors Validation
config config::ConfigError Config (config.phase 메타데이터 포함)
tokio tokio::time::error::Elapsed Timeout
serde_json serde_json::Error Serialization (I/O), Deserialization (구문/데이터/EOF)
teloxide teloxide_core::RequestError ExternalApi, Unauthorized, RateLimited, Network, Deserialization, Internal
init-data init_data_rs::InitDataError TelegramAuth
tonic masterror::Errortonic::Status 아웃바운드 매핑, 아래 참조
multipart axum::extract::multipart::MultipartError BadRequest (웹 프레임워크 참조)

sqlx 및 sqlx-migrate

sqlxsqlx-core에만 의존합니다(드라이버 없음, TLS 없음). 주요 매핑:

  • Error::RowNotFoundNotFound
  • 풀 타임아웃 → Timeout; 풀 종료 및 I/O 실패 → DependencyUnavailable; TLS 오류 → Network
  • 제약 조건 위반은 sqlx 오류 종류로 분류됩니다: 유니크 및 외래 키 위반 → Conflict, not-null / check 위반 → Validation, 그 외 → Database
  • 인코딩 → Serialization, 디코딩 → Deserialization

데이터베이스 오류는 SQLSTATE와 제약 조건 이름을 메타데이터로 캡처합니다. 알려진 SQLSTATE 코드는 공개 AppCode를 재정의합니다: 23505USER_ALREADY_EXISTS, 23503CONFLICT, 23502/23514VALIDATION. 일시적인 SQLSTATE(40001 직렬화 실패, 55P03 잠금 획득 불가)는 재시도 힌트를 첨부합니다.

use masterror::{AppErrorKind, Error};

async fn load_user(pool: &sqlx::PgPool, id: i64) -> Result<User, Error> {
    let user = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1")
        .bind(id)
        .fetch_one(pool)
        .await?;          // RowNotFound becomes AppErrorKind::NotFound
    Ok(user)
}

sqlx-migrate는 전체 sqlx(여전히 기본 기능 없이)를 가져오며 sqlx::migrate::MigrateErrorDatabase로 매핑하고, 마이그레이션 단계와 버전을 메타데이터에 기록합니다.

redis

모든 redis::RedisError 값은 기본적으로 Cache로 매핑됩니다. 타임아웃 계열 오류는 Timeout이 되고 연결 실패는 DependencyUnavailable이 됩니다. 오류 카테고리와 코드는 메타데이터로 보존됩니다.

reqwest

reqwest를 외부 HTTP API의 클라이언트로 취급합니다:

  • is_timeout()Timeout
  • is_connect() / is_request()Network
  • HTTP 상태 오류: 429RateLimited, 408Timeout, 5xxDependencyUnavailable, 그 외 → ExternalApi
  • 나머지 모든 경우 → ExternalApi

메타데이터는 엔드포인트, 상태 및 저수준 플래그를 기록합니다. URL은 공개 페이로드에서 해싱/리덕션 대상으로 표시됩니다.

validator

validator::ValidationErrorsValidation이며, 메타데이터에 집계 컨텍스트를 포함합니다: 실패한 필드 이름(validation.fields), 필드 및 오류 개수, 그리고 첫 번째 검증 코드(validation.codes):

use masterror::AppResult;
use validator::Validate;

#[derive(Validate)]
struct Payload {
    #[validate(length(min = 5))]
    name: String
}

fn check(p: &Payload) -> AppResult<()> {
    p.validate()?;      // ValidationErrors -> AppErrorKind::Validation
    Ok(())
}

config, tokio, serde_json

  • config::ConfigErrorConfig이며, 실패 단계를 식별하는 config.phase 메타데이터 필드(not_found, file_parse, type 등)를 포함합니다.
  • tokio::time::error::ElapsedTimeout이며 timeout.source = "tokio::time::timeout" 메타데이터 필드를 포함합니다. 이 오류는 커스텀 메시지를 갖지 않으므로 클라이언트는 해당 종류의 고정 제목 "Operation timed out"을 보게 됩니다.
  • serde_json::ErrorError::classify()를 통해 분류됩니다: I/O → Serialization; 구문, 데이터 및 EOF → Deserialization.

teloxide

teloxide_core::RequestError 매핑:

변형 AppErrorKind
Api ExternalApi (유효하지 않은 토큰 → Unauthorized)
MigrateToChatId ExternalApi
RetryAfter RateLimited
Network Network
InvalidJson Deserialization
Io Internal

init-data (Telegram Mini Apps)

모든 init_data_rs::InitDataError 변형(누락/유효하지 않은 해시, 만료된 페이로드, 서명 실패)은 TelegramAuth로 매핑되어 Mini App 인증 실패를 일반적인 잘못된 요청과 구분합니다.

tonic (아웃바운드 gRPC)

tonic은 반대 방향으로 변환합니다: From을 통해 masterror::Errortonic::Status. AppCode는 HTTP에 사용되는 것과 동일한 CODE_MAPPINGS 테이블을 통해 정규 tonic::Code로 매핑됩니다. 상태에는 메타데이터 항목 app-code, app-http-statusapp-problem-type이 포함되며, 존재할 경우 재시도 및 www-authenticate 힌트도 함께 전달됩니다. 리덕션 가능한 오류는 메시지가 종류 레이블로 대체되고 메타데이터가 제거됩니다.

use masterror::AppError;
use tonic::{Code, Status};

let status = Status::from(AppError::not_found("missing"));
assert_eq!(status.code(), Code::NotFound);

frontend (WASM / 브라우저)

frontend 기능은 wasm-bindgen을 기반으로 AppErrorErrorResponse에 대한 masterror::frontend::BrowserConsoleExt 트레이트를 추가합니다:

  • to_js_value() — 오류를 wasm_bindgen::JsValue로 직렬화
  • log_to_browser_console()console.error를 통해 발행

둘 다 wasm32 타깃에서 동작합니다. 네이티브 타깃에서는 BrowserConsoleError::UnsupportedTarget을 반환합니다. 실패 모드(콘솔 없음, console.error 호출 불가, 직렬화 실패)는 BrowserConsoleError 열거형으로 처리됩니다.

use masterror::{AppError, frontend::BrowserConsoleExt};

let err = AppError::not_found("user not found");
err.log_to_browser_console()?;

turnkey

turnkey 기능은 masterror::turnkey에 작고 안정적인 도메인 분류 체계를 노출합니다:

TurnkeyErrorKind AppErrorKind
UniqueLabel Conflict
RateLimited RateLimited
Timeout Timeout
Auth Unauthorized
Network Network
Service Turnkey

TurnkeyError::new(kind, msg)는 도메인 오류를 빌드하고, From<TurnkeyError> for AppErrorFrom<TurnkeyErrorKind> for AppErrorKind가 매핑을 수행합니다. classify_turnkey_error(&str)는 원시 프로바이더 메시지를 휴리스틱하게 (대소문자 무시, 단어 경계 인식) TurnkeyErrorKind로 분류합니다:

use masterror::turnkey::{TurnkeyError, TurnkeyErrorKind, classify_turnkey_error};
use masterror::{AppError, AppErrorKind};

let kind = classify_turnkey_error("429 rate-limit reached");
assert_eq!(kind, TurnkeyErrorKind::RateLimited);

let app: AppError = TurnkeyError::new(kind, "quota exceeded").into();
assert_eq!(app.kind, AppErrorKind::RateLimited);

함께 보기: 기능 플래그 · 웹 프레임워크 · 오류 종류와 코드 · 관측성

Clone this wiki locally