Skip to content

Commit 30dfbfa

Browse files
메시지 전송 요청 모델의 스키마를 개선하고, 전화번호 형식을 정규화하는 phoneNumberSchema를 추가함. 또한, 빈 배열 검증을 위한 재사용 가능한 필터를 도입하고, 에이전트 스키마의 기본값을 설정하여 코드의 재사용성을 높임. 메시지 서비스에서 스키마 검증을 비동기적으로 처리하도록 변경하고, 에러 처리를 개선하여 안정성을 강화함. 새로운 테스트 케이스를 추가하여 다양한 메시지 전송 시나리오를 검증함.
1 parent d1f9989 commit 30dfbfa

3 files changed

Lines changed: 668 additions & 23 deletions

File tree

src/models/requests/messages/sendMessage.ts

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@ import {messageSchema} from '@models/base/messages/message';
22
import {Schema} from 'effect';
33
import {defaultAgentTypeSchema} from './requestConfig';
44

5+
export const phoneNumberSchema = Schema.String.pipe(
6+
Schema.transform(Schema.String, {
7+
decode: s => s.replace(/-/g, ''),
8+
encode: s => s,
9+
}),
10+
);
11+
12+
// 빈 배열 검증을 위한 재사용 가능한 필터
13+
const nonEmptyArrayFilter = <A>(schema: Schema.Schema<A>) =>
14+
Schema.Array(schema).pipe(
15+
Schema.filter(arr => arr.length > 0, {
16+
message: () => '데이터가 반드시 1건 이상 기입되어 있어야 합니다.',
17+
}),
18+
);
19+
520
/**
621
* 단건 메시지 발송 요청 모델
722
* @description 단건 메시지 발송 요청 모델
@@ -14,7 +29,15 @@ import {defaultAgentTypeSchema} from './requestConfig';
1429
* };
1530
* ```
1631
*/
17-
export const requestSendOneMessageSchema = messageSchema;
32+
export const requestSendOneMessageSchema = messageSchema.pipe(
33+
Schema.omit('to', 'from'),
34+
Schema.extend(
35+
Schema.Struct({
36+
to: Schema.Union(phoneNumberSchema, Schema.Array(phoneNumberSchema)),
37+
from: Schema.optional(phoneNumberSchema),
38+
}),
39+
),
40+
);
1841

1942
/**
2043
* 메시지 발송 요청 모델
@@ -39,7 +62,7 @@ export const requestSendOneMessageSchema = messageSchema;
3962
*/
4063
export const requestSendMessageSchema = Schema.Union(
4164
requestSendOneMessageSchema,
42-
Schema.Array(requestSendOneMessageSchema),
65+
nonEmptyArrayFilter(requestSendOneMessageSchema),
4366
);
4467

4568
export type RequestSendOneMessageSchema = Schema.Schema.Type<
@@ -53,21 +76,21 @@ export type RequestSendMessagesSchema = Schema.Schema.Type<
5376
// 기본 Agent 객체 (sdkVersion, osPlatform 값 포함) – 빈 객체 디코딩으로 생성
5477
const defaultAgentValue = Schema.decodeSync(defaultAgentTypeSchema)({});
5578

79+
// Agent 스키마의 재사용 가능한 정의
80+
const agentWithDefaultSchema = Schema.optional(defaultAgentTypeSchema).pipe(
81+
Schema.withDecodingDefault(() => defaultAgentValue),
82+
Schema.withConstructorDefault(() => defaultAgentValue),
83+
);
84+
5685
export const singleMessageSendingRequestSchema = Schema.Struct({
5786
message: requestSendOneMessageSchema,
58-
agent: Schema.optional(defaultAgentTypeSchema).pipe(
59-
Schema.withDecodingDefault(() => defaultAgentValue),
60-
Schema.withConstructorDefault(() => defaultAgentValue),
61-
),
87+
agent: agentWithDefaultSchema,
6288
});
6389

