From c9a8a6c3f5c54a2c47ac1e64064e685312054fad Mon Sep 17 00:00:00 2001 From: dankinsoid <30962149+dankinsoid@users.noreply.github.com> Date: Thu, 16 May 2024 15:05:39 +0400 Subject: [PATCH] 1.11.0 --- Sources/SwiftAPIClient/APIClientCaller.swift | 36 +----- .../Modifiers/ErrorHandler.swift | 121 ++++++++++++++++++ .../SwiftAPIClient/Utils/Error+String.swift | 11 ++ 3 files changed, 139 insertions(+), 29 deletions(-) create mode 100644 Sources/SwiftAPIClient/Modifiers/ErrorHandler.swift diff --git a/Sources/SwiftAPIClient/APIClientCaller.swift b/Sources/SwiftAPIClient/APIClientCaller.swift index f093991..f00cd9d 100644 --- a/Sources/SwiftAPIClient/APIClientCaller.swift +++ b/Sources/SwiftAPIClient/APIClientCaller.swift @@ -206,11 +206,14 @@ public extension APIClient { if configs.reportMetrics { updateTotalResponseMetrics(for: request, successful: false) } + + var context = APIErrorContext(request: request, response: nil, fileIDLine: fileIDLine) if let data = response as? Data, let failure = configs.errorDecoder.decodeError(data, configs) { - try configs.errorHandler(failure, configs) + context.response = data + try configs.errorHandler(failure, configs, context) throw failure } - try configs.errorHandler(error, configs) + try configs.errorHandler(error, configs, context) throw error } } @@ -229,35 +232,10 @@ public extension APIClient { if configs.reportMetrics { updateTotalErrorsMetrics(for: nil) } - try configs.errorHandler(error, configs) + let context = APIErrorContext(fileIDLine: fileIDLine) + try configs.errorHandler(error, configs, context) } throw error } } } - -public extension APIClient.Configs { - - var errorHandler: (Error, APIClient.Configs) throws -> Void { - get { self[\.errorHandler] ?? { _, _ in } } - set { self[\.errorHandler] = newValue } - } -} - -public extension APIClient { - - /// Sets the error handler. - func errorHandler(_ handler: @escaping (Error, APIClient.Configs) throws -> Void) -> APIClient { - configs { configs in - let current = configs.errorHandler - configs.errorHandler = { failure, configs in - do { - try current(failure, configs) - } catch { - try handler(error, configs) - throw error - } - } - } - } -} diff --git a/Sources/SwiftAPIClient/Modifiers/ErrorHandler.swift b/Sources/SwiftAPIClient/Modifiers/ErrorHandler.swift new file mode 100644 index 0000000..e64823c --- /dev/null +++ b/Sources/SwiftAPIClient/Modifiers/ErrorHandler.swift @@ -0,0 +1,121 @@ +import Foundation + +public extension APIClient { + + /// Sets the error handler. + func errorHandler(_ handler: @escaping (Error, APIClient.Configs, APIErrorContext) throws -> Void) -> APIClient { + configs { configs in + let current = configs.errorHandler + configs.errorHandler = { failure, configs, context in + do { + try current(failure, configs, context) + } catch { + try handler(error, configs, context) + throw error + } + } + } + } + + /// Sets the error handler to throw an `APIClientError` with detailed information. + func detailedError(includeBody: APIClientError.IncludeBodyPolicy = .auto) -> APIClient { + errorHandler { error, configs, context in + throw APIClientError(error: error, configs: configs, context: context, includeBody: includeBody) + } + } +} + + +public extension APIClient.Configs { + + var errorHandler: (Error, APIClient.Configs, APIErrorContext) throws -> Void { + get { self[\.errorHandler] ?? { _, _, _ in } } + set { self[\.errorHandler] = newValue } + } +} + +public struct APIErrorContext: Equatable { + + public var request: HTTPRequestComponents? + public var response: Data? + public var fileID: String + public var line: UInt + + public init( + request: HTTPRequestComponents? = nil, + response: Data? = nil, + fileID: String, + line: UInt + ) { + self.request = request + self.response = response + self.fileID = fileID + self.line = line + } + + public init( + request: HTTPRequestComponents? = nil, + response: Data? = nil, + fileIDLine: FileIDLine + ) { + self.init( + request: request, + response: response, + fileID: fileIDLine.fileID, + line: fileIDLine.line + ) + } +} + +public struct APIClientError: LocalizedError, CustomStringConvertible { + + public var error: Error + public var configs: APIClient.Configs + public var context: APIErrorContext + public var includeBody: IncludeBodyPolicy + + public init(error: Error, configs: APIClient.Configs, context: APIErrorContext, includeBody: IncludeBodyPolicy) { + self.error = error + self.configs = configs + self.context = context + self.includeBody = includeBody + } + + public var errorDescription: String? { + description + } + + public var description: String { + var components = [error.humanReadable] + + if let request = context.request { + let urlString = request.urlComponents.url?.absoluteString ?? request.urlComponents.path + components.append("Request: \(request.method) \(urlString)") + } + if let response = context.response { + switch includeBody { + case .never: + break + case .always: + if let utf8 = String(data: response, encoding: .utf8) { + components.append("Response: \(utf8)") + } + case let .auto(sizeLimit): + if response.count < sizeLimit, let utf8 = String(data: response, encoding: .utf8) { + components.append("Response: \(utf8)") + } + } + } + components.append("File: \(context.fileID) Line: \(context.line)") + return components.joined(separator: " - ") + } + + public enum IncludeBodyPolicy { + + case never + case always + case auto(sizeLimit: Int) + + public static var auto: IncludeBodyPolicy { .auto(sizeLimit: 1024) } + } +} diff --git a/Sources/SwiftAPIClient/Utils/Error+String.swift b/Sources/SwiftAPIClient/Utils/Error+String.swift index 1c694ee..eb3ba4d 100644 --- a/Sources/SwiftAPIClient/Utils/Error+String.swift +++ b/Sources/SwiftAPIClient/Utils/Error+String.swift @@ -73,3 +73,14 @@ private extension CodingKey { return "." + stringValue } } + +struct CodableError: LocalizedError, CustomStringConvertible { + + var error: Error + var description: String { error.humanReadable } + var errorDescription: String? { description } + + init(_ error: Error) { + self.error = error + } +}