問題
Apple公式のOpenAPI Generatorを使うと、Responseをパーズできなくて下記のエラーが出ることがある。
Client error - cause description: 'Unknown', underlying error: DecodingError: dataCorrupted - at : Expected date string to be ISO8601-formatted.
原因
正確にはSwift OpenAPI Generatorではなくswift-openapi-runtimeがデフォルトで利用するISO8601DateTranscoderの実装に起因している。
public struct ISO8601DateTranscoder: DateTranscoder { /// Creates and returns an ISO 8601 formatted string representation of the specified date. public func encode(_ date: Date) throws -> String { ISO8601DateFormatter().string(from: date) } /// Creates and returns a date object from the specified ISO 8601 formatted string representation. public func decode(_ dateString: String) throws -> Date { guard let date = ISO8601DateFormatter().date(from: dateString) else { throw DecodingError.dataCorrupted( .init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted.") ) } return date } }
SwiftのISO8601DateFormatter
を利用しているので良いように見えるが、ISO8601には拡張形式がありこのコードはそれに対応していない。
例えば、 2023-11-23T10:29:15.53+09:00
のように、ミリ秒まで含まれるISO8601拡張形式を処理したい場合は
let formatter = ISO8601DateFormatter() formatter.formatOptions = [.withFractionalSeconds]
のように .withFractionalSeconds
オプションを追加で指定する必要がある。他にも拡張形式は様々あるため、拡張形式でDateが表記されているレスポンスの場合、ISO8601DateTranscoderではうまく処理できないことがある。
対策
DateTranscoder
はprotocolとして公開されているので、自分で DateTranscoder
を作ってしまえば良い。
struct CustomISO8601DateTranscoder: DateTranscoder { public func encode(_ date: Date) throws -> String { ISO8601DateFormatter().string(from: date) } public func decode(_ dateString: String) throws -> Date { let iso8601DateFormatter = ISO8601DateFormatter() iso8601DateFormatter.formatOptions = [.withFractionalSeconds] guard let date = iso8601DateFormatter.date(from: dateString) else { throw DecodingError.dataCorrupted( .init(codingPath: [], debugDescription: "Expected date string to be ISO8601-formatted.") ) } return date } }
実装した CustomISO8601DateTranscoder
はConfiguration経由でClientに渡せば良い。
let transport = AsyncHTTPClientTransport(configuration: .init()) let client = Client( serverURL: serverURL, configuration: Configuration(dateTranscoder: CustomISO8601DateTranscoder()), transport: transport )
iso8601DateFormatter.formatOptions
にどのOptionを指定すればよいかはAPIのレスポンス形式による。使えるOptionはこちらを参照すること。