From 0d6a6a1cea1dd30e92057fcc1f310a9d11415ae9 Mon Sep 17 00:00:00 2001 From: William Laverty Date: Mon, 2 Feb 2026 01:02:23 -0800 Subject: [PATCH] Handle non-JSON error payloads gracefully Fixes #136 When a registry returns an error response with a non-JSON body (e.g., plain text like 'page not found'), ContainerRegistry now catches the DecodingError and throws a more informative RegistryClientError with the HTTP status code and raw response body, instead of propagating the confusing JSONDecoder error. Added new error case: - RegistryClientError.unexpectedRegistryResponse(status:body:) This provides clearer error messages when registries don't follow the OCI distribution spec for error responses. --- .../ContainerRegistry/RegistryClient.swift | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/Sources/ContainerRegistry/RegistryClient.swift b/Sources/ContainerRegistry/RegistryClient.swift index 41fcfda..474ab40 100644 --- a/Sources/ContainerRegistry/RegistryClient.swift +++ b/Sources/ContainerRegistry/RegistryClient.swift @@ -26,6 +26,7 @@ public enum RegistryClientError: Error { case invalidUploadLocation(String) case invalidDigestAlgorithm(String) case digestMismatch(expected: String, registry: String) + case unexpectedRegistryResponse(status: Int, body: String) } extension RegistryClientError: CustomStringConvertible { @@ -38,6 +39,8 @@ extension RegistryClientError: CustomStringConvertible { case let .invalidDigestAlgorithm(digest): return "Invalid or unsupported digest algorithm: \(digest)" case let .digestMismatch(expected, registry): return "Digest mismatch: expected \(expected), registry sent \(registry)" + case let .unexpectedRegistryResponse(status, body): + return "Registry returned HTTP \(status): \(body)" } } } @@ -369,8 +372,14 @@ extension RegistryClient { } catch HTTPClientError.unexpectedStatusCode(let status, _, let .some(responseData)) where errors.contains(status) { - let decoded = try decoder.decode(DistributionErrors.self, from: responseData) - throw decoded + // Try to decode as JSON; if that fails, throw a generic error with the raw response body + do { + let decoded = try decoder.decode(DistributionErrors.self, from: responseData) + throw decoded + } catch is DecodingError { + let bodyText = String(data: responseData, encoding: .utf8) ?? "" + throw RegistryClientError.unexpectedRegistryResponse(status: status.code, body: bodyText) + } } } @@ -410,8 +419,14 @@ extension RegistryClient { } catch HTTPClientError.unexpectedStatusCode(let status, _, let .some(responseData)) where errors.contains(status) { - let decoded = try decoder.decode(DistributionErrors.self, from: responseData) - throw decoded + // Try to decode as JSON; if that fails, throw a generic error with the raw response body + do { + let decoded = try decoder.decode(DistributionErrors.self, from: responseData) + throw decoded + } catch is DecodingError { + let bodyText = String(data: responseData, encoding: .utf8) ?? "" + throw RegistryClientError.unexpectedRegistryResponse(status: status.code, body: bodyText) + } } }