Skip to content

Commit e18a148

Browse files
Support for AnyValue Scalar type (#13)
1 parent d1b3231 commit e18a148

File tree

8 files changed

+500
-2
lines changed

8 files changed

+500
-2
lines changed

Sources/Scalars/AnyValue.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
17+
/// AnyValue represents the Any graphql scalar, which represents Codable data - scalar data (Int,
18+
/// Double, String, Bool,...) or a JSON object
19+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
20+
public struct AnyValue {
21+
public private(set) var value: Data
22+
23+
public init(codableValue: Codable) throws {
24+
do {
25+
let jsonEncoder = JSONEncoder()
26+
value = try jsonEncoder.encode(codableValue)
27+
}
28+
}
29+
30+
public func decodeValue<T: Decodable>(_ type: T.Type) throws -> T? {
31+
do {
32+
let jsonDecoder = JSONDecoder()
33+
let decodedResult = try jsonDecoder.decode(type, from: value)
34+
return decodedResult
35+
}
36+
}
37+
}
38+
39+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
40+
extension AnyValue: Codable {
41+
public init(from decoder: any Decoder) throws {
42+
let singleValueContainer = try decoder.singleValueContainer()
43+
value = try singleValueContainer.decode(Data.self)
44+
}
45+
46+
public func encode(to encoder: any Encoder) throws {
47+
var container = encoder.singleValueContainer()
48+
try container.encode(value)
49+
}
50+
}
51+
52+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
53+
extension AnyValue: Equatable {
54+
public static func == (lhs: Self, rhs: Self) -> Bool {
55+
return lhs.value == rhs.value
56+
}
57+
}
58+
59+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
60+
extension AnyValue: Hashable {
61+
public func hash(into hasher: inout Hasher) {
62+
hasher.combine(value)
63+
}
64+
}
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import XCTest
16+
17+
import FirebaseCore
18+
@testable import FirebaseDataConnect
19+
20+
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
21+
final class AnyScalarTests: IntegrationTestBase {
22+
override func setUp(completion: @escaping ((any Error)?) -> Void) {
23+
Task {
24+
do {
25+
try await ProjectConfigurator.shared.configureProject()
26+
completion(nil)
27+
} catch {
28+
completion(error)
29+
}
30+
}
31+
}
32+
33+
func testAnyValueString() async throws {
34+
let testData = "Test String Data \(Int.random(in: 1 ... 10000))"
35+
let anyTestData = try AnyValue(codableValue: testData)
36+
37+
let anyValueId = UUID()
38+
_ = try await DataConnect.kitchenSinkClient.createAnyValueTypeMutationRef(
39+
id: anyValueId,
40+
props: anyTestData
41+
).execute()
42+
print(anyValueId)
43+
44+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
45+
.execute()
46+
let anyValueResult = result.data.anyValueType?.props
47+
let decodedResult = try anyValueResult?.decodeValue(String.self)
48+
49+
XCTAssertEqual(testData, decodedResult)
50+
}
51+
52+
func testAnyValueInt() async throws {
53+
let testNumber = Int.random(in: 1 ... 9999)
54+
let anyTestData = try AnyValue(codableValue: testNumber)
55+
56+
let anyValueId = UUID()
57+
_ = try await DataConnect.kitchenSinkClient.createAnyValueTypeMutationRef(
58+
id: anyValueId,
59+
props: anyTestData
60+
).execute()
61+
62+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
63+
.execute()
64+
let anyValueResult = result.data.anyValueType?.props
65+
let decodedResult = try anyValueResult?.decodeValue(Int.self)
66+
67+
XCTAssertEqual(testNumber, decodedResult)
68+
}
69+
70+
func testAnyValueDouble() async throws {
71+
let testDouble = Double
72+
.random(in: Double.leastNormalMagnitude ... Double.greatestFiniteMagnitude)
73+
let anyTestData = try AnyValue(codableValue: testDouble)
74+
75+
let anyValueId = UUID()
76+
_ = try await DataConnect.kitchenSinkClient.createAnyValueTypeMutationRef(
77+
id: anyValueId,
78+
props: anyTestData
79+
).execute()
80+
81+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
82+
.execute()
83+
let anyValueResult = result.data.anyValueType?.props
84+
let decodedResult = try anyValueResult?.decodeValue(Double.self)
85+
86+
XCTAssertEqual(testDouble, decodedResult)
87+
}
88+
89+
func testAnyValueInt64() async throws {
90+
let testInt64 = Int64.random(in: Int64.min ... Int64.max)
91+
let anyTestData = try AnyValue(codableValue: testInt64)
92+
93+
let anyValueId = UUID()
94+
_ = try await DataConnect.kitchenSinkClient.createAnyValueTypeMutationRef(
95+
id: anyValueId,
96+
props: anyTestData
97+
).execute()
98+
99+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
100+
.execute()
101+
let anyValueResult = result.data.anyValueType?.props
102+
let decodedResult = try anyValueResult?.decodeValue(Int64.self)
103+
104+
XCTAssertEqual(testInt64, decodedResult)
105+
}
106+
107+
func testAnyValueInt64Max() async throws {
108+
let int64Max = Int64.max
109+
let anyTestData = try AnyValue(codableValue: int64Max)
110+
111+
let anyValueId = UUID()
112+
_ = try await DataConnect.kitchenSinkClient
113+
.createAnyValueTypeMutationRef(id: anyValueId, props: anyTestData).execute()
114+
115+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
116+
.execute()
117+
let anyValueResult = result.data.anyValueType?.props
118+
let decodedResult = try anyValueResult?.decodeValue(Int64.self)
119+
120+
XCTAssertEqual(int64Max, decodedResult)
121+
}
122+
123+
func testAnyValueInt64Min() async throws {
124+
let int64Min = Int64.min
125+
let anyTestData = try AnyValue(codableValue: int64Min)
126+
127+
let anyValueId = UUID()
128+
_ = try await DataConnect.kitchenSinkClient
129+
.createAnyValueTypeMutationRef(id: anyValueId, props: anyTestData).execute()
130+
131+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
132+
.execute()
133+
let anyValueResult = result.data.anyValueType?.props
134+
let decodedResult = try anyValueResult?.decodeValue(Int64.self)
135+
136+
XCTAssertEqual(int64Min, decodedResult)
137+
}
138+
139+
func testAnyValueDoubleMax() async throws {
140+
let testDouble = Double.greatestFiniteMagnitude
141+
let anyTestData = try AnyValue(codableValue: testDouble)
142+
143+
let anyValueId = UUID()
144+
_ = try await DataConnect.kitchenSinkClient.createAnyValueTypeMutationRef(
145+
id: anyValueId,
146+
props: anyTestData
147+
).execute()
148+
149+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
150+
.execute()
151+
let anyValueResult = result.data.anyValueType?.props
152+
let decodedResult = try anyValueResult?.decodeValue(Double.self)
153+
154+
XCTAssertEqual(testDouble, decodedResult)
155+
}
156+
157+
func testAnyValueDoubleMin() async throws {
158+
let testDouble = Double.leastNormalMagnitude
159+
let anyTestData = try AnyValue(codableValue: testDouble)
160+
161+
let anyValueId = UUID()
162+
_ = try await DataConnect.kitchenSinkClient.createAnyValueTypeMutationRef(
163+
id: anyValueId,
164+
props: anyTestData
165+
).execute()
166+
167+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
168+
.execute()
169+
let anyValueResult = result.data.anyValueType?.props
170+
let decodedResult = try anyValueResult?.decodeValue(Double.self)
171+
172+
XCTAssertEqual(testDouble, decodedResult)
173+
}
174+
175+
struct AnyValueEmbeddedStruct: Codable, Equatable {
176+
var stringVal = "Hello World \(Int.random(in: 1 ... 1000))"
177+
var doubleVal = Double.random(in: 1 ... 10000)
178+
var dictValInt: [String: Int] = [
179+
"keyOne": Int.random(in: 1 ... 1000),
180+
"KeyTwo": Int.random(in: 1 ... 100_000),
181+
]
182+
183+
static func == (lhs: AnyValueEmbeddedStruct, rhs: AnyValueEmbeddedStruct) -> Bool {
184+
return lhs.stringVal == rhs.stringVal &&
185+
lhs.doubleVal == rhs.doubleVal &&
186+
lhs.dictValInt == rhs.dictValInt
187+
}
188+
}
189+
190+
struct AnyValueTestStruct: Codable, Equatable {
191+
var stringVal = "Test Data \(Int.random(in: 10 ... 1000))"
192+
var intVal = Int.random(in: 1 ... 10000)
193+
var doubleVal = Double.random(in: 1 ... 1000)
194+
var dictVal: [String: String] = ["key1": "val1", "key2": "val2"]
195+
var dictValDouble: [String: Double] = [
196+
"key1": Double.random(in: 1 ... 1000),
197+
"key2": Double.random(in: 1 ... 10000),
198+
]
199+
var structVal = AnyValueEmbeddedStruct()
200+
201+
static func == (lhs: AnyValueTestStruct, rhs: AnyValueTestStruct) -> Bool {
202+
return lhs.stringVal == rhs.stringVal &&
203+
lhs.intVal == rhs.intVal &&
204+
lhs.doubleVal == rhs.doubleVal &&
205+
lhs.dictVal == rhs.dictVal &&
206+
lhs.dictValDouble == rhs.dictValDouble
207+
}
208+
}
209+
210+
func testAnyValueStruct() async throws {
211+
let structVal = AnyValueTestStruct()
212+
let anyValStruct = try AnyValue(codableValue: structVal)
213+
214+
let anyValueId = UUID()
215+
_ = try await DataConnect.kitchenSinkClient.createAnyValueTypeMutationRef(
216+
id: anyValueId,
217+
props: anyValStruct
218+
).execute()
219+
220+
let result = try await DataConnect.kitchenSinkClient.getAnyValueTypeQueryRef(id: anyValueId)
221+
.execute()
222+
let anyValueResult = result.data.anyValueType?.props
223+
let decodedResult = try anyValueResult?.decodeValue(AnyValueTestStruct.self)
224+
225+
XCTAssertEqual(structVal, decodedResult)
226+
}
227+
}

Tests/Integration/ConfigSetup.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,10 @@ actor ProjectConfigurator {
5353
var configureRequest = URLRequest(url: configureUrl)
5454
configureRequest.httpMethod = "POST"
5555

56-
let (data, response) = try await URLSession.shared.upload(
56+
let (_, response) = try await URLSession.shared.upload(
5757
for: configureRequest,
5858
from: configureBody.data(using: .utf8)!
5959
)
60-
print("responseData \(response)")
6160
setupComplete = true
6261
}
6362
}

Tests/Integration/Gen/KitchenSink/Sources/KitchenSinkKeys.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,46 @@ import Foundation
1616

1717
import FirebaseDataConnect
1818

19+
public struct AnyValueTypeKey {
20+
public private(set) var id: UUID
21+
22+
enum CodingKeys: String, CodingKey {
23+
case id
24+
}
25+
}
26+
27+
extension AnyValueTypeKey: Codable {
28+
public init(from decoder: any Decoder) throws {
29+
var container = try decoder.container(keyedBy: CodingKeys.self)
30+
let codecHelper = CodecHelper<CodingKeys>()
31+
32+
id = try codecHelper.decode(UUID.self, forKey: .id, container: &container)
33+
}
34+
35+
public func encode(to encoder: Encoder) throws {
36+
var container = encoder.container(keyedBy: CodingKeys.self)
37+
let codecHelper = CodecHelper<CodingKeys>()
38+
39+
try codecHelper.encode(id, forKey: .id, container: &container)
40+
}
41+
}
42+
43+
extension AnyValueTypeKey: Equatable {
44+
public static func == (lhs: AnyValueTypeKey, rhs: AnyValueTypeKey) -> Bool {
45+
if lhs.id != rhs.id {
46+
return false
47+
}
48+
49+
return true
50+
}
51+
}
52+
53+
extension AnyValueTypeKey: Hashable {
54+
public func hash(into hasher: inout Hasher) {
55+
hasher.combine(id)
56+
}
57+
}
58+
1959
public struct LargeIntTypeKey {
2060
public private(set) var id: UUID
2161

0 commit comments

Comments
 (0)