-
Notifications
You must be signed in to change notification settings - Fork 14
fix(responses): sync query API schemas and add runtime response validation #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Palbahngmiyine
merged 10 commits into
solapi:beta
from
Palbahngmiyine:sync/response-schema-validation-beta
Apr 17, 2026
+701
−75
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
7d2979c
feat(responses): sync query API schemas and add runtime validation
Palbahngmiyine 61247e5
fix(responses): allow nullish startKey in kakao list responses
Palbahngmiyine 90cd341
refactor(responses): address review findings for response validation
Palbahngmiyine 5276053
refactor: tighten response decoding edge cases per review round 2
Palbahngmiyine d268c5e
fix(responses): accept new message types in countForCharge; null feat…
Palbahngmiyine ff37fe5
fix(errors): redact responseBody in production ResponseSchemaMismatch…
Palbahngmiyine 300d9eb
fix(errors): redact all PII channels (validationErrors/url) in produc…
Palbahngmiyine 0af8ead
fix(errors): safe-by-default redact gate; strip url fragment
Palbahngmiyine 6df299c
refactor(stored-message): tighten option payloads to typed records
Palbahngmiyine 28c912c
fix(statistics): keep dayPeriod.statusCode typed via partial MessageT…
Palbahngmiyine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import {ParseResult, Schema} from 'effect'; | ||
| import {messageTypeSchema} from './message'; | ||
|
|
||
| /** | ||
| * 서버가 동일 필드를 boolean 또는 0/1 정수로 섞어 내려주는 경우가 있어 | ||
| * 소비자에게는 boolean으로만 노출되도록 wire 단계에서 정규화한다. | ||
| * 0/1 외의 숫자(NaN, 2, -1 등)는 drift 신호이므로 silent 처리하지 않고 | ||
| * ResponseSchemaMismatchError로 전파되도록 transformOrFail을 사용한다. | ||
| */ | ||
| const booleanOrZeroOne = Schema.transformOrFail( | ||
| Schema.Union(Schema.Boolean, Schema.Number), | ||
| Schema.Boolean, | ||
| { | ||
| decode: (value, _opts, ast) => { | ||
| if (typeof value === 'boolean') return ParseResult.succeed(value); | ||
| if (value === 0) return ParseResult.succeed(false); | ||
| if (value === 1) return ParseResult.succeed(true); | ||
| return ParseResult.fail( | ||
| new ParseResult.Type( | ||
| ast, | ||
| value, | ||
| `Expected boolean, 0, or 1 but received ${String(value)}`, | ||
| ), | ||
| ); | ||
| }, | ||
| encode: value => ParseResult.succeed(value), | ||
| strict: true, | ||
| }, | ||
| ); | ||
|
|
||
| /** | ||
| * 조회 응답(getMessages/getGroupMessages)에 포함된 메시지 아이템 스키마. | ||
| * | ||
| * 발송용 messageSchema와 달리 서버가 저장해둔 값을 그대로 반환하므로 | ||
| * - optional 필드 상당수가 null로 내려올 수 있다. | ||
| * - kakaoOptions/rcsOptions 등 내부 구조가 발송 요청과 다르다(서버 정규화 포맷). | ||
| * | ||
| * 핵심 필드만 선언하고 타입 수준에서 검증/정규화한다. 여기에 없는 필드는 | ||
| * decodeServerResponse의 onExcessProperty:'preserve' 옵션으로 런타임에 그대로 보존된다. | ||
| */ | ||
| export const storedMessageSchema = Schema.Struct({ | ||
| messageId: Schema.optional(Schema.String), | ||
| type: Schema.NullishOr(messageTypeSchema), | ||
| to: Schema.optional(Schema.Union(Schema.String, Schema.Array(Schema.String))), | ||
| from: Schema.NullishOr(Schema.String), | ||
| text: Schema.NullishOr(Schema.String), | ||
| imageId: Schema.NullishOr(Schema.String), | ||
| subject: Schema.NullishOr(Schema.String), | ||
| country: Schema.NullishOr(Schema.String), | ||
| accountId: Schema.optional(Schema.String), | ||
| groupId: Schema.optional(Schema.String), | ||
| status: Schema.NullishOr(Schema.String), | ||
| statusCode: Schema.NullishOr(Schema.String), | ||
| reason: Schema.NullishOr(Schema.String), | ||
| networkName: Schema.NullishOr(Schema.String), | ||
| networkCode: Schema.NullishOr(Schema.String), | ||
| customFields: Schema.optional( | ||
| Schema.NullishOr(Schema.Record({key: Schema.String, value: Schema.String})), | ||
| ), | ||
| autoTypeDetect: Schema.optional(booleanOrZeroOne), | ||
| replacement: Schema.optional(booleanOrZeroOne), | ||
| resendCount: Schema.optional(Schema.Number), | ||
| dateCreated: Schema.optional(Schema.String), | ||
| dateUpdated: Schema.optional(Schema.String), | ||
| dateProcessed: Schema.NullishOr(Schema.String), | ||
| dateReceived: Schema.NullishOr(Schema.String), | ||
| dateReported: Schema.NullishOr(Schema.String), | ||
| // 옵션 객체는 서버 정규화 포맷(저장 형태)으로 발송 요청용 스키마와 필드가 다르다. | ||
| // 상세 타이핑을 확정하려면 각 옵션별 별도 조회 스키마 정의가 필요하지만 본 PR 범위를 | ||
| // 벗어나므로, 최소한 "object"임을 보장해 원시 값이 섞이는 drift를 감지할 수 있게 한다. | ||
| kakaoOptions: Schema.optional( | ||
| Schema.NullishOr( | ||
| Schema.Record({key: Schema.String, value: Schema.Unknown}), | ||
| ), | ||
| ), | ||
| rcsOptions: Schema.optional( | ||
| Schema.NullishOr( | ||
| Schema.Record({key: Schema.String, value: Schema.Unknown}), | ||
| ), | ||
| ), | ||
| naverOptions: Schema.optional( | ||
| Schema.NullishOr( | ||
| Schema.Record({key: Schema.String, value: Schema.Unknown}), | ||
| ), | ||
| ), | ||
| faxOptions: Schema.optional( | ||
| Schema.NullishOr( | ||
| Schema.Record({key: Schema.String, value: Schema.Unknown}), | ||
| ), | ||
| ), | ||
| voiceOptions: Schema.optional( | ||
| Schema.NullishOr( | ||
| Schema.Record({key: Schema.String, value: Schema.Unknown}), | ||
| ), | ||
| ), | ||
| replacements: Schema.optional(Schema.NullishOr(Schema.Array(Schema.Unknown))), | ||
| log: Schema.optional(Schema.NullishOr(Schema.Array(Schema.Unknown))), | ||
| queues: Schema.optional(Schema.NullishOr(Schema.Array(Schema.Unknown))), | ||
| currentQueue: Schema.optional(Schema.NullishOr(Schema.Unknown)), | ||
| clusterKey: Schema.NullishOr(Schema.String), | ||
| unavailableSenderNumber: Schema.optional(Schema.NullishOr(booleanOrZeroOne)), | ||
| faxPageCount: Schema.optional(Schema.NullishOr(Schema.Number)), | ||
| voiceDuration: Schema.optional(Schema.NullishOr(Schema.Number)), | ||
| voiceReplied: Schema.optional(Schema.NullishOr(booleanOrZeroOne)), | ||
| _id: Schema.optional(Schema.String), | ||
| }); | ||
| export type StoredMessage = Schema.Schema.Type<typeof storedMessageSchema>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URL 파싱에 실패했을 때의 fallback 로직에서
userinfo(예:user:password@)가 포함된 경우 PII가 유출될 가능성이 있습니다. 현재는?,#,;문자만 체크하여 그 이후를 마스킹하고 있는데, URL의 권한(authority) 부분에 포함될 수 있는 민감 정보도 고려하는 것이 안전합니다. 파싱이 불가능한 비정상적인 URL 문자열이더라도 보수적으로 전체를 마스킹하거나, 최소한 프로토콜 구분자(//) 이후의 첫 번째/이전 구간에@가 있는지 확인하여 마스킹하는 로직을 검토해 주세요.