Skip to content

Commit 431e794

Browse files
committed
Add date conversion error handling and more tests
1 parent 8f3df6b commit 431e794

File tree

3 files changed

+220
-9
lines changed

3 files changed

+220
-9
lines changed

FirebaseVertexAI/Sources/GenerateContentResponse.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,30 +372,40 @@ extension Citation: Decodable {
372372
let container = try decoder.container(keyedBy: CodingKeys.self)
373373
startIndex = try container.decodeIfPresent(Int.self, forKey: .startIndex) ?? 0
374374
endIndex = try container.decode(Int.self, forKey: .endIndex)
375+
375376
if let uri = try container.decodeIfPresent(String.self, forKey: .uri), !uri.isEmpty {
376377
self.uri = uri
377378
} else {
378379
uri = nil
379380
}
381+
380382
if let title = try container.decodeIfPresent(String.self, forKey: .title), !title.isEmpty {
381383
self.title = title
382384
} else {
383385
title = nil
384386
}
387+
385388
if let license = try container.decodeIfPresent(String.self, forKey: .license),
386389
!license.isEmpty {
387390
self.license = license
388391
} else {
389392
license = nil
390393
}
394+
391395
if let publicationProtoDate = try container.decodeIfPresent(
392396
ProtoDate.self,
393397
forKey: .publicationDate
394398
) {
395-
if let publicationDate = publicationProtoDate.dateComponents.date {
396-
self.publicationDate = publicationDate
397-
} else {
398-
publicationDate = nil
399+
do {
400+
publicationDate = try publicationProtoDate.asDate()
401+
} catch let error as ProtoDate.DateConversionError {
402+
throw DecodingError.dataCorrupted(
403+
DecodingError.Context(
404+
codingPath: [CodingKeys.publicationDate],
405+
debugDescription: "Invalid citation publicationDate.",
406+
underlyingError: error
407+
)
408+
)
399409
}
400410
} else {
401411
publicationDate = nil

FirebaseVertexAI/Sources/Types/Internal/ProtoDate.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,41 @@ struct ProtoDate {
4444
var dateComponents: DateComponents {
4545
DateComponents(
4646
calendar: Calendar.current,
47-
timeZone: TimeZone.current,
4847
year: year,
4948
month: month,
5049
day: day
5150
)
5251
}
5352

53+
func asDate() throws -> Date {
54+
guard year != nil else {
55+
throw DateConversionError(message: "Missing a year: \(self)")
56+
}
57+
guard month != nil else {
58+
throw DateConversionError(message: "Missing a month: \(self)")
59+
}
60+
guard day != nil else {
61+
throw DateConversionError(message: "Missing a day: \(self)")
62+
}
63+
guard let date = dateComponents.date else {
64+
throw DateConversionError(message: "Date conversion failed: \(self)")
65+
}
66+
return date
67+
}
68+
5469
init(year: Int?, month: Int?, day: Int?) {
5570
self.year = year
5671
self.month = month
5772
self.day = day
5873
}
74+
75+
struct DateConversionError: Error {
76+
let localizedDescription: String
77+
78+
init(message: String) {
79+
localizedDescription = message
80+
}
81+
}
5982
}
6083

6184
// MARK: - Codable Conformance

FirebaseVertexAI/Tests/Unit/Types/ProtoDateTests.swift

Lines changed: 182 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import XCTest
1919
final class ProtoDateTests: XCTestCase {
2020
let decoder = JSONDecoder()
2121

22+
// MARK: - Date Components Tests
23+
2224
// A full date, with non-zero year, month, and day values.
2325
func testProtoDate_fullDate_dateComponents() {
2426
let year = 2024
@@ -64,7 +66,7 @@ final class ProtoDateTests: XCTestCase {
6466
// A year and month value, with a zero day, such as a credit card expiration date
6567
func testProtoDate_yearMonth_dateComponents() {
6668
let year = 2024
67-
let month = 08
69+
let month = 8
6870
let protoDate = ProtoDate(year: year, month: month, day: nil)
6971

7072
let dateComponents = protoDate.dateComponents
@@ -75,18 +77,78 @@ final class ProtoDateTests: XCTestCase {
7577
XCTAssertEqual(protoDate.day, nil)
7678
}
7779

78-
func testProtoDate_asDate() throws {
80+
// MARK: - Date Conversion Tests
81+
82+
func testProtoDate_fullDate_asDate() throws {
7983
let protoDate = ProtoDate(year: 2024, month: 12, day: 31)
8084
let dateFormatter = DateFormatter()
8185
dateFormatter.dateFormat = "yyyy-MM-dd"
8286
let expectedDate = try XCTUnwrap(dateFormatter.date(from: "2024-12-31"))
8387

84-
let date = try XCTUnwrap(protoDate.dateComponents.date)
88+
let date = try XCTUnwrap(protoDate.asDate())
8589

8690
XCTAssertEqual(date, expectedDate)
8791
}
8892

89-
func testDecodeProtoDate() throws {
93+
func testProtoDate_monthDay_asDate_throws() {
94+
let protoDate = ProtoDate(year: nil, month: 7, day: 1)
95+
96+
do {
97+
_ = try protoDate.asDate()
98+
} catch let error as ProtoDate.DateConversionError {
99+
XCTAssertTrue(error.localizedDescription.contains("Missing a year"))
100+
return
101+
} catch {
102+
XCTFail("Expected \(ProtoDate.DateConversionError.self), got \(error).")
103+
}
104+
XCTFail("Expected an error but none thrown.")
105+
}
106+
107+
func testProtoDate_yearOnly_asDate_throws() {
108+
let protoDate = ProtoDate(year: 2024, month: nil, day: nil)
109+
110+
do {
111+
_ = try protoDate.asDate()
112+
} catch let error as ProtoDate.DateConversionError {
113+
XCTAssertTrue(error.localizedDescription.contains("Missing a month"))
114+
return
115+
} catch {
116+
XCTFail("Expected \(ProtoDate.DateConversionError.self), got \(error).")
117+
}
118+
XCTFail("Expected an error but none thrown.")
119+
}
120+
121+
func testProtoDate_yearMonth_asDate_throws() {
122+
let protoDate = ProtoDate(year: 2024, month: 8, day: nil)
123+
124+
do {
125+
_ = try protoDate.asDate()
126+
} catch let error as ProtoDate.DateConversionError {
127+
XCTAssertTrue(error.localizedDescription.contains("Missing a day"))
128+
return
129+
} catch {
130+
XCTFail("Expected \(ProtoDate.DateConversionError.self), got \(error).")
131+
}
132+
XCTFail("Expected an error but none thrown.")
133+
}
134+
135+
func testProtoDate_empty_asDate_throws() {
136+
let protoDate = ProtoDate(year: nil, month: nil, day: nil)
137+
138+
do {
139+
_ = try protoDate.asDate()
140+
} catch let error as ProtoDate.DateConversionError {
141+
XCTAssertTrue(error.localizedDescription.contains("Missing a year"))
142+
return
143+
} catch {
144+
XCTFail("Expected \(ProtoDate.DateConversionError.self), got \(error).")
145+
}
146+
XCTFail("Expected an error but none thrown.")
147+
}
148+
149+
// MARK: - Decoding Tests
150+
151+
func testDecodeProtoDate_fullDate() throws {
90152
let json = """
91153
{
92154
"year" : 2024,
@@ -103,7 +165,123 @@ final class ProtoDateTests: XCTestCase {
103165
XCTAssertEqual(protoDate.day, 31)
104166
}
105167

168+
func testDecodeProtoDate_monthDay() throws {
169+
let json = """
170+
{
171+
"year": 0,
172+
"month" : 12,
173+
"day" : 31
174+
}
175+
"""
176+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
177+
178+
let protoDate = try decoder.decode(ProtoDate.self, from: jsonData)
179+
180+
XCTAssertNil(protoDate.year)
181+
XCTAssertEqual(protoDate.month, 12)
182+
XCTAssertEqual(protoDate.day, 31)
183+
}
184+
185+
func testDecodeProtoDate_monthDay_defaultsOmitted() throws {
186+
let json = """
187+
{
188+
"month" : 12,
189+
"day" : 31
190+
}
191+
"""
192+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
193+
194+
let protoDate = try decoder.decode(ProtoDate.self, from: jsonData)
195+
196+
XCTAssertNil(protoDate.year)
197+
XCTAssertEqual(protoDate.month, 12)
198+
XCTAssertEqual(protoDate.day, 31)
199+
}
200+
201+
func testDecodeProtoDate_yearOnly() throws {
202+
let json = """
203+
{
204+
"year": 2024,
205+
"month" : 0,
206+
"day" : 0
207+
}
208+
"""
209+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
210+
211+
let protoDate = try decoder.decode(ProtoDate.self, from: jsonData)
212+
213+
XCTAssertEqual(protoDate.year, 2024)
214+
XCTAssertNil(protoDate.month)
215+
XCTAssertNil(protoDate.day)
216+
}
217+
218+
func testDecodeProtoDate_yearOnly_defaultsOmitted() throws {
219+
let json = """
220+
{
221+
"year": 2024
222+
}
223+
"""
224+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
225+
226+
let protoDate = try decoder.decode(ProtoDate.self, from: jsonData)
227+
228+
XCTAssertEqual(protoDate.year, 2024)
229+
XCTAssertNil(protoDate.month)
230+
XCTAssertNil(protoDate.day)
231+
}
232+
233+
func testDecodeProtoDate_yearMonth() throws {
234+
let json = """
235+
{
236+
"year": 2024,
237+
"month" : 12,
238+
"day": 0
239+
}
240+
"""
241+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
242+
243+
let protoDate = try decoder.decode(ProtoDate.self, from: jsonData)
244+
245+
XCTAssertEqual(protoDate.year, 2024)
246+
XCTAssertEqual(protoDate.month, 12)
247+
XCTAssertNil(protoDate.day)
248+
}
249+
250+
func testDecodeProtoDate_yearMonth_defaultsOmitted() throws {
251+
let json = """
252+
{
253+
"year": 2024,
254+
"month" : 12
255+
}
256+
"""
257+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
258+
259+
let protoDate = try decoder.decode(ProtoDate.self, from: jsonData)
260+
261+
XCTAssertEqual(protoDate.year, 2024)
262+
XCTAssertEqual(protoDate.month, 12)
263+
XCTAssertNil(protoDate.day)
264+
}
265+
106266
func testDecodeProtoDate_emptyDate_throws() throws {
267+
let json = """
268+
{
269+
"year": 0,
270+
"month" : 0,
271+
"day": 0
272+
}
273+
"""
274+
let jsonData = try XCTUnwrap(json.data(using: .utf8))
275+
276+
do {
277+
_ = try decoder.decode(ProtoDate.self, from: jsonData)
278+
} catch DecodingError.dataCorrupted {
279+
return
280+
}
281+
XCTFail("Expected a DecodingError.")
282+
}
283+
284+
func testDecodeProtoDate_emptyDate_defaultsOmitted_throws() throws {
107285
let json = "{}"
108286
let jsonData = try XCTUnwrap(json.data(using: .utf8))
109287

0 commit comments

Comments
 (0)