6490
export const multipleMessageSendingRequestSchema = Schema.Struct({
6591
allowDuplicates: Schema.optional(Schema.Boolean),
66-
agent: Schema.optional(defaultAgentTypeSchema).pipe(
67-
Schema.withDecodingDefault(() => defaultAgentValue),
68-
Schema.withConstructorDefault(() => defaultAgentValue),
69-
),
70-
messages: Schema.Array(requestSendOneMessageSchema),
92+
agent: agentWithDefaultSchema,
93+
messages: nonEmptyArrayFilter(requestSendOneMessageSchema),
7194
scheduledDate: Schema.optional(
7295
Schema.Union(Schema.DateFromSelf, Schema.DateFromString),
7396
),

src/services/messages/messageService.ts

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import {toCompatibleError} from '@lib/effectErrorHandler';
12
import stringifyQuery from '@lib/stringifyQuery';
23
import {
34
GetMessagesFinalizeRequest,
@@ -26,9 +27,8 @@ import {
2627
SingleMessageSentResponse,
2728
} from '@models/responses/messageResponses';
2829
import {DetailGroupMessageResponse} from '@models/responses/sendManyDetailResponse';
29-
import {Schema} from 'effect';
30+
import {Cause, Exit, Schema} from 'effect';
3031
import * as Effect from 'effect/Effect';
31-
import {defaultRuntime, runPromise as runtimeRunPromise} from 'effect/Runtime';
3232
import {
3333
BadRequestError,
3434
MessageNotReceivedError,
@@ -76,18 +76,29 @@ export default class MessageService extends DefaultService {
7676
* @throws MessageNotReceivedError 모든 메시지 접수건이 실패건으로 진행되는 경우 반환되는 에러
7777
* @throws BadRequestError 잘못된 파라미터를 기입했거나, 데이터가 아예 없는 경우 반환되는 에러
7878
*/
79-
send(
79+
async send(
8080
messages: RequestSendMessagesSchema,
8181
requestConfigParameter?: SendRequestConfigSchema,
8282
): Promise<DetailGroupMessageResponse> {
8383
const request = this.request.bind(this);
84-
const messageSchema = Schema.encodeUnknownSync(requestSendMessageSchema)(
85-
messages,
86-
);
8784

8885
const effect = Effect.gen(function* (_) {
8986
/**
90-
* 1. MessageParameter → Message 변환 및 기본 검증
87+
* 1. 스키마 검증 - Effect 내부에서 실행하여 에러를 안전하게 처리
88+
*/
89+
const messageSchema = yield* _(
90+
Effect.try({
91+
try: () =>
92+
Schema.encodeUnknownSync(requestSendMessageSchema)(messages),
93+
catch: error =>
94+
new BadRequestError({
95+
message: error instanceof Error ? error.message : String(error),
96+
}),
97+
}),
98+
);
99+
100+
/**
101+
* 2. MessageParameter → Message 변환 및 기본 검증
91102
*/
92103
const messageParameters = Array.isArray(messageSchema)
93104
? messageSchema
@@ -103,8 +114,17 @@ export default class MessageService extends DefaultService {
103114
);
104115
}
105116

106-
const decodedConfig = Schema.encodeUnknownSync(sendRequestConfigSchema)(
107-
requestConfigParameter ?? {},
117+
const decodedConfig = yield* _(
118+
Effect.try({
119+
try: () =>
120+
Schema.encodeUnknownSync(sendRequestConfigSchema)(
121+
requestConfigParameter ?? {},
122+
),
123+
catch: error =>
124+
new BadRequestError({
125+
message: error instanceof Error ? error.message : String(error),
126+
}),
127+
}),
108128
);
109129

110130
const parameterObject = {
@@ -116,8 +136,17 @@ export default class MessageService extends DefaultService {
116136
};
117137

118138
// 스키마 검증 및 파라미터 확정
119-
const parameter = Schema.decodeSync(multipleMessageSendingRequestSchema)(
120-
parameterObject,
139+
const parameter = yield* _(
140+
Effect.try({
141+
try: () =>
142+
Schema.decodeSync(multipleMessageSendingRequestSchema)(
143+
parameterObject,
144+
),
145+
catch: error =>
146+
new BadRequestError({
147+
message: error instanceof Error ? error.message : String(error),
148+
}),
149+
}),
121150
);
122151

123152
/**
@@ -158,7 +187,20 @@ export default class MessageService extends DefaultService {
158187
return response;
159188
});
160189

161-
return runtimeRunPromise(defaultRuntime, effect);
190+
// Effect를 Promise로 변환하되 에러를 표준 Error 객체로 변환
191+
const exit = await Effect.runPromiseExit(effect);
192+
193+
return Exit.match(exit, {
194+
onFailure: cause => {
195+
// Effect 에러를 표준 JavaScript Error로 변환
196+
const failure = Cause.failureOption(cause);
197+
if (failure._tag === 'Some') {
198+
throw toCompatibleError(failure.value);
199+
}
200+
throw new Error('Unknown error occurred');
201+
},
202+
onSuccess: value => value,
203+
});
162204
}
163205

164206
/**

0 commit comments

Comments
 (0)