Skip to content

Commit 7f86e4a

Browse files
authored
Make the ISO8601 date transcoder configurable (#94)
Make the ISO8601 date transcoder configurable ### Motivation Fixes apple/swift-openapi-generator#389. ### Modifications Add a way to configure the existing `ISO8601DateTranscoder`. ### Result Adopters can more easily use e.g. fractional seconds-based transcoder. ### Test Plan Added unit tests. Reviewed by: simonjbeaumont Builds: ✔︎ pull request validation (5.10) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (5.9.0) - Build finished. ✔︎ pull request validation (api breakage) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. #94
1 parent 6be221f commit 7f86e4a

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

Sources/OpenAPIRuntime/Conversion/Configuration.swift

+31-3
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,37 @@ public protocol DateTranscoder: Sendable {
2525
}
2626

2727
/// A transcoder for dates encoded as an ISO-8601 string (in RFC 3339 format).
28-
public struct ISO8601DateTranscoder: DateTranscoder {
28+
public struct ISO8601DateTranscoder: DateTranscoder, @unchecked Sendable {
29+
30+
/// The lock protecting the formatter.
31+
private let lock: NSLock
32+
33+
/// The underlying date formatter.
34+
private let locked_formatter: ISO8601DateFormatter
35+
36+
/// Creates a new transcoder with the provided options.
37+
/// - Parameter options: Options to override the default ones. If you provide nil here, the default options
38+
/// are used.
39+
public init(options: ISO8601DateFormatter.Options? = nil) {
40+
let formatter = ISO8601DateFormatter()
41+
if let options { formatter.formatOptions = options }
42+
lock = NSLock()
43+
lock.name = "com.apple.swift-openapi-generator.runtime.ISO8601DateTranscoder"
44+
locked_formatter = formatter
45+
}
2946

3047
/// Creates and returns an ISO 8601 formatted string representation of the specified date.
31-
public func encode(_ date: Date) throws -> String { ISO8601DateFormatter().string(from: date) }
48+
public func encode(_ date: Date) throws -> String {
49+
lock.lock()
50+
defer { lock.unlock() }
51+
return locked_formatter.string(from: date)
52+
}
3253

3354
/// Creates and returns a date object from the specified ISO 8601 formatted string representation.
3455
public func decode(_ dateString: String) throws -> Date {
35-
guard let date = ISO8601DateFormatter().date(from: dateString) else {
56+
lock.lock()
57+
defer { lock.unlock() }
58+
guard let date = locked_formatter.date(from: dateString) else {
3659
throw DecodingError.dataCorrupted(
3760
.init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted.")
3861
)
@@ -44,6 +67,11 @@ public struct ISO8601DateTranscoder: DateTranscoder {
4467
extension DateTranscoder where Self == ISO8601DateTranscoder {
4568
/// A transcoder that transcodes dates as ISO-8601–formatted string (in RFC 3339 format).
4669
public static var iso8601: Self { ISO8601DateTranscoder() }
70+
71+
/// A transcoder that transcodes dates as ISO-8601–formatted string (in RFC 3339 format) with fractional seconds.
72+
public static var iso8601WithFractionalSeconds: Self {
73+
ISO8601DateTranscoder(options: [.withInternetDateTime, .withFractionalSeconds])
74+
}
4775
}
4876

4977
extension JSONEncoder.DateEncodingStrategy {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
import XCTest
15+
@_spi(Generated) import OpenAPIRuntime
16+
17+
final class Test_Configuration: Test_Runtime {
18+
19+
func testDateTranscoder_iso8601() throws {
20+
let transcoder: any DateTranscoder = .iso8601
21+
XCTAssertEqual(try transcoder.encode(testDate), testDateString)
22+
XCTAssertEqual(testDate, try transcoder.decode(testDateString))
23+
}
24+
25+
func testDateTranscoder_iso8601WithFractionalSeconds() throws {
26+
let transcoder: any DateTranscoder = .iso8601WithFractionalSeconds
27+
XCTAssertEqual(try transcoder.encode(testDateWithFractionalSeconds), testDateWithFractionalSecondsString)
28+
XCTAssertEqual(testDateWithFractionalSeconds, try transcoder.decode(testDateWithFractionalSecondsString))
29+
}
30+
}

Tests/OpenAPIRuntimeTests/Test_Runtime.swift

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ class Test_Runtime: XCTestCase {
4242

4343
var testDateString: String { "2023-01-18T10:04:11Z" }
4444

45+
var testDateWithFractionalSeconds: Date { Date(timeIntervalSince1970: 1_674_036_251.123) }
46+
47+
var testDateWithFractionalSecondsString: String { "2023-01-18T10:04:11.123Z" }
48+
4549
var testDateEscapedString: String { "2023-01-18T10%3A04%3A11Z" }
4650

4751
var testDateStringData: Data { Data(testDateString.utf8) }

0 commit comments

Comments
 (0)