diff --git a/Sources/MBAsyncNetworking/AsyncNetworkable+Data.swift b/Sources/MBAsyncNetworking/AsyncNetworkable+Data.swift index b65b30e..8d08e09 100644 --- a/Sources/MBAsyncNetworking/AsyncNetworkable+Data.swift +++ b/Sources/MBAsyncNetworking/AsyncNetworkable+Data.swift @@ -35,7 +35,7 @@ public extension AsyncNetworkable { isRefreshToken: isRefreshToken ) case 200 ... 299: - printResponse(data, request: request) + printResponse(data, request: request, response: httpResponse) do { if T.self is MBEmptyCodable.Type { // swiftlint:disable force_cast @@ -47,6 +47,9 @@ public extension AsyncNetworkable { return data as! T // swiftlint:enable force_cast } + if data.isEmpty, httpResponse.statusCode == 204 { + throw AsyncNetworkableError.noContent + } return try decoder.decode(T.self, from: data) } catch { let error = NSError( @@ -58,10 +61,9 @@ public extension AsyncNetworkable { throw error } default: - let error = NSError( - domain: "MBAsyncNetworking", - code: httpResponse.statusCode, - userInfo: ["MBAsyncNetworkingErrorData": data] + let error = AsyncNetworkableError.httpError( + statusCode: httpResponse.statusCode, + data: data ) printErrorLog(error, request: request) throw error @@ -84,13 +86,7 @@ public extension AsyncNetworkable { isRefreshToken: Bool ) async throws -> T { guard hasAuthentication else { - throw NSError( - domain: "MBAsyncNetworking", - code: 401, - userInfo: [ - NSLocalizedDescriptionKey: "unauthorized" - ] - ) + throw AsyncNetworkableError.unauthorized } if !isRefreshToken { return try await RequestQueue.shared.executeAfterTokenRefresh { @@ -101,14 +97,7 @@ public extension AsyncNetworkable { } } else { await UserSession.clear() - throw NSError( - domain: "MBAsyncNetworking", - code: -3, - userInfo: [ - NSLocalizedDescriptionKey: "Tokenization could not be completed because, " + - "access_token and/or exires_in is not in expected format." - ] - ) + throw AsyncNetworkableError.tokenizationFailed } } // swiftformat:enable all diff --git a/Sources/MBAsyncNetworking/AsyncNetworkableError.swift b/Sources/MBAsyncNetworking/AsyncNetworkableError.swift new file mode 100644 index 0000000..c3b1dcf --- /dev/null +++ b/Sources/MBAsyncNetworking/AsyncNetworkableError.swift @@ -0,0 +1,58 @@ +// +// AsyncNetworkableError.swift +// MBAsyncNetworking +// +// Prompted by Eser Küçüker using Cursor on 30.01.2026. +// + +import Foundation + +enum AsyncNetworkableError: Error { + case noContent + case unauthorized + case tokenizationFailed + case httpError(statusCode: Int, data: Data) +} + +extension AsyncNetworkableError: LocalizedError { + var errorDescription: String? { + switch self { + case .noContent: + NSLocalizedString("network.error.noContent", bundle: .module, comment: "") + case .unauthorized: + NSLocalizedString("network.error.unauthorized", bundle: .module, comment: "") + case .tokenizationFailed: + NSLocalizedString("network.error.tokenizationFailed", bundle: .module, comment: "") + case .httpError: + nil + } + } +} + +extension AsyncNetworkableError: CustomNSError { + static var errorDomain: String { + "MBAsyncNetworking" + } + + var errorCode: Int { + switch self { + case .noContent: + 204 + case .unauthorized: + 401 + case .tokenizationFailed: + -3 + case let .httpError(statusCode, _): + statusCode + } + } + + var errorUserInfo: [String: Any] { + switch self { + case let .httpError(_, data): + ["MBAsyncNetworkingErrorData": data] + default: + [NSLocalizedDescriptionKey: errorDescription ?? ""] + } + } +} diff --git a/Sources/MBAsyncNetworking/Log/AsyncNetworkable+logs.swift b/Sources/MBAsyncNetworking/Log/AsyncNetworkable+logs.swift index ca1f34b..d53e9cb 100644 --- a/Sources/MBAsyncNetworking/Log/AsyncNetworkable+logs.swift +++ b/Sources/MBAsyncNetworking/Log/AsyncNetworkable+logs.swift @@ -18,8 +18,18 @@ public extension AsyncNetworkable { /// - Parameters: /// - data: The response data to log. Can be `nil`. /// - request: The original URL request associated with the response. - func printResponse(_ data: Data?, request: URLRequest) { - let logString = getRequestLog(request) + "\n" + getStringFrom(data) + /// - response: The HTTP response received for the request. + func printResponse(_ data: Data?, request: URLRequest, response: HTTPURLResponse?) { + var logString = getRequestLog(request) + if let response { + logString.append("\n") + logString.append( + "Status: \(response.statusCode) " + + HTTPURLResponse.localizedString(forStatusCode: response.statusCode) + ) + } + logString.append("\n") + logString.append(getStringFrom(data)) printLog(logString) NetworkLogsManager.shared.delegate?.didReceiveResponse( request: request, diff --git a/Sources/MBAsyncNetworking/Resources/en.lproj/Localizable.strings b/Sources/MBAsyncNetworking/Resources/en.lproj/Localizable.strings index f1af3ae..a1f843e 100644 --- a/Sources/MBAsyncNetworking/Resources/en.lproj/Localizable.strings +++ b/Sources/MBAsyncNetworking/Resources/en.lproj/Localizable.strings @@ -2,3 +2,6 @@ auth.error.missingToken = "Authentication failed: No token available."; auth.error.refreshingFailed = "Token refresh failed. Unable to obtain a new authentication token."; auth.error.storageNotFound = "Token storage is unavailable. Ensure the authentication storage mechanism is correctly implemented."; auth.error.tokenQueueResumeFailed = "Failed to resume queued request after token refresh. Please retry."; +network.error.noContent = "No Content"; +network.error.unauthorized = "Unauthorized"; +network.error.tokenizationFailed = "Tokenization could not be completed because access_token and/or exires_in is not in expected format."; diff --git a/Sources/MBAsyncNetworking/Resources/tr.lproj/Localizable.strings b/Sources/MBAsyncNetworking/Resources/tr.lproj/Localizable.strings index c2e62cb..7b85600 100644 --- a/Sources/MBAsyncNetworking/Resources/tr.lproj/Localizable.strings +++ b/Sources/MBAsyncNetworking/Resources/tr.lproj/Localizable.strings @@ -2,3 +2,6 @@ auth.error.missingToken = "Kimlik doğrulama başarısız: Token bulunamadı."; auth.error.refreshingFailed = "Token yenileme başarısız oldu. Yeni kimlik doğrulama tokenı alınamadı."; auth.error.storageNotFound = "Token saklama mekanizması bulunamadı. Kimlik doğrulama depolama sisteminin düzgün çalıştığından emin olun."; auth.error.tokenQueueResumeFailed = "Token yenilendikten sonra sıraya alınan istek devam ettirilemedi. Lütfen tekrar deneyin."; +network.error.noContent = "İçerik yok"; +network.error.unauthorized = "Yetkisiz"; +network.error.tokenizationFailed = "Tokenizasyon tamamlanamadı çünkü access_token ve/veya exires_in beklenen formatta değil.";