Fix PR inbox: decode Gitea ISO8601 dates with timezone offset
Build iOS App / Build .ipa and publish source (push) Successful in 28s
Build iOS App / Build .ipa and publish source (push) Successful in 28s
Gitea returns dates like 2026-08-12T11:09:13+02:00. The default JSONDecoder date strategy expects a Unix timestamp number, so decoding any Date? field threw and the whole [PullRequest] array failed — the inbox showed no PRs. Add a shared decoder with a custom ISO8601 strategy (with and without fractional seconds) and use it for all get/post decoding.
This commit is contained in:
1 parent
ddb6983a99
commit
b2311d8007
1 file changed
+29
-3
@@ -22,6 +22,32 @@ struct GiteaClient {
|
||||
let token: String
|
||||
let session: URLSession
|
||||
|
||||
/// Shared decoder. Gitea returns ISO8601 date strings WITH a timezone
|
||||
/// offset (e.g. "2026-08-12T11:09:13+02:00"), which neither the default
|
||||
/// `.deferredToDate` strategy (expects a Unix timestamp number) nor
|
||||
/// `.iso8601` (expects a trailing "Z") can parse. Without this, decoding
|
||||
/// any `Date?` field throws and the whole array fails — the inbox shows
|
||||
/// nothing even though the data is present.
|
||||
private static let decoder: JSONDecoder = {
|
||||
let d = JSONDecoder()
|
||||
let formatter = ISO8601DateFormatter()
|
||||
formatter.formatOptions = [.withInternetDateTime,
|
||||
.withFractionalSeconds]
|
||||
d.dateDecodingStrategy = .custom { decoder in
|
||||
let container = try decoder.singleValueContainer()
|
||||
let str = try container.decode(String.self)
|
||||
if let date = formatter.date(from: str) { return date }
|
||||
// Fall back to a formatter without fractional seconds.
|
||||
let plain = ISO8601DateFormatter()
|
||||
plain.formatOptions = [.withInternetDateTime]
|
||||
if let date = plain.date(from: str) { return date }
|
||||
throw DecodingError.dataCorruptedError(
|
||||
in: container,
|
||||
debugDescription: "Cannot decode date string \(str)")
|
||||
}
|
||||
return d
|
||||
}()
|
||||
|
||||
init(instanceURL: URL, token: String, session: URLSession = .shared) {
|
||||
self.instanceURL = instanceURL
|
||||
self.token = token
|
||||
@@ -62,7 +88,7 @@ struct GiteaClient {
|
||||
case 404:
|
||||
throw APIError.notFound
|
||||
default:
|
||||
let message = (try? JSONDecoder().decode([String: String].self,
|
||||
let message = (try? Self.decoder.decode([String: String].self,
|
||||
from: data))?["message"]
|
||||
throw APIError.httpStatus(http.statusCode, message)
|
||||
}
|
||||
@@ -72,7 +98,7 @@ struct GiteaClient {
|
||||
query: [URLQueryItem]? = nil) async throws -> T {
|
||||
let data = try await request(path, query: query)
|
||||
do {
|
||||
return try JSONDecoder().decode(T.self, from: data)
|
||||
return try Self.decoder.decode(T.self, from: data)
|
||||
} catch {
|
||||
throw APIError.decoding(String(describing: error))
|
||||
}
|
||||
@@ -80,7 +106,7 @@ struct GiteaClient {
|
||||
|
||||
private func post<T: Decodable>(_ path: String, body: Data? = nil) async throws -> T {
|
||||
let data = try await request(path, method: "POST", body: body)
|
||||
return try JSONDecoder().decode(T.self, from: data)
|
||||
return try Self.decoder.decode(T.self, from: data)
|
||||
}
|
||||
|
||||
private func postNoBody(_ path: String) async throws {
|
||||
|
||||
Reference in new issue
Block a user