Skip to content

Commit 4274811

Browse files
feat(bms): Enhance error handling and add BMS message types
- Introduced a new function `extractDefectInfo` to improve error reporting for unexpected defects in the effect error handler. - Updated `formatCauseForProduction` to include defect information in error messages. - Enhanced `runSafeSync` and `runSafePromise` to throw more descriptive errors, including specific names for unexpected defects and unhandled exits. - Expanded the `FileType` in `groupMessageRequest.ts` to support additional BMS types. - Added utility functions and test cases for BMS message types, including new test assets for image uploads. These changes improve the robustness of error handling and expand the capabilities of the BMS messaging service.
1 parent 5b9602e commit 4274811

6 files changed

Lines changed: 1586 additions & 17 deletions

File tree

src/lib/effectErrorHandler.ts

Lines changed: 103 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,42 @@ export const formatError = (error: unknown): string => {
4141
return String(error);
4242
};
4343

44+
/**
45+
* Defect(예측되지 않은 에러)에서 정보 추출
46+
*/
47+
const extractDefectInfo = (
48+
defect: unknown,
49+
): {summary: string; details: string} => {
50+
// Effect Tagged Error인 경우
51+
if (defect && typeof defect === 'object' && '_tag' in defect) {
52+
const tag = (defect as {_tag: string})._tag;
53+
const message =
54+
'message' in defect ? String((defect as {message: unknown}).message) : '';
55+
return {
56+
summary: `${tag}${message ? `: ${message}` : ''}`,
57+
details: `Tagged Error [${tag}]: ${JSON.stringify(defect, null, 2)}`,
58+
};
59+
}
60+
61+
// 일반 객체인 경우
62+
if (defect !== null && typeof defect === 'object') {
63+
const keys = Object.keys(defect);
64+
const summary =
65+
keys.length > 0
66+
? `Object with keys: ${keys.slice(0, 3).join(', ')}${keys.length > 3 ? '...' : ''}`
67+
: 'Empty object';
68+
return {
69+
summary,
70+
details: JSON.stringify(defect, null, 2),
71+
};
72+
}
73+
74+
return {
75+
summary: String(defect),
76+
details: `Value (${typeof defect}): ${String(defect)}`,
77+
};
78+
};
79+
4480
// Effect Cause를 프로덕션용으로 포맷팅
4581
export const formatCauseForProduction = (
4682
cause: Cause.Cause<unknown>,
@@ -49,7 +85,16 @@ export const formatCauseForProduction = (
4985
if (failure._tag === 'Some') {
5086
return formatError(failure.value);
5187
}
52-
return 'Unknown error occurred';
88+
89+
// Defect 정보도 포함
90+
const defects = Cause.defects(cause);
91+
if (defects.length > 0) {
92+
const firstDefect = Chunk.unsafeGet(defects, 0);
93+
const info = extractDefectInfo(firstDefect);
94+
return `Unexpected error: ${info.summary}`;
95+
}
96+
97+
return 'Effect execution failed';
5398
};
5499

55100
// Effect 프로그램의 실행 결과를 안전하게 처리
@@ -62,19 +107,30 @@ export const runSafeSync = <E, A>(effect: Effect.Effect<A, E>): A => {
62107
if (failure._tag === 'Some') {
63108
throw toCompatibleError(failure.value);
64109
}
110+
// 예측되지 않은 예외(Defect)인지 확인
65111
const defects = Cause.defects(cause);
66112
if (defects.length > 0) {
67113
const firstDefect = Chunk.unsafeGet(defects, 0);
68114
if (firstDefect instanceof Error) {
69115
throw firstDefect;
70116
}
71117
const isProduction = process.env.NODE_ENV === 'production';
118+
const defectInfo = extractDefectInfo(firstDefect);
72119
const message = isProduction
73-
? `Unexpected error: ${String(firstDefect)}`
74-
: `Unexpected error: ${String(firstDefect)}\nCause: ${Cause.pretty(cause)}`;
75-
throw new Error(message);
120+
? `Unexpected error: ${defectInfo.summary}`
121+
: `Unexpected error: ${defectInfo.details}\nCause: ${Cause.pretty(cause)}`;
122+
const error = new Error(message);
123+
error.name = 'UnexpectedDefectError';
124+
throw error;
76125
}
77-
throw new Error(`Unhandled Exit: ${Cause.pretty(cause)}`);
126+
// 그 외 (예: 중단)의 경우
127+
const isProduction = process.env.NODE_ENV === 'production';
128+
const message = isProduction
129+
? 'Effect execution failed unexpectedly'
130+
: `Unhandled Effect Exit:\n${Cause.pretty(cause)}`;
131+
const error = new Error(message);
132+
error.name = 'UnhandledExitError';
133+
throw error;
78134
},
79135
onSuccess: value => value,
80136
});
@@ -98,21 +154,26 @@ export const runSafePromise = <E, A>(
98154
if (defects.length > 0) {
99155
const firstDefect = Chunk.unsafeGet(defects, 0);
100156
if (firstDefect instanceof Error) {
101-
// 원본 Error 객체를 그대로 반환
102157
return Promise.reject(firstDefect);
103158
}
104-
// Error 객체가 아니면 환경에 따라 상세 정보 포함
105159
const isProduction = process.env.NODE_ENV === 'production';
160+
const defectInfo = extractDefectInfo(firstDefect);
106161
const message = isProduction
107-
? `Unexpected error: ${String(firstDefect)}`
108-
: `Unexpected error: ${String(firstDefect)}\nCause: ${Cause.pretty(cause)}`;
109-
return Promise.reject(new Error(message));
162+
? `Unexpected error: ${defectInfo.summary}`
163+
: `Unexpected error: ${defectInfo.details}\nCause: ${Cause.pretty(cause)}`;
164+
const error = new Error(message);
165+
error.name = 'UnexpectedDefectError';
166+
return Promise.reject(error);
110167
}
111168

112-
// 3. 그 외 (예: 중단)의 경우, Cause를 문자열로 변환하여 반환
113-
return Promise.reject(
114-
new Error(`Unhandled Exit: ${Cause.pretty(cause)}`),
115-
);
169+
// 3. 그 외 (예: 중단)의 경우
170+
const isProduction = process.env.NODE_ENV === 'production';
171+
const message = isProduction
172+
? 'Effect execution failed unexpectedly'
173+
: `Unhandled Effect Exit:\n${Cause.pretty(cause)}`;
174+
const error = new Error(message);
175+
error.name = 'UnhandledExitError';
176+
return Promise.reject(error);
116177
},
117178
onSuccess: value => Promise.resolve(value),
118179
}),
@@ -325,10 +386,36 @@ export const toCompatibleError = (effectError: unknown): Error => {
325386
return error;
326387
}
327388

