Skip to content

Use only FoundationEssentials when possible #81

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

Merged
merged 15 commits into from
Dec 19, 2024
100 changes: 70 additions & 30 deletions Sources/AWSLambdaEvents/Utils/DateWrappers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
//
//===----------------------------------------------------------------------===//

#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

@propertyWrapper
public struct ISO8601Coding: Decodable, Sendable {
Expand All @@ -25,23 +29,44 @@ public struct ISO8601Coding: Decodable, Sendable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
guard let date = Self.dateFormatter.date(from: dateString) else {

struct InvalidDateError: Error {}

do {
self.wrappedValue = try Self.parseISO8601(dateString: dateString)
} catch {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in ISO8601 date format, but `\(dateString)` is not in the correct format"
)
}
self.wrappedValue = date
}

private static func parseISO8601(dateString: String) throws -> Date {
if #available(macOS 12.0, *) {
return try Date(dateString, strategy: .iso8601)
Copy link
Contributor Author

@t089 t089 Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm I don't think this will actually compile on older swift versions on Linux where this api is not yet available in Foundation, need to check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah there are a bunch of build errors with swift 5.10 on linux. will look into it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next version will support Swift 6 and onwards, aligned on the Swift Lambda Runtime

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you want to fix this or not. I'm happy to merge with a Swift 6 only change. If there is an easy fix, let's be nice with our existing users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually the if #available(macOS 12, *) check was redundant as the conditional compile already excludes macOS altogether. Now it should compile for both on linux and macOS.

} else {
#if !canImport(FoundationEssentials)
guard let date = Self.dateFormatter.date(from: dateString) else {
throw InvalidDateError()
}
return date
#endif

fatalError("ISO8601Coding is not supported on this platform - this should never happen")
}
}

#if !canImport(FoundationEssentials)
private static var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
return formatter
}
#endif
}

@propertyWrapper
Expand All @@ -55,23 +80,49 @@ public struct ISO8601WithFractionalSecondsCoding: Decodable, Sendable {
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
guard let date = Self.dateFormatter.date(from: dateString) else {

struct InvalidDateError: Error {}

do {
self.wrappedValue = try Self.parseISO8601WithFractionalSeconds(dateString: dateString)
} catch {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in ISO8601 date format with fractional seconds, but `\(dateString)` is not in the correct format"
)
}
self.wrappedValue = date
}

private static func parseISO8601WithFractionalSeconds(dateString: String) throws -> Date {
if #available(macOS 12.0, *) {
return try Date(dateString, strategy: Self.iso8601WithFractionalSeconds)
} else {
#if !canImport(FoundationEssentials)
guard let date = Self.dateFormatter.date(from: dateString) else {
throw InvalidDateError()
}
return date
#endif

fatalError("ISO8601Coding is not supported on this platform - this should never happen")
}
}

#if !canImport(FoundationEssentials)
private static var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
return formatter
}
#endif

@available(macOS 12.0, *)
private static var iso8601WithFractionalSeconds: Date.ISO8601FormatStyle {
Date.ISO8601FormatStyle(includingFractionalSeconds: true)
}
}

@propertyWrapper
Expand All @@ -84,34 +135,23 @@ public struct RFC5322DateTimeCoding: Decodable, Sendable {

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
var string = try container.decode(String.self)
// RFC5322 dates sometimes have the alphabetic version of the timezone in brackets after the numeric version. The date formatter
// fails to parse this so we need to remove this before parsing.
if let bracket = string.firstIndex(of: "(") {
string = String(string[string.startIndex..<bracket].trimmingCharacters(in: .whitespaces))
}
for formatter in Self.dateFormatters {
if let date = formatter.date(from: string) {
self.wrappedValue = date
return
let string = try container.decode(String.self)

do {
if #available(macOS 12.0, *) {
self.wrappedValue = try Date(string, strategy: Self.rfc5322DateParseStrategy)
} else {
self.wrappedValue = try Self.rfc5322DateParseStrategy.parse(string)
}
} catch {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in RFC5322 date-time format, but `\(string)` is not in the correct format"
)
}
throw DecodingError.dataCorruptedError(
in: container,
debugDescription:
"Expected date to be in RFC5322 date-time format, but `\(string)` is not in the correct format"
)
}

private static var dateFormatters: [DateFormatter] {
// rfc5322 dates received in SES mails sometimes do not include the day, so need two dateformatters
// one with a day and one without
let formatterWithDay = DateFormatter()
formatterWithDay.dateFormat = "EEE, d MMM yyy HH:mm:ss z"
formatterWithDay.locale = Locale(identifier: "en_US_POSIX")
let formatterWithoutDay = DateFormatter()
formatterWithoutDay.dateFormat = "d MMM yyy HH:mm:ss z"
formatterWithoutDay.locale = Locale(identifier: "en_US_POSIX")
return [formatterWithDay, formatterWithoutDay]
}
private static let rfc5322DateParseStrategy = RFC5322DateParseStrategy(calendar: Calendar(identifier: .gregorian))

}
Loading