From 2a9128c130701d96e210efba3a200e90a17b92f4 Mon Sep 17 00:00:00 2001 From: Piotr Durlej Date: Wed, 13 May 2026 19:00:40 +0200 Subject: [PATCH] fix: preserve merge error context --- .../ForgejoKit/Services/ForgejoClient.swift | 22 ++++++++++++++++++- .../Services/PullRequestService.swift | 6 ++--- Tests/ForgejoKitTests/NotificationTests.swift | 15 +++++++++++++ 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/Sources/ForgejoKit/Services/ForgejoClient.swift b/Sources/ForgejoKit/Services/ForgejoClient.swift index 19d6135..f9fc5a3 100644 --- a/Sources/ForgejoKit/Services/ForgejoClient.swift +++ b/Sources/ForgejoKit/Services/ForgejoClient.swift @@ -488,7 +488,11 @@ public enum ServiceError: LocalizedError, Sendable { case .invalidResponse: "Invalid response from server" case let .httpError(statusCode, message): - message.isEmpty ? "HTTP error: \(statusCode)" : "HTTP \(statusCode): \(message)" + if let message = Self.normalizedHTTPMessage(message) { + "HTTP \(statusCode): \(message)" + } else { + "HTTP error: \(statusCode)" + } case .notMergeable: "This pull request cannot be merged" case .mergeConflict: @@ -497,4 +501,20 @@ public enum ServiceError: LocalizedError, Sendable { "Decoding failed: \(detail)" } } + + private static func normalizedHTTPMessage(_ message: String) -> String? { + let trimmed = message.trimmingCharacters(in: .whitespacesAndNewlines) + guard !trimmed.isEmpty else { + return nil + } + guard + let data = trimmed.data(using: .utf8), + let object = try? JSONSerialization.jsonObject(with: data) as? [String: Any], + let jsonMessage = object["message"] as? String + else { + return trimmed + } + let trimmedJSONMessage = jsonMessage.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmedJSONMessage.isEmpty ? trimmed : trimmedJSONMessage + } } diff --git a/Sources/ForgejoKit/Services/PullRequestService.swift b/Sources/ForgejoKit/Services/PullRequestService.swift index 9fa8146..933a477 100644 --- a/Sources/ForgejoKit/Services/PullRequestService.swift +++ b/Sources/ForgejoKit/Services/PullRequestService.swift @@ -173,11 +173,11 @@ public final class PullRequestService: Sendable { do { _ = try await client.performRequestNoContent(url: url, method: "POST", body: jsonData, validateStatus: true) } catch let error as ServiceError { - if case let .httpError(statusCode, _) = error { + if case let .httpError(statusCode, message) = error { switch statusCode { - case 405: + case 405 where message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty: throw ServiceError.notMergeable - case 409: + case 409 where message.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty: throw ServiceError.mergeConflict default: throw error diff --git a/Tests/ForgejoKitTests/NotificationTests.swift b/Tests/ForgejoKitTests/NotificationTests.swift index 95238bf..24c6919 100644 --- a/Tests/ForgejoKitTests/NotificationTests.swift +++ b/Tests/ForgejoKitTests/NotificationTests.swift @@ -75,6 +75,21 @@ struct NotificationTests { #expect(ServiceError.decodingFailed(detail: "missing key").errorDescription == "Decoding failed: missing key") } + @Test func httpErrorDescriptionUsesForgejoMessageBody() { + let error = ServiceError.httpError( + statusCode: 405, + message: #"{"message":"This pull request has failing status checks"}"#, + ) + + #expect(error.errorDescription == "HTTP 405: This pull request has failing status checks") + } + + @Test func httpErrorDescriptionFallsBackToRawBody() { + let error = ServiceError.httpError(statusCode: 500, message: "database unavailable") + + #expect(error.errorDescription == "HTTP 500: database unavailable") + } + // MARK: - Full API response decoding @Test func decodesNotificationArray() throws {