feat: 에러 코드 SSOT 체계 도입
사용자에게 노출되는 실패가 에러 코드 없이 만들어질 수 없도록 구조를 바꾼다. 기존에는 예외 필터가 HTTP 상태로 코드를 추론해 도메인별 에러를 표현할 수 없었고, 세 플랫폼의 에러 사전을 손으로 맞춰야 해서 어긋나기 쉬웠다. - 코드↔메시지↔HTTP 상태를 error-codes.ts 레지스트리 한 곳으로 통합 - BusinessException 은 에러 코드를 생성자 필수 인자로 받아 코드 누락을 타입으로 차단 - sync:error-codes 스크립트가 프론트·플러터 사전을 자동 생성 (수동 복사 제거) - 429 전용 코드 REQ_001 추가 (기존에는 "시스템 오류"로 잘못 안내) - AUTH_002(세션 만료)를 클라이언트에서 실제로 발행하도록 구현 - 응답에 traceId 를 실어 로그와 대조 가능하게 함 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 에러 코드 SSOT 동기화 스크립트.
|
||||
*
|
||||
* backend 의 error-codes.ts 레지스트리를 읽어 프론트엔드/플러터의 에러 사전을 생성한다.
|
||||
* 실행: npm run sync:error-codes (backend 디렉터리에서)
|
||||
*
|
||||
* 생성 파일은 직접 수정하지 말 것 — 레지스트리를 고치고 다시 실행한다.
|
||||
* CI 는 이 스크립트를 실행한 뒤 git diff 로 드리프트를 검사한다.
|
||||
*/
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
|
||||
import { ERROR_CODES } from '../src/common/errors/error-codes';
|
||||
|
||||
const REPO_ROOT = resolve(__dirname, '..', '..');
|
||||
const GENERATED_NOTICE =
|
||||
'backend/src/common/errors/error-codes.ts 에서 자동 생성된 파일입니다. 직접 수정하지 마세요.';
|
||||
|
||||
const entries = Object.entries(ERROR_CODES);
|
||||
|
||||
/** TypeScript 작은따옴표 문자열 리터럴로 이스케이프한다. */
|
||||
function toTsLiteral(value: string): string {
|
||||
const escaped = value
|
||||
.split('\\')
|
||||
.join('\\\\')
|
||||
.split("'")
|
||||
.join('\\' + "'");
|
||||
return `'${escaped}'`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dart 작은따옴표 문자열 리터럴로 이스케이프한다.
|
||||
* Dart 는 문자열 안의 `$` 를 보간으로 해석하므로 함께 이스케이프해야 한다.
|
||||
*/
|
||||
function toDartLiteral(value: string): string {
|
||||
const escaped = value
|
||||
.split('\\')
|
||||
.join('\\\\')
|
||||
.split("'")
|
||||
.join('\\' + "'")
|
||||
.split('$')
|
||||
.join('\\' + '$');
|
||||
return `'${escaped}'`;
|
||||
}
|
||||
|
||||
function writeGenerated(relativePath: string, content: string): void {
|
||||
const target = resolve(REPO_ROOT, relativePath);
|
||||
mkdirSync(dirname(target), { recursive: true });
|
||||
writeFileSync(target, content, { encoding: 'utf8' });
|
||||
console.log(`생성 완료: ${relativePath}`);
|
||||
}
|
||||
|
||||
// ---------- frontend (TypeScript) ----------
|
||||
const tsBody = entries
|
||||
.map(([code, def]) => ` ${code}: ${toTsLiteral(def.message)},`)
|
||||
.join('\n');
|
||||
|
||||
writeGenerated(
|
||||
'frontend/src/shared/constants/error-codes.generated.ts',
|
||||
`// ${GENERATED_NOTICE}\n\n` +
|
||||
`export const ERROR_CODE_MESSAGES = {\n${tsBody}\n} as const\n\n` +
|
||||
`export type ErrorCode = keyof typeof ERROR_CODE_MESSAGES\n\n` +
|
||||
`/** 매핑 실패 시 사용하는 폴백 코드 */\n` +
|
||||
`export const FALLBACK_ERROR_CODE: ErrorCode = 'SYS_001'\n`,
|
||||
);
|
||||
|
||||
// ---------- flutter (Dart) ----------
|
||||
const dartBody = entries
|
||||
.map(([code, def]) => ` '${code}': ${toDartLiteral(def.message)},`)
|
||||
.join('\n');
|
||||
|
||||
writeGenerated(
|
||||
'flutter/lib/core/api/error_lexicon.g.dart',
|
||||
`// ${GENERATED_NOTICE}\n\n` +
|
||||
`const Map<String, String> kErrorCodeMessages = {\n${dartBody}\n};\n\n` +
|
||||
`/// 매핑 실패 시 사용하는 폴백 코드\n` +
|
||||
`const String kFallbackErrorCode = 'SYS_001';\n`,
|
||||
);
|
||||
|
||||
console.log(`\n총 ${entries.length}개 에러 코드를 동기화했습니다.`);
|
||||
Reference in New Issue
Block a user