From 2743c0ae4f9b1f84906d1d64b709da0cc5f6a33a Mon Sep 17 00:00:00 2001 From: Alexander Cyon <116169792+CyonAlexRDX@users.noreply.github.com> Date: Fri, 31 May 2024 15:23:53 +0200 Subject: [PATCH] Swift - UniFFI boundary - update `into_custom` and `from_custom` especially for date. (#153) Swift - change UniFFI into_custom and from_custom to use ISO8601DateFormatter. --- Cargo.lock | 2 +- Cargo.toml | 2 +- apple/Tests/TestCases/Prelude/DateTests.swift | 74 +++++++++++++++++++ .../TestCases/Profile/DeviceInfoTests.swift | 17 +---- .../Tests/Utils/Extensions/JSON+ISO8601.swift | 24 ++++++ uniffi.toml | 30 +++++++- 6 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 apple/Tests/TestCases/Prelude/DateTests.swift create mode 100644 apple/Tests/Utils/Extensions/JSON+ISO8601.swift diff --git a/Cargo.lock b/Cargo.lock index 03f74230d..8476bb5b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2586,7 +2586,7 @@ dependencies = [ [[package]] name = "sargon" -version = "1.0.6" +version = "1.0.7" dependencies = [ "actix-rt", "aes-gcm", diff --git a/Cargo.toml b/Cargo.toml index f69e57143..ee64d56b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sargon" -version = "1.0.6" +version = "1.0.7" edition = "2021" build = "build.rs" diff --git a/apple/Tests/TestCases/Prelude/DateTests.swift b/apple/Tests/TestCases/Prelude/DateTests.swift new file mode 100644 index 000000000..2c1bf85d9 --- /dev/null +++ b/apple/Tests/TestCases/Prelude/DateTests.swift @@ -0,0 +1,74 @@ +import CustomDump +import Foundation +import Sargon +import SargonUniFFI +import XCTest + +final class DateTests: TestCase { + + func test_date() { + + /// This matches `[bindings.swift.custom_types.Timestamp]` + /// inside `uniffi.toml` with the only difference that we use `$0` here but + /// the toml uses `{}`; + let into_custom: (String) -> Date = { + let stringToDeserialize = $0 + let formatter = ISO8601DateFormatter() + let formatOptionMS = ISO8601DateFormatter.Options.withFractionalSeconds + formatter.formatOptions.insert(formatOptionMS) + + func format() -> Date? { + formatter.date(from: stringToDeserialize) + } + + if let date = format() { + return date + } + + // try without fractional seconds + formatter.formatOptions.remove(formatOptionMS) + return format()! + } + + /// This matches `[bindings.swift.custom_types.Timestamp]` + /// inside `uniffi.toml` + let from_custom: (Date) -> String = { + let dateToSerialize = $0 + let formatter = ISO8601DateFormatter() + formatter.formatOptions.insert(.withFractionalSeconds) + return formatter.string(from: dateToSerialize) + } + + func testIntoThenFrom(_ vector: (String, String?)) { + let sut = vector.0 + let expected = vector.1 ?? sut + + let string = from_custom(into_custom(sut)) + + XCTAssertEqual(string, expected) + } + + func testFromThenInto(_ vector: (String, String?)) { + let lhs = vector.0 + let rhs = vector.1 ?? lhs + + let lhsString = from_custom(into_custom(lhs)) + let rhsString = from_custom(into_custom(rhs)) + + XCTAssertEqual(rhsString, rhs) + + XCTAssertEqual( + into_custom(lhsString), + into_custom(rhsString) + ) + } + + let vectors = [ + ("2023-12-24T17:13:56.123456Z", "2023-12-24T17:13:56.123Z"), // precision lost (which is OK) + ("2023-12-24T17:13:56.123Z", nil), // unchanged + ("2023-12-24T17:13:56Z", "2023-12-24T17:13:56.000Z") // (000 added, which is OK) + ] + vectors.forEach(testIntoThenFrom) + vectors.forEach(testFromThenInto) + } +} diff --git a/apple/Tests/TestCases/Profile/DeviceInfoTests.swift b/apple/Tests/TestCases/Profile/DeviceInfoTests.swift index 4ddab2aaa..c80ea3e84 100644 --- a/apple/Tests/TestCases/Profile/DeviceInfoTests.swift +++ b/apple/Tests/TestCases/Profile/DeviceInfoTests.swift @@ -76,21 +76,6 @@ final class DeviceInfoTests: Test { } """, expected: nil) } + } - -extension JSONEncoder { - static var iso8601: JSONEncoder { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - return encoder - } -} - -extension JSONDecoder { - static var iso8601: JSONDecoder { - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - return decoder - } -} diff --git a/apple/Tests/Utils/Extensions/JSON+ISO8601.swift b/apple/Tests/Utils/Extensions/JSON+ISO8601.swift new file mode 100644 index 000000000..d0308cf19 --- /dev/null +++ b/apple/Tests/Utils/Extensions/JSON+ISO8601.swift @@ -0,0 +1,24 @@ +// +// File.swift +// +// +// Created by Alexander Cyon on 2024-05-31. +// + +import Foundation + +extension JSONEncoder { + static var iso8601: JSONEncoder { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + return encoder + } +} + +extension JSONDecoder { + static var iso8601: JSONDecoder { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + return decoder + } +} diff --git a/uniffi.toml b/uniffi.toml index 785268a79..64318d21d 100644 --- a/uniffi.toml +++ b/uniffi.toml @@ -44,8 +44,34 @@ from_custom = "{}.toString()" [bindings.swift.custom_types.Timestamp] type_name = "Date" imports = ["Foundation"] -into_custom = "{let df = DateFormatter();df.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ\";return df.date(from: {})!}()" -from_custom = "{let df = DateFormatter();df.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ\";return df.string(from: {})}()" +into_custom = """ +{ + let stringToDeserialize = {} // this is UniFFIs counterpart to `$0` + let formatter = ISO8601DateFormatter() + let formatOptionMS = ISO8601DateFormatter.Options.withFractionalSeconds + formatter.formatOptions.insert(formatOptionMS) + + func format() -> Date? { + formatter.date(from: stringToDeserialize) + } + + if let date = format() { + return date + } + + // try without fractional seconds + formatter.formatOptions.remove(formatOptionMS) + return format()! +}() +""" +from_custom = """ +{ + let dateToSerialize = {} // this is UniFFIs counterpart to `$0` + let formatter = ISO8601DateFormatter() + formatter.formatOptions.insert(.withFractionalSeconds) + return formatter.string(from: dateToSerialize) +}() +""" [bindings.kotlin.custom_types.Timestamp] type_name = "OffsetDateTime"