|
| 1 | +import {ParseResult, Schema} from 'effect'; |
| 2 | +import {messageTypeSchema} from './message'; |
| 3 | + |
| 4 | +/** |
| 5 | + * 서버가 동일 필드를 boolean 또는 0/1 정수로 섞어 내려주는 경우가 있어 |
| 6 | + * 소비자에게는 boolean으로만 노출되도록 wire 단계에서 정규화한다. |
| 7 | + * 0/1 외의 숫자(NaN, 2, -1 등)는 drift 신호이므로 silent 처리하지 않고 |
| 8 | + * ResponseSchemaMismatchError로 전파되도록 transformOrFail을 사용한다. |
| 9 | + */ |
| 10 | +const booleanOrZeroOne = Schema.transformOrFail( |
| 11 | + Schema.Union(Schema.Boolean, Schema.Number), |
| 12 | + Schema.Boolean, |
| 13 | + { |
| 14 | + decode: (value, _opts, ast) => { |
| 15 | + if (typeof value === 'boolean') return ParseResult.succeed(value); |
| 16 | + if (value === 0) return ParseResult.succeed(false); |
| 17 | + if (value === 1) return ParseResult.succeed(true); |
| 18 | + return ParseResult.fail( |
| 19 | + new ParseResult.Type( |
| 20 | + ast, |
| 21 | + value, |
| 22 | + `Expected boolean, 0, or 1 but received ${String(value)}`, |
| 23 | + ), |
| 24 | + ); |
| 25 | + }, |
| 26 | + encode: value => ParseResult.succeed(value), |
| 27 | + strict: true, |
| 28 | + }, |
| 29 | +); |
| 30 | + |
| 31 | +/** |
| 32 | + * 조회 응답(getMessages/getGroupMessages)에 포함된 메시지 아이템 스키마. |
| 33 | + * |
| 34 | + * 발송용 messageSchema와 달리 서버가 저장해둔 값을 그대로 반환하므로 |
| 35 | + * - optional 필드 상당수가 null로 내려올 수 있다. |
| 36 | + * - kakaoOptions/rcsOptions 등 내부 구조가 발송 요청과 다르다(서버 정규화 포맷). |
| 37 | + * |
| 38 | + * 핵심 필드만 선언하고 타입 수준에서 검증/정규화한다. 여기에 없는 필드는 |
| 39 | + * decodeServerResponse의 onExcessProperty:'preserve' 옵션으로 런타임에 그대로 보존된다. |
| 40 | + */ |
| 41 | +export const storedMessageSchema = Schema.Struct({ |
| 42 | + messageId: Schema.optional(Schema.String), |
| 43 | + type: Schema.NullishOr(messageTypeSchema), |
| 44 | + to: Schema.optional(Schema.Union(Schema.String, Schema.Array(Schema.String))), |
| 45 | + from: Schema.NullishOr(Schema.String), |
| 46 | + text: Schema.NullishOr(Schema.String), |
| 47 | + imageId: Schema.NullishOr(Schema.String), |
| 48 | + subject: Schema.NullishOr(Schema.String), |
| 49 | + country: Schema.NullishOr(Schema.String), |
| 50 | + accountId: Schema.optional(Schema.String), |
| 51 | + groupId: Schema.optional(Schema.String), |
| 52 | + status: Schema.NullishOr(Schema.String), |
| 53 | + statusCode: Schema.NullishOr(Schema.String), |
| 54 | + reason: Schema.NullishOr(Schema.String), |
| 55 | + networkName: Schema.NullishOr(Schema.String), |
| 56 | + networkCode: Schema.NullishOr(Schema.String), |
| 57 | + customFields: Schema.optional( |
| 58 | + Schema.NullishOr(Schema.Record({key: Schema.String, value: Schema.String})), |
| 59 | + ), |
| 60 | + autoTypeDetect: Schema.optional(booleanOrZeroOne), |
| 61 | + replacement: Schema.optional(booleanOrZeroOne), |
| 62 | + resendCount: Schema.optional(Schema.Number), |
| 63 | + dateCreated: Schema.optional(Schema.String), |
| 64 | + dateUpdated: Schema.optional(Schema.String), |
| 65 | + dateProcessed: Schema.NullishOr(Schema.String), |
| 66 | + dateReceived: Schema.NullishOr(Schema.String), |
| 67 | + dateReported: Schema.NullishOr(Schema.String), |
| 68 | + // 옵션 객체는 서버 정규화 포맷(저장 형태)으로 발송 요청용 스키마와 필드가 다르다. |
| 69 | + // 상세 타이핑을 확정하려면 각 옵션별 별도 조회 스키마 정의가 필요하지만 본 PR 범위를 |
| 70 | + // 벗어나므로, 최소한 "object"임을 보장해 원시 값이 섞이는 drift를 감지할 수 있게 한다. |
| 71 | + kakaoOptions: Schema.optional( |
| 72 | + Schema.NullishOr( |
| 73 | + Schema.Record({key: Schema.String, value: Schema.Unknown}), |
| 74 | + ), |
| 75 | + ), |
| 76 | + rcsOptions: Schema.optional( |
| 77 | + Schema.NullishOr( |
| 78 | + Schema.Record({key: Schema.String, value: Schema.Unknown}), |
| 79 | + ), |
| 80 | + ), |
| 81 | + naverOptions: Schema.optional( |
| 82 | + Schema.NullishOr( |
| 83 | + Schema.Record({key: Schema.String, value: Schema.Unknown}), |
| 84 | + ), |
| 85 | + ), |
| 86 | + faxOptions: Schema.optional( |
| 87 | + Schema.NullishOr( |
| 88 | + Schema.Record({key: Schema.String, value: Schema.Unknown}), |
| 89 | + ), |
| 90 | + ), |
| 91 | + voiceOptions: Schema.optional( |
| 92 | + Schema.NullishOr( |
| 93 | + Schema.Record({key: Schema.String, value: Schema.Unknown}), |
| 94 | + ), |
| 95 | + ), |
| 96 | + replacements: Schema.optional(Schema.NullishOr(Schema.Array(Schema.Unknown))), |
| 97 | + log: Schema.optional(Schema.NullishOr(Schema.Array(Schema.Unknown))), |
| 98 | + queues: Schema.optional(Schema.NullishOr(Schema.Array(Schema.Unknown))), |
| 99 | + currentQueue: Schema.optional(Schema.NullishOr(Schema.Unknown)), |
| 100 | + clusterKey: Schema.NullishOr(Schema.String), |
| 101 | + unavailableSenderNumber: Schema.optional(Schema.NullishOr(booleanOrZeroOne)), |
| 102 | + faxPageCount: Schema.optional(Schema.NullishOr(Schema.Number)), |
| 103 | + voiceDuration: Schema.optional(Schema.NullishOr(Schema.Number)), |
| 104 | + voiceReplied: Schema.optional(Schema.NullishOr(booleanOrZeroOne)), |
| 105 | + _id: Schema.optional(Schema.String), |
| 106 | +}); |
| 107 | +export type StoredMessage = Schema.Schema.Type<typeof storedMessageSchema>; |
0 commit comments