refactor(polymorphic): 컨테이너 확장 중복 제거 + null outcome 일관화#24
Conversation
contains/decodeNil 존재 판별을 캡슐화하는 내부 헬퍼를 도입해 이후 디코딩 오버로드들의 중복 보일러플레이트 제거의 기반을 마련한다.
6개 decode 컨테이너 확장의 contains/decodeNil 존재 판별을 공통 헬퍼로 통일한다. 또한 release 빌드에서 PolymorphicLossyArrayValue.decodeIfPresent 가 null 값에 대해 .valueWasNil 을 반환하도록 하여 decode(_:forKey:) 와 동작을 일치시킨다.
공유 헬퍼의 null 분기를 보호하기 위한 특성화 테스트를 추가한다.
…오버로드 통합 거의 동일한 4개의 KeyedEncodingContainer.encode 오버로드를 제약 제네릭 1개 + wrapper 별 conformance 로 축약한다.
📝 WalkthroughWalkthroughAdds a shared ChangesPolymorphic Codable Refactor
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant KeyedDecodingContainer
participant PolymorphicKeyPresenceHelper
participant PolymorphicWrapper
Client->>KeyedDecodingContainer: decodeIfPresent(forKey:)
KeyedDecodingContainer->>PolymorphicKeyPresenceHelper: polymorphicKeyPresence(forKey:)
PolymorphicKeyPresenceHelper-->>KeyedDecodingContainer: .missing / .null / .present
alt .missing
KeyedDecodingContainer-->>Client: nil
else .null
KeyedDecodingContainer->>PolymorphicWrapper: init(outcome: .valueWasNil)
PolymorphicWrapper-->>Client: wrapper with valueWasNil
else .present
KeyedDecodingContainer->>PolymorphicWrapper: decode via superDecoder(forKey:)
PolymorphicWrapper-->>Client: wrapper with decodedSuccessfully
end
Suggested labels: Nice tidy-up here — consolidating the 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer+PolymorphicLossyArrayValue.swift (1)
74-106: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRestore lossy fallback for present array values
decodeIfPresentshould mirrordecode(_:forKey:)here. As written, a present-but-invalid JSON value now throws instead of degrading to[], which breaks the documented behavior ofPolymorphicLossyArrayValuefor malformed arrays.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer`+PolymorphicLossyArrayValue.swift around lines 74 - 106, `decodeIfPresent(_:forKey:)` in `KeyedDecodingContainer+PolymorphicLossyArrayValue` no longer preserves the lossy array behavior for present values, because the `.present` path directly throws from `PolymorphicLossyArrayValue(from:)` instead of recovering to an empty array. Update the `.present` branch to mirror `decode(_:forKey:)` by decoding through the same lossy path and, on malformed input, returning a `PolymorphicLossyArrayValue` with `wrappedValue: []` and a recovered outcome instead of propagating the decode error.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In
`@Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer`+PolymorphicLossyArrayValue.swift:
- Around line 74-106: `decodeIfPresent(_:forKey:)` in
`KeyedDecodingContainer+PolymorphicLossyArrayValue` no longer preserves the
lossy array behavior for present values, because the `.present` path directly
throws from `PolymorphicLossyArrayValue(from:)` instead of recovering to an
empty array. Update the `.present` branch to mirror `decode(_:forKey:)` by
decoding through the same lossy path and, on malformed input, returning a
`PolymorphicLossyArrayValue` with `wrappedValue: []` and a recovered outcome
instead of propagating the decode error.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro
Run ID: fbe55e7d-82ec-4d63-8eb7-6507796fd8ae
📒 Files selected for processing (12)
Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer+DefaultEmptyPolymorphicArrayValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer+LossyOptionalPolymorphicValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer+OptionalPolymorphicArrayValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer+OptionalPolymorphicValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer+PolymorphicKeyPresence.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedDecodingContainer+PolymorphicLossyArrayValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+LossyOptionalPolymorphicValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+OmitNilPolymorphicEncodable.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+OptionalPolymorphicArrayValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+OptionalPolymorphicLossyArrayValue.swiftSources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+OptionalPolymorphicValue.swiftTests/KarrotCodableKitTests/PolymorphicCodable/ArrayValue/PolymorphicLossyArrayNullOutcomeTests.swift
💤 Files with no reviewable changes (4)
- Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+OptionalPolymorphicArrayValue.swift
- Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+LossyOptionalPolymorphicValue.swift
- Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+OptionalPolymorphicValue.swift
- Sources/KarrotCodableKit/PolymorphicCodable/Extensions/KeyedEncodingContainer+OptionalPolymorphicLossyArrayValue.swift
배경
polymorphic 프로퍼티 래퍼의 디코딩/인코딩은
KeyedDecodingContainer/KeyedEncodingContainer확장 10개 파일에 흩어져 있고, 각 파일이 아래의 동일한 존재 판별 절차를 복붙 수준으로 반복합니다.이 구조는 (a) 동기화가 어렵고 (b) 분기 하나만 어긋나도 버그가 됩니다. 실제로 어긋난 곳이 있었습니다 —
KeyedDecodingContainer+PolymorphicLossyArrayValue:null값일 때 outcome (release)decode(_:forKey:).valueWasNildecodeIfPresent(_:forKey:).decodedSuccessfully← 불일치변경사항
1) 존재 판별 일원화 —
PolymorphicKeyPresencecontains/decodeNil순서를 단일 헬퍼로 캡슐화하고, 6개 decode 오버로드를 모두 이 헬퍼로 경유시킵니다.2)
nulloutcome 일관화헬퍼의
.null분기가 단일 지점이 되면서 위 불일치가 구조적으로 해소됩니다.3) omit-nil encode 통합 —
OmitNilPolymorphicEncodablenil이면 키를 생략하는 4개의 거의 동일한encode오버로드를 제약 제네릭 1개로 축약합니다.변경 파일
KeyedDecodingContainer+PolymorphicKeyPresence.swift,KeyedEncodingContainer+OmitNilPolymorphicEncodable.swift,Tests/…/PolymorphicLossyArrayNullOutcomeTests.swiftKeyedDecodingContainer+{OptionalPolymorphicValue, LossyOptionalPolymorphicValue, OptionalPolymorphicArrayValue, OptionalPolymorphicLossyArrayValue, DefaultEmptyPolymorphicArrayValue, PolymorphicLossyArrayValue}.swiftKeyedEncodingContainer+{OptionalPolymorphicValue, LossyOptionalPolymorphicValue, OptionalPolymorphicArrayValue, OptionalPolymorphicLossyArrayValue}.swift12 files changed, +214 / −201
변경했을 때의 트레이드 오프
.present는 의도적으로Decoder를 들고 있지 않습니다.OptionalPolymorphicArrayValue만nestedUnkeyedContainer(forKey:)를 쓰고 나머지 5개는superDecoder(forKey:)를 쓰기 때문에, 헬퍼가 특정 컨테이너를 강제하면 경로가 어긋납니다. 대신 각 arm 이 자기 경로를 직접 획득 → 판별 후 컨테이너 획득이 한 번 더 일어나지만(무시 가능) 기존 동작을 정확히 보존합니다.OmitNilPolymorphicEncodable이 API 표면에 추가됩니다. 또 오버로드 해소가 "제약 제네릭이 stdlibencode<T: Encodable>보다 더 구체적" 이라는 규칙에 의존합니다 → omit-nil 테스트로 검증했습니다.nulloutcome 수정의 실이득은 코드 일관성입니다(런타임 관측 영향은 아래 "기타" 참고).테스트 방법
swift test -c debug/swift test -c release— 전부 통과(실패 0). 총 360 테스트로 리팩터 전과 동일.null/missingoutcome 를 특성화하는PolymorphicLossyArrayNullOutcomeTests신규 추가.OptionalPolymorphicValueTests(예:encodingOptionalPolymorphicValueOmitsNilFields) 등으로 검증 — 오버로드 통합 후에도 green.기타
nulloutcome 불일치는 release 전용이며 사실상 관측 불가입니다. release 에서ResilientDecodingOutcome는 크기 0의 미사용 struct 이고wrappedValue는 이전에도 이후에도[]입니다. 따라서 이는 기능 버그가 아니라 일관성 정리이며, 헬퍼 도입으로 자연히 해결됩니다.OmitNilPolymorphicEncodable은 추가만 합니다.Summary by CodeRabbit
New Features
Bug Fixes
Tests