389+
// Unknown 에러 타입에 대한 개선된 처리
390+
// Tagged Error 확인 (_tag 속성 존재 여부)
391+
if (effectError && typeof effectError === 'object' && '_tag' in effectError) {
392+
const taggedError = effectError as {_tag: string};
393+
const formatted = formatError(effectError);
394+
const error = new Error(formatted);
395+
error.name = `UnknownTaggedError_${taggedError._tag}`;
396+
if (!isProduction) {
397+
Object.defineProperty(error, 'originalError', {
398+
value: effectError,
399+
writable: false,
400+
enumerable: true,
401+
});
402+
}
403+
if (isProduction) {
404+
delete error.stack;
405+
}
406+
return error;
407+
}
408+
328409
const formatted = formatError(effectError);
329-
// 하위 호환성을 위해 여전히 Error 사용하지만 스택 제거
330410
const error = new Error(formatted);
331-
error.name = 'FromSolapiError';
411+
error.name = 'UnknownSolapiError';
412+
if (!isProduction) {
413+
Object.defineProperty(error, 'originalError', {
414+
value: effectError,
415+
writable: false,
416+
enumerable: true,
417+
});
418+
}
332419
if (isProduction) {
333420
delete error.stack;
334421
}

src/models/requests/messages/groupMessageRequest.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,18 @@ export type FileIds = {
4141
fileIds: ReadonlyArray<string>;
4242
};
4343

44-
export type FileType = 'KAKAO' | 'MMS' | 'DOCUMENT' | 'RCS' | 'FAX';
44+
export type FileType =
45+
| 'KAKAO'
46+
| 'MMS'
47+
| 'DOCUMENT'
48+
| 'RCS'
49+
| 'FAX'
50+
| 'BMS'
51+
| 'BMS_WIDE'
52+
| 'BMS_WIDE_MAIN_ITEM_LIST'
53+
| 'BMS_WIDE_SUB_ITEM_LIST'
54+
| 'BMS_CAROUSEL_FEED_LIST'
55+
| 'BMS_CAROUSEL_COMMERCE_LIST';
4556

4657
export type FileUploadRequest = {
4758
file: string;

test/assets/example-1to1.jpg

36.6 KB
Loading

test/assets/example-2to1.jpg

45.4 KB
Loading

0 commit comments

Comments
 (0)