Repository navigation
refactor(core): 전역 coerceInputValues 를 걷는다 — 계약 위반을 텔레메트리 없이 정상값으로 치환 #1494
Description
작업 유형
refactor — 동작 변경 없는 구조·책임·경계 개선
주 담당 모듈
core — 코어
개요
NetworkModule.provideJson 의 coerceInputValues = true 는 non-null 프로퍼티에 null 이 와도 기본값이 있으면 조용히 그 값으로 바꾼다. 전 앱의 모든 응답 디코딩에 걸리고, 치환이 일어났다는 기록은 어디에도 남지 않는다.
Json {
ignoreUnknownKeys = true
coerceInputValues = true // ← 이 이슈의 대상
}이 팀이 최근 세운 기준과 방향이 반대다. #1010 은 계약 위반을 만나면 제외하되 ErrorReporter 로 세어 보고하도록 만들었고(receiver_list_decoding / receiver_list_mapping), #789 는 «누락 계약을 정상값으로 은폐» 하는 DTO 기본값을 mindrecord 에서 걷어냈다. 필드 단위로 막아 온 것을 전역 플래그 하나가 되돌리고 있는 구조다.
| #1010 이 세운 기준 | coerceInputValues = true |
|
|---|---|---|
| 계약 위반을 만나면 | 제외하되 세어서 보고 | 조용히 기본값으로 치환 |
| 관측 가능성 | 있음 (ErrorReporter) |
없음 |
| 적용 범위 | category 한 필드 |
전 앱 |
실제 노출 범위
coercion 이 걸릴 수 있는 자리 — non-null 인데 생성자 기본값이 있는 응답 DTO 프로퍼티 — 는 develop 542a7b1f7 전 트리에 6곳뿐이다.
| 위치 | 프로퍼티 |
|---|---|
ReceivedTimeLetterDto.kt#L11 |
blocks: List<TimeLetterBlockDto> = emptyList() |
AfternoteDto.kt#L11 |
category: String = "GALLERY" (@EncodeDefault) |
AfternoteDto.kt#L21 |
category: String = "PLAYLIST" (@EncodeDefault) |
AfternoteDto.kt#L25 |
receivers: List<AfternoteReceiverRefDto> = emptyList() |
AfternoteDto.kt#L36 |
receivers: List<AfternoteReceiverRefDto> = emptyList() |
AfternoteDto.kt#L76 |
songs: List<AfternoteSongDto> = emptyList() |
즉 끄더라도 영향을 받는 곳이 6곳뿐이고, 켜 둬서 얻는 것도 그 6곳뿐이다. category 두 건은 @EncodeDefault 가 붙은 요청 직렬화용이라 디코딩 coercion 과 무관할 가능성이 높다 — 자리별로 따로 판정해야 한다.
[2026-08-29 실측 정정] 위 6곳 중 5곳은 요청 DTO 였다. 요청 DTO 는 인코딩만 하므로
coerceInputValues와 무관하다. 실제 대상은ReceivedTimeLetterDto.blocks1곳이고, 이미ResponseDtoContractKonsistTest의 허용목록에서 #790 이 추적 중이다(ci-expected-failures.json의도된 실패). BEReceivedTimeLetterResponse가 미공개 구간에List.of()를 채우므로 서버는 이 키를null로 보내지 않아, 플래그 제거는 그 1곳과 독립적으로 안전하다. 자세한 근거는 PR #1495.
완료 기준
provideJson에서coerceInputValues를 제거한다.ignoreUnknownKeys는 유지한다 — 서버의 필드 추가는 정상적인 진화이고, 선언하지 않은 키는 어차피 쓸 수 없다.- 위 6곳 각각에 대해 서버가 실제로 그 키에
null을 보내는지 BE 실코드·OpenAPI 로 판정한다.- 보낸다면 → 타입을 nullable 로 명시해 «null 이 올 수 있다» 를 계약에 드러낸다.
- 안 보낸다면 → 생성자 기본값을 제거해 누락이 파싱 실패로 드러나게 한다.
- 응답 키에
null이 오면 기본값으로 성공하지 않고 계약 테스트가 실패한다. - 실제로 nullable 인 필드의 명시적
null은 정상 역직렬화되고, 그 사유를 테스트로 고정한다.
자식 이슈
No response
참고
- 기준을 세운 선행 작업: 카테고리 실패의 폭을 항목 단위로 좁힌다 — 와이어 관용·도메인 엄격, 매퍼가 기각·보고 #1010 (제외를 세어 보고), refactor(mindrecord): DTO 기본값·불필요 nullable 제거 — 누락 계약을 정상값으로 은폐 #789 (DTO 기본값이 누락 계약을 은폐), refactor(dto): 응답 DTO 를 BE 실코드에 동기화 — 유령 필드 9건·누락 필드 21건 #676 (응답 DTO 를 BE 실코드에 동기화). 셋 다 필드 단위 로 접근했고, 이 이슈는 같은 문제를 전역 설정 쪽에서 닫는다.
useAlternativeNames는 이 이슈 범위가 아니다.@JsonNames를 명시적으로 단 프로퍼티에만 작동하므로 계약 위반을 은폐하지 않는다.- 계약 근거는 현재 OpenAPI 와
Afternote-BErelease 실코드로 확인한다.
Activity
refactor(core): 계약 테스트도 프로덕션 Json 설정에 맞춘다 (#1494)