Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Cherry pick ObjectData type check to ListData #473

Merged
merged 4 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions Tests/ApolloTests/ObjectDataTransformerTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
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
}
}

// Empty until needed in tests
func transform(_ value: AnyHashable) -> ObjectData? { return nil }
func transform(_ value: AnyHashable) -> ListData? { 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))
}
}
32 changes: 19 additions & 13 deletions apollo-ios/Sources/ApolloAPI/ObjectData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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
Expand Down Expand Up @@ -72,17 +69,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.isCanonicalBool {
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)
}

Expand All @@ -96,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
}
}
Loading