Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 9 additions & 20 deletions Sources/MBAsyncNetworking/AsyncNetworkable+Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand Down
58 changes: 58 additions & 0 deletions Sources/MBAsyncNetworking/AsyncNetworkableError.swift
Original file line number Diff line number Diff line change
@@ -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 ?? ""]
}
}
}
14 changes: 12 additions & 2 deletions Sources/MBAsyncNetworking/Log/AsyncNetworkable+logs.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.";
Original file line number Diff line number Diff line change
Expand Up @@ -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.";