From 290ccb975d74e9f0e176fc14b6b5a6868b19d96c Mon Sep 17 00:00:00 2001 From: Steven Sherry Date: Thu, 26 Sep 2024 09:44:43 -0500 Subject: [PATCH] chore(ios): address linter errors for Codable implementation chore(ios): address failing Codable tests when run in a locale other then en_US --- .../Capacitor/Codable/JSValueDecoder.swift | 97 +++++++++++-------- .../Capacitor/Codable/JSValueEncoder.swift | 9 +- .../CodableTests/DateCodableTests.swift | 1 + 3 files changed, 66 insertions(+), 41 deletions(-) diff --git a/ios/Capacitor/Capacitor/Codable/JSValueDecoder.swift b/ios/Capacitor/Capacitor/Codable/JSValueDecoder.swift index b1f7081db..347fbd2f7 100644 --- a/ios/Capacitor/Capacitor/Codable/JSValueDecoder.swift +++ b/ios/Capacitor/Capacitor/Codable/JSValueDecoder.swift @@ -127,55 +127,72 @@ extension _JSValueDecoder: Decoder { SingleValueContainer(data: data, codingPath: codingPath, userInfo: userInfo, options: options) } + // force casting is fine becasue we've already determined that T is the type in the case + // the swift standard library also force casts in their similar functions + // https://github.com/swiftlang/swift-foundation/blob/da80d51fa3e77f3e7ed57c4300a870689e755713/Sources/FoundationEssentials/JSON/JSONEncoder.swift#L1140 + //swiftlint:disable force_cast fileprivate func decodeData(as type: T.Type) throws -> T where T: Decodable { switch type { case is Date.Type: - switch options.dateStrategy { - case .deferredToDate: - return try T(from: self) - case .secondsSince1970: - guard let value = data as? NSNumber else { throw DecodingError.dataCorrupted(data, target: Double.self, codingPath: codingPath) } - return Date(timeIntervalSince1970: value.doubleValue) as! T - case .millisecondsSince1970: - guard let value = data as? NSNumber else { throw DecodingError.dataCorrupted(data, target: Double.self, codingPath: codingPath) } - return Date(timeIntervalSince1970: value.doubleValue / Double(MSEC_PER_SEC)) as! T - case .iso8601: - guard let value = data as? String else { throw DecodingError.dataCorrupted(data, target: String.self, codingPath: codingPath) } - let formatter = ISO8601DateFormatter() - guard let date = formatter.date(from: value) else { throw DecodingError.dataCorrupted(value, target: Date.self, codingPath: codingPath) } - return date as! T - case .formatted(let formatter): - guard let value = data as? String else { throw DecodingError.dataCorrupted(data, target: String.self, codingPath: codingPath) } - guard let date = formatter.date(from: value) else { throw DecodingError.dataCorrupted(value, target: Date.self, codingPath: codingPath) } - return date as! T - case .custom(let decode): - return try decode(self) as! T - @unknown default: - return try T(from: self) - } + return try decodeDate() as! T case is URL.Type: - guard let str = data as? String, - let url = URL(string: str) - else { throw DecodingError.dataCorrupted(data, target: URL.self, codingPath: codingPath) } - - return url as! T + return try decodeUrl() as! T case is Data.Type: - switch options.dataStrategy { - case .deferredToData: - return try T(from: self) - case .base64: - guard let value = data as? String else { throw DecodingError.dataCorrupted(data, target: String.self, codingPath: codingPath) } - guard let data = Data(base64Encoded: value) else { throw DecodingError.dataCorrupted(value, target: Data.self, codingPath: codingPath) } - return data as! T - case .custom(let decode): - return try decode(self) as! T - @unknown default: - return try T(from: self) - } + return try decodeData() as! T default: return try T(from: self) } } + //swiftlint:enable force_cast + + private func decodeDate() throws -> Date { + switch options.dateStrategy { + case .deferredToDate: + return try Date(from: self) + case .secondsSince1970: + guard let value = data as? NSNumber else { throw DecodingError.dataCorrupted(data, target: Double.self, codingPath: codingPath) } + return Date(timeIntervalSince1970: value.doubleValue) + case .millisecondsSince1970: + guard let value = data as? NSNumber else { throw DecodingError.dataCorrupted(data, target: Double.self, codingPath: codingPath) } + return Date(timeIntervalSince1970: value.doubleValue / Double(MSEC_PER_SEC)) + case .iso8601: + guard let value = data as? String else { throw DecodingError.dataCorrupted(data, target: String.self, codingPath: codingPath) } + let formatter = ISO8601DateFormatter() + guard let date = formatter.date(from: value) else { throw DecodingError.dataCorrupted(value, target: Date.self, codingPath: codingPath) } + return date + case .formatted(let formatter): + guard let value = data as? String else { throw DecodingError.dataCorrupted(data, target: String.self, codingPath: codingPath) } + guard let date = formatter.date(from: value) else { throw DecodingError.dataCorrupted(value, target: Date.self, codingPath: codingPath) } + return date + case .custom(let decode): + return try decode(self) + @unknown default: + return try Date(from: self) + } + } + + private func decodeUrl() throws -> URL { + guard let str = data as? String, + let url = URL(string: str) + else { throw DecodingError.dataCorrupted(data, target: URL.self, codingPath: codingPath) } + + return url + } + + private func decodeData() throws -> Data { + switch options.dataStrategy { + case .deferredToData: + return try Data(from: self) + case .base64: + guard let value = data as? String else { throw DecodingError.dataCorrupted(data, target: String.self, codingPath: codingPath) } + guard let data = Data(base64Encoded: value) else { throw DecodingError.dataCorrupted(value, target: Data.self, codingPath: codingPath) } + return data + case .custom(let decode): + return try decode(self) + @unknown default: + return try Data(from: self) + } + } } private final class KeyedContainer where Key: CodingKey { diff --git a/ios/Capacitor/Capacitor/Codable/JSValueEncoder.swift b/ios/Capacitor/Capacitor/Codable/JSValueEncoder.swift index ce017fc34..464fe6e9d 100644 --- a/ios/Capacitor/Capacitor/Codable/JSValueEncoder.swift +++ b/ios/Capacitor/Capacitor/Codable/JSValueEncoder.swift @@ -79,7 +79,12 @@ public final class JSValueEncoder: TopLevelEncoder { dataEncodingStrategy: DataEncodingStrategy = .deferredToData, nonConformingFloatEncodingStategy: NonConformingFloatEncodingStrategy = .deferred ) { - self.options = .init(optionalStrategy: optionalEncodingStrategy, dateStrategy: dateEncodingStrategy, dataStrategy: dataEncodingStrategy, nonConformingFloatStrategy: nonConformingFloatEncodingStategy) + self.options = .init( + optionalStrategy: optionalEncodingStrategy, + dateStrategy: dateEncodingStrategy, + dataStrategy: dataEncodingStrategy, + nonConformingFloatStrategy: nonConformingFloatEncodingStategy + ) } @@ -591,6 +596,7 @@ extension SingleValueContainer: SingleValueEncodingContainer { try encodeFloat(value) } + //swiftlint:disable force_cast private func encodeFloat(_ value: N) throws where N: FloatingPoint { if value.isFinite { data = value as! NSNumber @@ -610,6 +616,7 @@ extension SingleValueContainer: SingleValueEncodingContainer { } } } + //swiftlint:enable force_cast func encode(_ value: Float) throws { try encodeFloat(value) diff --git a/ios/Capacitor/CodableTests/DateCodableTests.swift b/ios/Capacitor/CodableTests/DateCodableTests.swift index 875e8247d..811e6afa0 100644 --- a/ios/Capacitor/CodableTests/DateCodableTests.swift +++ b/ios/Capacitor/CodableTests/DateCodableTests.swift @@ -21,6 +21,7 @@ private let formatter: DateFormatter = { formatter.dateStyle = .medium formatter.timeStyle = .long formatter.timeZone = .init(abbreviation: "CDT") + formatter.locale = .init(identifier: "en_US") return formatter }() private let formatted = "Sep 5, 2024 at 5:36:20 PM CDT"