Skip to content

refactor(core): 전역 coerceInputValues 를 걷는다 — 계약 위반을 텔레메트리 없이 정상값으로 치환 #1494

Closed

Description

@OnlyReadOneBook

작업 유형

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.blocks 1곳이고, 이미 ResponseDtoContractKonsistTest 의 허용목록에서 #790 이 추적 중이다(ci-expected-failures.json 의도된 실패). BE ReceivedTimeLetterResponse 가 미공개 구간에 List.of() 를 채우므로 서버는 이 키를 null 로 보내지 않아, 플래그 제거는 그 1곳과 독립적으로 안전하다. 자세한 근거는 PR #1495.

완료 기준

  • provideJson 에서 coerceInputValues 를 제거한다. ignoreUnknownKeys 는 유지한다 — 서버의 필드 추가는 정상적인 진화이고, 선언하지 않은 키는 어차피 쓸 수 없다.
  • 위 6곳 각각에 대해 서버가 실제로 그 키에 null 을 보내는지 BE 실코드·OpenAPI 로 판정한다.
    • 보낸다면 → 타입을 nullable 로 명시해 «null 이 올 수 있다» 를 계약에 드러낸다.
    • 안 보낸다면 → 생성자 기본값을 제거해 누락이 파싱 실패로 드러나게 한다.
  • 응답 키에 null 이 오면 기본값으로 성공하지 않고 계약 테스트가 실패한다.
  • 실제로 nullable 인 필드의 명시적 null 은 정상 역직렬화되고, 그 사유를 테스트로 고정한다.

자식 이슈

No response

참고

Activity

  1. added
    refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업
    area:core공용 코어·네트워크 기능 영역
    on Aug 29, 2026
  2. added
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 29, 2026
  3. removed
    pr-open이 이슈를 구현하는 PR 이 열려 있다 — base 가 기본 브랜치가 아니어도 드러낸다
    on Aug 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:core공용 코어·네트워크 기능 영역refactor동작 변경 없이 코드 구조·책임·경계를 개선하는 작업

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions