Skip to content

Commit

Permalink
1.11.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dankinsoid committed May 16, 2024
1 parent 16b70a0 commit c9a8a6c
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 29 deletions.
36 changes: 7 additions & 29 deletions Sources/SwiftAPIClient/APIClientCaller.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -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
}
}
}
}
}
121 changes: 121 additions & 0 deletions Sources/SwiftAPIClient/Modifiers/ErrorHandler.swift
Original file line number Diff line number Diff line change
@@ -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) }
}
}
11 changes: 11 additions & 0 deletions Sources/SwiftAPIClient/Utils/Error+String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

0 comments on commit c9a8a6c

Please sign in to comment.