소스 정보
- 저장소
- puk0806/gugbab-claude
- 최근 소스 활동
- 2026년 6월 23일 04:41
- 감지된 SKILL.md 언어
- 한국어
- 스타
- 2
- 포크
- 0
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/puk0806/gugbab-claude --skill thiserror명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
DDD(Domain-Driven Design) 아키텍처 핵심 패턴 - 유비쿼터스 언어, 서브도메인, 바운디드 컨텍스트, Aggregate, Entity/VO, 도메인 서비스/이벤트, 레이어드 아키텍처
대규모 React/Next.js 프로젝트를 layer-first(types/·utils/·hooks/·api/·components/ 밑에 도메인이 반복되는 구조)에서 domain-first(feature/도메인 우선) 구조로 전환하는 설계 기준과 절차. Feature-Sliced Design 2.1 정본(layers 6종·slices·segments·import 규칙·@x 크로스임포트·public API), FSD를 쓰지 않는 경량 대안(features + shared 2~3계층 + ESLint import/no-restricted-paths), Next.js App Router 공존 전략(route group `()`·private folder `_`·colocation), Turborepo/Nx 모노레포에서 폴더↔패키지 승격 기준, colocation과 배럴 파일 성능 트레이드오프, 도메인 경계 역추출(import 그래프·change coupling·용어 클러스터), 전환 실패 패턴(shared 비대화·entities 남용·순환 의존·도메인=라우트 착각·조기 추상화). 도메인 개념 자체(바운디드 컨텍스트·유비쿼터스 언어)는 `architecture/ddd` 스킬을 참조한다.
소스 파일 수천 개 규모 프론트엔드 코드베이스를 멈추지 않고 점진 재구조화하는 실행 전략 - Strangler Fig / Branch by Abstraction / Parallel Change, ts-morph·jscodeshift codemod, PR 분할·검증 게이트·되돌리기, 테스트 없는 코드의 안전망, 작업 순서 설계와 위반 수 기반 진행 추적
SOC 직업 분류 기준
SKILL.md 표시 중
| name | thiserror |
| description | Rust thiserror 크레이트 기반 에러 처리 패턴 - derive(Error), 메시지 포매팅, from 변환, Axum 연동 |
소스: https://docs.rs/thiserror/latest/thiserror/ | https://github.com/dtolnay/thiserror 검증일: 2026-06-20
주의: 이 문서는 thiserror 2.x 기준으로 작성되었습니다. 2.0.0 출시 시 MSRV는 1.61이었으나 이후 지속적으로 상향되어 2026년 4월 기준 1.71입니다. 1.x에서 2.x로 업그레이드 시 일부 동작 차이가 있으므로 공식 CHANGELOG를 확인하세요.
# Cargo.toml
[dependencies]
thiserror = "2"
#[derive(Error)]는 std::error::Error 트레이트를 자동 구현한다. Display도 #[error("...")] 어트리뷰트로 함께 생성된다.
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("unauthorized")]
Unauthorized,
#[error("internal server error")]
Internal,
}
핵심 규칙:
Debug는 직접 derive 해야 함 (thiserror가 자동 추가하지 않음)#[error("...")]는 모든 variant에 필수 (없으면 컴파일 에러)#[error("...")] 내부에서 Display 포맷 문법을 사용한다.
#[derive(Debug, Error)]
pub enum ValidationError {
// 위치 기반 참조
#[error("invalid field: {0}")]
InvalidField(String),
// 이름 기반 참조 (named struct variant)
#[error("field `{field}` must be between {min} and {max}")]
OutOfRange {
field: String,
min: i64,
max: i64,
},
// source()의 Display 출력 포함
#[error("database error: {source}")]
Database {
#[source]
source: sqlx::Error,
},
// 메서드 호출 가능
#[error("error at line {}: {}", .line, .message)]
Parse { line: usize, message: String },
}
포매팅 규칙:
{0}, {1} -- 튜플 variant 필드 인덱스{field_name} -- named struct variant 필드명.field 문법으로 named field 접근 (fmt 인자 위치에서){field:?} -- Debug 포맷 사용 가능#[from]은 From<T> 트레이트를 자동 구현하여 ? 연산자로 에러를 변환한다.
#[derive(Debug, Error)]
pub enum AppError {
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
#[error("database error: {0}")]
Database(#[from] sqlx::Error),
}
// 사용: ? 연산자로 자동 변환
fn read_config() -> Result<Config, AppError> {
let data = std::fs::read_to_string("config.json")?; // io::Error -> AppError::Io
let config = serde_json::from_str(&data)?; // serde_json::Error -> AppError::Json
Ok(config)
}
#[from] 규칙:
#[from]은 한 번만 사용 가능#[from]은 #[source]를 암묵적으로 포함 (별도 #[source] 불필요)From 구현 필요#[source]는 Error::source() 메서드를 구현하여 에러 원인 체이닝을 지원한다.
#[derive(Debug, Error)]
pub enum ServiceError {
// #[source]만 사용 -- From 구현 없이 source() 체이닝만
#[error("failed to fetch user")]
FetchUser {
#[source]
source: DatabaseError,
},
// 필드명이 `source`이면 자동으로 #[source] 적용
#[error("connection failed")]
Connection {
source: std::io::Error, // 필드명이 source -> 자동 인식
},
}
#[error(transparent)]는 Display와 source()를 내부 에러에 위임한다.
#[derive(Debug, Error)]
pub enum AppError {
#[error(transparent)]
Unexpected(#[from] anyhow::Error),
#[error("not found: {0}")]
NotFound(String),
}
thiserror로 정의한 에러를 Axum 핸들러에서 HTTP 응답으로 변환하는 패턴.
use axum::{
http::StatusCode,
response::{IntoResponse, Response},
Json,
};
use serde_json::json;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
#[error("not found: {0}")]
NotFound(String),
#[error("unauthorized")]
Unauthorized,
#[error("validation error: {0}")]
Validation(String),
#[error("internal error: {0}")]
Internal(#[from] anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match &self {
AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg.clone()),
AppError::Unauthorized => (StatusCode::UNAUTHORIZED, self.to_string()),
AppError::Validation(msg) => (StatusCode::BAD_REQUEST, msg.clone()),
AppError::Internal(_) => (
StatusCode::INTERNAL_SERVER_ERROR,
"internal server error".to_string(), // 내부 에러 메시지 노출 방지
),
};
= json!({
: {
: status.(),
: message,
}
});
(status, (body)).()
}
}
((id): Path<>) <Json<User>, AppError> {
= db::(id)
.
.(|e| AppError::(e.()))?
.(|| AppError::(()))?;
((user))
}
패턴 핵심:
IntoResponse 구현으로 Result<T, AppError>를 핸들러 반환 타입으로 사용into_response 내부에서 tracing::error! 호출use thiserror::Error;
#[derive(Debug, Error)]
pub enum AppError {
// 인증/인가
#[error("unauthorized")]
Unauthorized,
#[error("forbidden: {0}")]
Forbidden(String),
// 리소스
#[error("{resource} not found: {id}")]
NotFound { resource: &'static str, id: String },
#[error("{resource} already exists: {id}")]
Conflict { resource: &'static str, id: String },
// 입력 검증
#[error("validation error: {0}")]
Validation(String),
// 외부 서비스
#[error("database error")]
Database(#[from] sqlx::Error),
#[error("redis error")]
Redis(#[from] redis::RedisError),
#[error("http client error")]
HttpClient(#[from] reqwest::Error),
// 직렬화
#[error("json error")]
Json(#[from] serde_json::Error),
// 포괄적 내부 에러
#[error(transparent)]
Unexpected( anyhow::Error),
}
{
(resource: & , id: <>) {
::NotFound { resource, id: id.() }
}
(resource: & , id: <>) {
::Conflict { resource, id: id.() }
}
}
사용 예시:
async fn create_user(Json(input): Json<CreateUser>) -> Result<Json<User>, AppError> {
if input.email.is_empty() {
return Err(AppError::Validation("email is required".into()));
}
let exists = db::user_exists(&input.email).await?; // sqlx::Error -> AppError::Database
if exists {
return Err(AppError::conflict("user", &input.email));
}
let user = db::create_user(input).await?;
Ok(Json(user))
}
| 기준 | thiserror | anyhow |
|---|---|---|
| 용도 | 라이브러리, 에러 타입 정의 | 애플리케이션, 에러 전파 |
| 에러 타입 | 구체적인 enum/struct | 단일 anyhow::Error 타입 |
| 패턴 매칭 | 가능 (variant별 분기) | 불편 (downcast 필요) |
| 컨텍스트 추가 | 직접 필드로 포함 | .context("...") 체이닝 |
| 적합한 곳 | 공개 API, 에러 분기 처리 필요 시 | 내부 로직, 빠른 프로토타이핑 |
실전 조합 패턴:
// thiserror로 공개 에러 타입 정의
#[derive(Debug, Error)]
pub enum AppError {
#[error("specific known error")]
Known,
// anyhow로 예상치 못한 에러를 포괄적으로 수집
#[error(transparent)]
Unexpected(#[from] anyhow::Error),
}
선택 규칙:
thiserroranyhowthiserror + anyhow 조합