From f7ff566ea96e328ea458e0215a5d8ae5a632ce9e Mon Sep 17 00:00:00 2001 From: MagellaX Date: Fri, 19 Sep 2025 14:18:57 +0530 Subject: [PATCH] Propagate realtime request IDs in error paths --- Sources/FalClient/Realtime.swift | 21 ++++++++++-- Tests/FalClientTests/RealtimeSpec.swift | 44 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 Tests/FalClientTests/RealtimeSpec.swift diff --git a/Sources/FalClient/Realtime.swift b/Sources/FalClient/Realtime.swift index 8769104..718ddef 100644 --- a/Sources/FalClient/Realtime.swift +++ b/Sources/FalClient/Realtime.swift @@ -21,7 +21,7 @@ public enum FalRealtimeError: Error { case unauthorized case invalidInput case invalidResult(requestId: String? = nil, causedBy: Error? = nil) - case serviceError(type: String, reason: String) + case serviceError(type: String, reason: String, requestId: String? = nil) } extension FalRealtimeError: LocalizedError { @@ -35,11 +35,24 @@ extension FalRealtimeError: LocalizedError { return NSLocalizedString("Invalid input format", comment: "FalRealtimeError.invalidInput") case .invalidResult: return NSLocalizedString("Invalid result", comment: "FalRealtimeError.invalidResult") - case let .serviceError(type, reason): + case let .serviceError(type, reason, _): return NSLocalizedString("\(type): \(reason)", comment: "FalRealtimeError.serviceError") } } } +public extension FalRealtimeError { + var requestId: String? { + switch self { + case let .invalidResult(requestId, _): + return requestId + case let .serviceError(_, _, requestId): + return requestId + default: + return nil + } + } +} + typealias SendFunction = (URLSessionWebSocketTask.Message) throws -> Void typealias CloseFunction = () -> Void @@ -355,7 +368,8 @@ func getError(_ message: Payload) -> FalRealtimeError? { // not trigger the onError callback of the client error != "TIMEOUT" { - return FalRealtimeError.serviceError(type: error, reason: reason) + let requestId = message["request_id"].stringValue ?? message["requestId"].stringValue + return FalRealtimeError.serviceError(type: error, reason: reason, requestId: requestId) } return nil } @@ -485,3 +499,4 @@ public extension Realtime { ) } } + diff --git a/Tests/FalClientTests/RealtimeSpec.swift b/Tests/FalClientTests/RealtimeSpec.swift new file mode 100644 index 0000000..c12502d --- /dev/null +++ b/Tests/FalClientTests/RealtimeSpec.swift @@ -0,0 +1,44 @@ +@testable import FalClient +import Nimble +import Quick + +class RealtimeSpec: QuickSpec { + override class func spec() { + describe("Realtime error decoding") { + it("attaches the request id to service errors") { + let message: Payload = [ + "type": "x-fal-error", + "error": "MODEL_ERROR", + "reason": "failed to process", + "request_id": "req-123" + ] + + let error = getError(message) + guard case let .serviceError(_, _, requestId)? = error else { + fail("Expected serviceError with request identifier") + return + } + + expect(requestId).to(equal("req-123")) + expect(error?.requestId).to(equal("req-123")) + } + + it("leaves the request id unset when unavailable") { + let message: Payload = [ + "type": "x-fal-error", + "error": "MODEL_ERROR", + "reason": "failed to process" + ] + + let error = getError(message) + guard case let .serviceError(_, _, requestId)? = error else { + fail("Expected serviceError when decoding realtime message") + return + } + + expect(requestId).to(beNil()) + expect(error?.requestId).to(beNil()) + } + } + } +}