From 43e26d8c1b3b38bbcb134cc22fe4e9cd3f3be99f Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Fri, 30 Aug 2024 12:18:00 -0700 Subject: [PATCH 1/4] Cherry pick ObjectData bool check to ListData --- apollo-ios/Sources/ApolloAPI/ObjectData.swift | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/apollo-ios/Sources/ApolloAPI/ObjectData.swift b/apollo-ios/Sources/ApolloAPI/ObjectData.swift index db2781616..4bda24d98 100644 --- a/apollo-ios/Sources/ApolloAPI/ObjectData.swift +++ b/apollo-ios/Sources/ApolloAPI/ObjectData.swift @@ -61,6 +61,9 @@ public struct ListData { public let _transformer: any _ObjectData_Transformer public let _rawData: [AnyHashable] + @usableFromInline internal static let _boolTrue = AnyHashable(true) + @usableFromInline internal static let _boolFalse = AnyHashable(false) + public init( _transformer: any _ObjectData_Transformer, _rawData: [AnyHashable] @@ -72,17 +75,17 @@ public struct ListData { @inlinable public subscript(_ key: Int) -> (any ScalarType)? { var value: AnyHashable = _rawData[key] - // Attempting cast to `Int` to ensure we always use `Int` vs `Int32` or `Int64` for consistency and ScalarType casting, - // also need to attempt `Bool` cast first to ensure a bool doesn't get inadvertently converted to `Int` - switch value { - case let boolVal as Bool: + // This check is based on AnyHashable using a canonical representation of the type-erased value so + // instances wrapping the same value of any type compare as equal. Therefore while Int(1) and Int(0) + // might be representable as Bool they will never equal Bool(true) nor Bool(false). + if let boolVal = value as? Bool, (value == Self._boolTrue || value == Self._boolFalse) { value = boolVal - case let intVal as Int: - value = intVal - default: - break + + // Cast to `Int` to ensure we always use `Int` vs `Int32` or `Int64` for consistency and ScalarType casting + } else if let intValue = value as? Int { + value = intValue } - + return _transformer.transform(value) } From 41668a81736a44fd8143d4843ada4f3586fd5a75 Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Wed, 4 Sep 2024 16:33:48 -0700 Subject: [PATCH 2/4] Refactor static check --- apollo-ios/Sources/ApolloAPI/ObjectData.swift | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/apollo-ios/Sources/ApolloAPI/ObjectData.swift b/apollo-ios/Sources/ApolloAPI/ObjectData.swift index 4bda24d98..b057b9266 100644 --- a/apollo-ios/Sources/ApolloAPI/ObjectData.swift +++ b/apollo-ios/Sources/ApolloAPI/ObjectData.swift @@ -11,9 +11,6 @@ public struct ObjectData { public let _transformer: any _ObjectData_Transformer public let _rawData: [String: AnyHashable] - @usableFromInline internal static let _boolTrue = AnyHashable(true) - @usableFromInline internal static let _boolFalse = AnyHashable(false) - public init( _transformer: any _ObjectData_Transformer, _rawData: [String: AnyHashable] @@ -29,7 +26,7 @@ public struct ObjectData { // This check is based on AnyHashable using a canonical representation of the type-erased value so // instances wrapping the same value of any type compare as equal. Therefore while Int(1) and Int(0) // might be representable as Bool they will never equal Bool(true) nor Bool(false). - if let boolVal = value as? Bool, (value == Self._boolTrue || value == Self._boolFalse) { + if let boolVal = value as? Bool, value.isCanonicalBool { value = boolVal // Cast to `Int` to ensure we always use `Int` vs `Int32` or `Int64` for consistency and ScalarType casting @@ -61,9 +58,6 @@ public struct ListData { public let _transformer: any _ObjectData_Transformer public let _rawData: [AnyHashable] - @usableFromInline internal static let _boolTrue = AnyHashable(true) - @usableFromInline internal static let _boolFalse = AnyHashable(false) - public init( _transformer: any _ObjectData_Transformer, _rawData: [AnyHashable] @@ -78,7 +72,7 @@ public struct ListData { // This check is based on AnyHashable using a canonical representation of the type-erased value so // instances wrapping the same value of any type compare as equal. Therefore while Int(1) and Int(0) // might be representable as Bool they will never equal Bool(true) nor Bool(false). - if let boolVal = value as? Bool, (value == Self._boolTrue || value == Self._boolFalse) { + if let boolVal = value as? Bool, value.isCanonicalBool { value = boolVal // Cast to `Int` to ensure we always use `Int` vs `Int32` or `Int64` for consistency and ScalarType casting @@ -99,3 +93,12 @@ public struct ListData { return _transformer.transform(_rawData[key]) } } + +extension AnyHashable { + fileprivate static let boolTrue = AnyHashable(true) + fileprivate static let boolFalse = AnyHashable(false) + + @usableFromInline var isCanonicalBool: Bool { + self == Self.boolTrue || self == Self.boolFalse + } +} From 453e4dd5002025344762b80f0c772960d731d31b Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Tue, 10 Sep 2024 15:56:58 -0700 Subject: [PATCH 3/4] Adds data transformer tests --- .../ObjectDataTransformerTests.swift | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 Tests/ApolloTests/ObjectDataTransformerTests.swift diff --git a/Tests/ApolloTests/ObjectDataTransformerTests.swift b/Tests/ApolloTests/ObjectDataTransformerTests.swift new file mode 100644 index 000000000..a91f8e365 --- /dev/null +++ b/Tests/ApolloTests/ObjectDataTransformerTests.swift @@ -0,0 +1,159 @@ +import XCTest +import Nimble +import ApolloAPI + +class ObjectDataTransformerTests: XCTestCase { + + fileprivate struct DataTransformer: _ObjectData_Transformer { + func transform(_ value: AnyHashable) -> (any ScalarType)? { + switch value { + case let scalar as any ScalarType: + return scalar + default: + return nil + } + } + + func transform(_ value: AnyHashable) -> ObjectData? { + return nil + } + + func transform(_ value: AnyHashable) -> ListData? { + switch value { + case let list as [AnyHashable]: + return ListData(_transformer: self, _rawData: list) + default: + return nil + } + } + } + + // MARK: ObjectData Tests + + func test__ObjectData_subscript_ScalarType__givenData_asInt_equalToBoolFalse_shouldReturnIntType() { + // given + let dataTransformer = ObjectData( + _transformer: DataTransformer(), + _rawData: ["intKey": 0] + ) + + // when + let actual = dataTransformer["intKey"] + + // then + expect(actual).to(beAnInstanceOf(Int.self)) + } + + func test__ObjectData_subscript_ScalarType__givenData_asInt_equalToBoolTrue_shouldReturnIntType() { + // given + let dataTransformer = ObjectData( + _transformer: DataTransformer(), + _rawData: ["intKey": 1] + ) + + // when + let actual = dataTransformer["intKey"] + + // then + expect(actual).to(beAnInstanceOf(Int.self)) + } + + func test__ObjectData_subscript_ScalarType__givenData_asInt_outsideBoolRange_shouldReturnIntType() { + // given + let dataTransformer = ObjectData( + _transformer: DataTransformer(), + _rawData: ["intKey": 2] + ) + + // when + let actual = dataTransformer["intKey"] + + // then + expect(actual).to(beAnInstanceOf(Int.self)) + } + + func test__ObjectData_subscript_ScalarType__givenData_asBool_true_shouldReturnBoolType() { + // given + let dataTransformer = ObjectData( + _transformer: DataTransformer(), + _rawData: ["boolKey": true] + ) + + // when + let actual = dataTransformer["boolKey"] + + // then + expect(actual).to(beAnInstanceOf(Bool.self)) + } + + func test__ObjectData_subscript_ScalarType__givenData_asBool_false_shouldReturnBoolType() { + // given + let dataTransformer = ObjectData( + _transformer: DataTransformer(), + _rawData: ["boolKey": false] + ) + + // when + let actual = dataTransformer["boolKey"] + + // then + expect(actual).to(beAnInstanceOf(Bool.self)) + } + + // MARK: ListData Tests + + func test__ListData_subscript_ScalarType__givenData_asInt_equalToBoolFalse_shouldReturnIntType() { + // given + let dataTransformer = ListData(_transformer: DataTransformer(), _rawData: [0]) + + // when + let actual = dataTransformer[0] + + // then + expect(actual).to(beAnInstanceOf(Int.self)) + } + + func test__ListData_subscript_ScalarType__givenData_asInt_equalToBoolTrue_shouldReturnIntType() { + // given + let dataTransformer = ListData(_transformer: DataTransformer(), _rawData: [1]) + + // when + let actual = dataTransformer[0] + + // then + expect(actual).to(beAnInstanceOf(Int.self)) + } + + func test__ListData_subscript_ScalarType__givenData_asInt_outsideBoolRange_shouldReturnIntType() { + // given + let dataTransformer = ListData(_transformer: DataTransformer(), _rawData: [2]) + + // when + let actual = dataTransformer[0] + + // then + expect(actual).to(beAnInstanceOf(Int.self)) + } + + func test__ListData_subscript_ScalarType__givenData_asBool_true_shouldReturnBoolType() { + // given + let dataTransformer = ListData(_transformer: DataTransformer(), _rawData: [true]) + + // when + let actual = dataTransformer[0] + + // then + expect(actual).to(beAnInstanceOf(Bool.self)) + } + + func test__ListData_subscript_ScalarType__givenData_asBool_false_shouldReturnBoolType() { + // given + let dataTransformer = ListData(_transformer: DataTransformer(), _rawData: [false]) + + // when + let actual = dataTransformer[0] + + // then + expect(actual).to(beAnInstanceOf(Bool.self)) + } +} From a4f6284c8ad6c6e3e4e667f7ad1776be26d5343c Mon Sep 17 00:00:00 2001 From: Calvin Cestari Date: Wed, 11 Sep 2024 13:19:18 -0700 Subject: [PATCH 4/4] Clean up test data transformer --- .../ApolloTests/ObjectDataTransformerTests.swift | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/Tests/ApolloTests/ObjectDataTransformerTests.swift b/Tests/ApolloTests/ObjectDataTransformerTests.swift index a91f8e365..daee1ec6e 100644 --- a/Tests/ApolloTests/ObjectDataTransformerTests.swift +++ b/Tests/ApolloTests/ObjectDataTransformerTests.swift @@ -14,18 +14,9 @@ class ObjectDataTransformerTests: XCTestCase { } } - func transform(_ value: AnyHashable) -> ObjectData? { - return nil - } - - func transform(_ value: AnyHashable) -> ListData? { - switch value { - case let list as [AnyHashable]: - return ListData(_transformer: self, _rawData: list) - default: - return nil - } - } + // Empty until needed in tests + func transform(_ value: AnyHashable) -> ObjectData? { return nil } + func transform(_ value: AnyHashable) -> ListData? { return nil } } // MARK: ObjectData Tests