Skip to content

Commit 434677e

Browse files
authored
Merge pull request #11 from daangn/feature/elon/add-resilient-decoding
feat: Add ResilientDecodingErrorReporter
2 parents f022148 + 8884c8b commit 434677e

71 files changed

Lines changed: 4283 additions & 231 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
runs-on: macos-15
2020
strategy:
2121
matrix:
22-
xcode: ['16.1']
22+
xcode: ['16.4']
2323
config: ['debug', 'release']
2424

2525
steps:

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,10 @@ extension User: Codable {
120120

121121
### PolymorphicCodable
122122

123-
`PolymorphicCodable` provides functionality to easily decode polymorphic types from JSON. It includes several interfaces like `PolymorphicIdentifiable`, `PolymorphicCodableStrategy`, and property wrappers like `PolymorphicValue` and `PolymorphicArrayValue`.
123+
`PolymorphicCodable` provides functionality to easily decode polymorphic types from JSON. This functionality provides Swift implementation for the OpenAPI Specification's `oneOf` pattern, allowing type-safe handling of multiple possible schemas. It includes several interfaces like `PolymorphicIdentifiable`, `PolymorphicCodableStrategy`, and property wrappers like `PolymorphicValue` and `PolymorphicArrayValue`.
124124

125125
**Parameters:**
126-
- `identifierCodingKey`: Specifies the JSON key used to determine the type of object being decoded. Defaults to "type" if not specified, allowing you to omit this parameter when using the default value.
126+
- `identifierCodingKey`: Specifies the JSON key used to determine the type of object being decoded. This parameter corresponds to the `discriminator.propertyName` in OpenAPI Specification's oneOf definition. Defaults to "type" if not specified, allowing you to omit this parameter when using the default value.
127127
- `fallbackType`: Defines a default type to use when the identifier in the JSON doesn't match any of the registered types, preventing decoding failures for unknown types. If this parameter is omitted and an unknown type identifier is encountered during decoding, a decoding error will be thrown.
128128

129129
The following example demonstrates how to decode dynamic JSON content where the type of object is determined at runtime:
@@ -277,3 +277,4 @@ This project is licensed under the MIT. See LICENSE for details.
277277
- PolymorphicCodable was inspired by [Encode and decode polymorphic types in Swift](https://nilcoalescing.com/blog/BringingPolymorphismToCodable/).
278278
- AnyCodable was adapted from [Flight-School/AnyCodable](https://github.com/Flight-School/AnyCodable).
279279
- BetterCodable was adapted from [marksands/BetterCodable](https://github.com/marksands/BetterCodable).
280+
- ResilientDecodingErrorReporter was adapted from [airbnb/ResilientDecoding](https://github.com/airbnb/ResilientDecoding).

Sources/KarrotCodableKit/BetterCodable/DataValue/DataValue.swift

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,33 @@ public protocol DataValueCodableStrategy {
2525
public struct DataValue<Coder: DataValueCodableStrategy> {
2626
public var wrappedValue: Coder.DataType
2727

28+
public let outcome: ResilientDecodingOutcome
29+
2830
public init(wrappedValue: Coder.DataType) {
2931
self.wrappedValue = wrappedValue
32+
self.outcome = .decodedSuccessfully
33+
}
34+
35+
init(wrappedValue: Coder.DataType, outcome: ResilientDecodingOutcome) {
36+
self.wrappedValue = wrappedValue
37+
self.outcome = outcome
3038
}
39+
40+
#if DEBUG
41+
public var projectedValue: ResilientProjectedValue { ResilientProjectedValue(outcome: outcome) }
42+
#endif
3143
}
3244

3345
extension DataValue: Decodable {
3446
public init(from decoder: Decoder) throws {
35-
self.wrappedValue = try Coder.decode(String(from: decoder))
47+
do {
48+
let stringValue = try String(from: decoder)
49+
self.wrappedValue = try Coder.decode(stringValue)
50+
self.outcome = .decodedSuccessfully
51+
} catch {
52+
decoder.reportError(error)
53+
throw error
54+
}
3655
}
3756
}
3857

@@ -42,6 +61,16 @@ extension DataValue: Encodable {
4261
}
4362
}
4463

45-
extension DataValue: Equatable where Coder.DataType: Equatable {}
46-
extension DataValue: Hashable where Coder.DataType: Hashable {}
64+
extension DataValue: Equatable where Coder.DataType: Equatable {
65+
public static func == (lhs: Self, rhs: Self) -> Bool {
66+
lhs.wrappedValue == rhs.wrappedValue
67+
}
68+
}
69+
70+
extension DataValue: Hashable where Coder.DataType: Hashable {
71+
public func hash(into hasher: inout Hasher) {
72+
hasher.combine(wrappedValue)
73+
}
74+
}
75+
4776
extension DataValue: Sendable where Coder.DataType: Sendable {}

Sources/KarrotCodableKit/BetterCodable/DateValue/DateValue.swift

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,33 @@ public protocol DateValueCodableStrategy {
2525
public struct DateValue<Formatter: DateValueCodableStrategy> {
2626
public var wrappedValue: Date
2727

28+
public let outcome: ResilientDecodingOutcome
29+
2830
public init(wrappedValue: Date) {
2931
self.wrappedValue = wrappedValue
32+
self.outcome = .decodedSuccessfully
3033
}
34+
35+
init(wrappedValue: Date, outcome: ResilientDecodingOutcome) {
36+
self.wrappedValue = wrappedValue
37+
self.outcome = outcome
38+
}
39+
40+
#if DEBUG
41+
public var projectedValue: ResilientProjectedValue { ResilientProjectedValue(outcome: outcome) }
42+
#endif
3143
}
3244

3345
extension DateValue: Decodable where Formatter.RawValue: Decodable {
3446
public init(from decoder: Decoder) throws {
35-
let value = try Formatter.RawValue(from: decoder)
36-
self.wrappedValue = try Formatter.decode(value)
47+
do {
48+
let value = try Formatter.RawValue(from: decoder)
49+
self.wrappedValue = try Formatter.decode(value)
50+
self.outcome = .decodedSuccessfully
51+
} catch {
52+
decoder.reportError(error)
53+
throw error
54+
}
3755
}
3856
}
3957

@@ -45,7 +63,7 @@ extension DateValue: Encodable where Formatter.RawValue: Encodable {
4563
}
4664

4765
extension DateValue: Equatable {
48-
public static func == (lhs: DateValue<Formatter>, rhs: DateValue<Formatter>) -> Bool {
66+
public static func == (lhs: Self, rhs: Self) -> Bool {
4967
lhs.wrappedValue == rhs.wrappedValue
5068
}
5169
}

Sources/KarrotCodableKit/BetterCodable/DateValue/OptionalDateValue.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,39 @@ public protocol OptionalDateValueCodableStrategy {
2626
public struct OptionalDateValue<Formatter: OptionalDateValueCodableStrategy> {
2727
public var wrappedValue: Date?
2828

29+
public let outcome: ResilientDecodingOutcome
30+
2931
public init(wrappedValue: Date?) {
3032
self.wrappedValue = wrappedValue
33+
self.outcome = .decodedSuccessfully
3134
}
35+
36+
init(wrappedValue: Date?, outcome: ResilientDecodingOutcome) {
37+
self.wrappedValue = wrappedValue
38+
self.outcome = outcome
39+
}
40+
41+
#if DEBUG
42+
public var projectedValue: ResilientProjectedValue { ResilientProjectedValue(outcome: outcome) }
43+
#endif
3244
}
3345

3446
extension OptionalDateValue: Decodable where Formatter.RawValue: Decodable {
3547
public init(from decoder: Decoder) throws {
3648
do {
3749
let value = try Formatter.RawValue(from: decoder)
38-
self.wrappedValue = try Formatter.decode(value)
50+
do {
51+
self.wrappedValue = try Formatter.decode(value)
52+
self.outcome = .decodedSuccessfully
53+
} catch {
54+
decoder.reportError(error)
55+
throw error
56+
}
3957
} catch DecodingError.valueNotFound(let rawType, _) where rawType == Formatter.RawValue.self {
4058
self.wrappedValue = nil
59+
self.outcome = .valueWasNil
4160
} catch {
61+
decoder.reportError(error)
4262
throw error
4363
}
4464
}
@@ -52,7 +72,7 @@ extension OptionalDateValue: Encodable where Formatter.RawValue: Encodable {
5272
}
5373

5474
extension OptionalDateValue: Equatable {
55-
public static func == (lhs: OptionalDateValue<Formatter>, rhs: OptionalDateValue<Formatter>) -> Bool {
75+
public static func == (lhs: Self, rhs: Self) -> Bool {
5676
lhs.wrappedValue == rhs.wrappedValue
5777
}
5878
}

0 commit comments

Comments
 (0)