From b9bdc84121ca6d5b383ae1d356512fdec8ab4407 Mon Sep 17 00:00:00 2001 From: Maksim Zoteev <39910552+F0x1d@users.noreply.github.com> Date: Tue, 21 Nov 2023 13:46:36 +0300 Subject: [PATCH] Better response error handling (#32) --- Papyrus/URLSession+Papyrus.swift | 39 +++++++++++------------- PapyrusAlamofire/Alamofire+Papyrus.swift | 27 +++++++--------- PapyrusCore/Sources/PapyrusError.swift | 10 +++++- PapyrusCore/Sources/Request.swift | 2 +- PapyrusCore/Sources/Response.swift | 14 ++++++--- PapyrusPlugin/Sources/APIMacro.swift | 1 + PapyrusPlugin/Tests/APIMacroTests.swift | 5 +++ 7 files changed, 54 insertions(+), 44 deletions(-) diff --git a/Papyrus/URLSession+Papyrus.swift b/Papyrus/URLSession+Papyrus.swift index cf962b8..00627f5 100644 --- a/Papyrus/URLSession+Papyrus.swift +++ b/Papyrus/URLSession+Papyrus.swift @@ -21,7 +21,7 @@ extension URLSession: HTTPService { request.httpMethod = method request.httpBody = body request.allHTTPHeaderFields = headers - return _Request(request: request) + return request } public func request(_ req: Request) async -> Response { @@ -51,21 +51,23 @@ extension URLSession: HTTPService { // MARK: `Response` Conformance extension Response { - public var urlRequest: URLRequest { (self as! _Response).request } - public var urlResponse: URLResponse? { (self as! _Response).response } + public var urlRequest: URLRequest { (self as! _Response).urlRequest } + public var urlResponse: URLResponse? { (self as! _Response).urlResponse } } private struct _Response: Response { - let request: URLRequest - let response: URLResponse? + let urlRequest: URLRequest + let urlResponse: URLResponse? + + var request: Request? { urlRequest } let error: Error? let body: Data? let headers: [String: String]? var statusCode: Int? { (urlResponse as? HTTPURLResponse)?.statusCode } init(request: URLRequest, response: URLResponse?, error: Error?, body: Data?) { - self.request = request - self.response = response + self.urlRequest = request + self.urlResponse = response self.error = error self.body = body let headerPairs = (response as? HTTPURLResponse)? @@ -89,30 +91,23 @@ private struct _Response: Response { extension Request { public var urlRequest: URLRequest { - (self as! _Request).request + (self as! URLRequest) } } -private struct _Request: Request { - var request: URLRequest - - public var url: URL { - get { request.url! } - set { request.url = newValue } - } - +extension URLRequest: Request { public var body: Data? { - get { request.httpBody } - set { request.httpBody = newValue } + get { httpBody } + set { httpBody = newValue } } public var method: String { - get { request.httpMethod ?? "" } - set { request.httpMethod = newValue } + get { httpMethod ?? "" } + set { httpMethod = newValue } } public var headers: [String: String] { - get { request.allHTTPHeaderFields ?? [:] } - set { request.allHTTPHeaderFields = newValue } + get { allHTTPHeaderFields ?? [:] } + set { allHTTPHeaderFields = newValue } } } diff --git a/PapyrusAlamofire/Alamofire+Papyrus.swift b/PapyrusAlamofire/Alamofire+Papyrus.swift index 44e7f4a..96b39c3 100644 --- a/PapyrusAlamofire/Alamofire+Papyrus.swift +++ b/PapyrusAlamofire/Alamofire+Papyrus.swift @@ -19,7 +19,7 @@ extension Session: HTTPService { request.httpMethod = method request.httpBody = body request.allHTTPHeaderFields = headers - return _Request(request: request) + return request } public func request(_ req: PapyrusCore.Request) async -> Response { @@ -42,6 +42,8 @@ extension Response { } extension DataResponse: Response { + @_implements(Response, request) + public var _request: PapyrusCore.Request? { request } public var body: Data? { data } public var headers: [String : String]? { response?.headers.dictionary } public var statusCode: Int? { response?.statusCode } @@ -55,30 +57,23 @@ extension DataResponse: Response { extension PapyrusCore.Request { public var urlRequest: URLRequest { - (self as! _Request).request + (self as! URLRequest) } } -private struct _Request: PapyrusCore.Request { - var request: URLRequest - - public var url: URL { - get { request.url! } - set { request.url = newValue } - } - +extension URLRequest: PapyrusCore.Request { public var body: Data? { - get { request.httpBody } - set { request.httpBody = newValue } + get { httpBody } + set { httpBody = newValue } } public var method: String { - get { request.httpMethod ?? "" } - set { request.httpMethod = newValue } + get { httpMethod ?? "" } + set { httpMethod = newValue } } public var headers: [String: String] { - get { request.allHTTPHeaderFields ?? [:] } - set { request.allHTTPHeaderFields = newValue } + get { allHTTPHeaderFields ?? [:] } + set { allHTTPHeaderFields = newValue } } } diff --git a/PapyrusCore/Sources/PapyrusError.swift b/PapyrusCore/Sources/PapyrusError.swift index 4c82cd9..39cab67 100644 --- a/PapyrusCore/Sources/PapyrusError.swift +++ b/PapyrusCore/Sources/PapyrusError.swift @@ -2,11 +2,19 @@ public struct PapyrusError: Error { /// What went wrong. public let message: String + /// Error related request. + public let request: Request? + /// Error related response. + public let response: Response? /// Create an error with the specified message. /// /// - Parameter message: What went wrong. - public init(_ message: String) { + /// - Parameter request: Error related request. + /// - Parameter response: Error related response. + public init(_ message: String, _ request: Request? = nil, _ response: Response? = nil) { self.message = message + self.request = request + self.response = response } } diff --git a/PapyrusCore/Sources/Request.swift b/PapyrusCore/Sources/Request.swift index 807f30b..cefe684 100644 --- a/PapyrusCore/Sources/Request.swift +++ b/PapyrusCore/Sources/Request.swift @@ -1,7 +1,7 @@ import Foundation public protocol Request { - var url: URL { get set } + var url: URL? { get set } var method: String { get set } var headers: [String: String] { get set } var body: Data? { get set } diff --git a/PapyrusCore/Sources/Response.swift b/PapyrusCore/Sources/Response.swift index 46506ed..0518b9e 100644 --- a/PapyrusCore/Sources/Response.swift +++ b/PapyrusCore/Sources/Response.swift @@ -1,6 +1,7 @@ import Foundation public protocol Response { + var request: Request? { get } var body: Data? { get } var headers: [String: String]? { get } var statusCode: Int? { get } @@ -12,7 +13,7 @@ extension Response { @discardableResult public func validate() throws -> Self { if let error { throw error } - if let statusCode, !(200..<300).contains(statusCode) { throw PapyrusError("Unsuccessful status code: \(statusCode).") } + if let statusCode, !(200..<300).contains(statusCode) { throw makePapyrusError(with: "Unsuccessful status code: \(statusCode).") } return self } @@ -22,19 +23,23 @@ extension Response { public func decode(_ type: Data.Type = Data.self, using decoder: ResponseDecoder) throws -> Data { guard let body = try decode(Data?.self, using: decoder) else { - throw PapyrusError("Unable to return the body of a `Response`; the body was nil.") + throw makePapyrusError(with: "Unable to return the body of a `Response`; the body was nil.") } return body } public func decode(_ type: D.Type = D.self, using decoder: ResponseDecoder) throws -> D { - guard let body = try validate().body else { - throw PapyrusError("Unable to decode `\(Self.self)` from a `Response`; body was nil.") + guard let body else { + throw makePapyrusError(with: "Unable to decode `\(Self.self)` from a `Response`; body was nil.") } return try decoder.decode(type, from: body) } + + private func makePapyrusError(with message: String) -> PapyrusError { + PapyrusError(message, request, self) + } } extension Response where Self == ErrorResponse { @@ -50,6 +55,7 @@ public struct ErrorResponse: Response { self._error = error } + public var request: Request? { nil } public var body: Data? { nil } public var headers: [String : String]? { nil } public var statusCode: Int? { nil } diff --git a/PapyrusPlugin/Sources/APIMacro.swift b/PapyrusPlugin/Sources/APIMacro.swift index f39da0f..3c5f15f 100644 --- a/PapyrusPlugin/Sources/APIMacro.swift +++ b/PapyrusPlugin/Sources/APIMacro.swift @@ -136,6 +136,7 @@ extension FunctionDeclSyntax { return """ let res = try await provider.request(req) + try res.validate() return \(resultExpression) """ } diff --git a/PapyrusPlugin/Tests/APIMacroTests.swift b/PapyrusPlugin/Tests/APIMacroTests.swift index 7926e07..6c9d225 100644 --- a/PapyrusPlugin/Tests/APIMacroTests.swift +++ b/PapyrusPlugin/Tests/APIMacroTests.swift @@ -98,6 +98,7 @@ final class APIMacroTests: XCTestCase { var req = builder(method: "GET", path: "some/path") req.addQuery("userId", value: userId) let res = try await provider.request(req) + try res.validate() return try res.decode(String.self, using: req.responseDecoder) } @@ -139,6 +140,7 @@ final class APIMacroTests: XCTestCase { var req = builder(method: "GET", path: "some/path") req.addQuery("userId", value: userId) let res = try await provider.request(req) + try res.validate() return try res.decode(String.self, using: req.responseDecoder) } @@ -177,6 +179,7 @@ final class APIMacroTests: XCTestCase { var req = builder(method: "GET", path: "some/path") req.addField("userId", value: userId) let res = try await provider.request(req) + try res.validate() return try res.decode(String.self, using: req.responseDecoder) } @@ -222,6 +225,7 @@ final class APIMacroTests: XCTestCase { req.addParameter("userId", value: userId) req.addQuery("since", value: since) let res = try await provider.request(req) + try res.validate() return try res.decode(String.self, using: req.responseDecoder) } @@ -268,6 +272,7 @@ final class APIMacroTests: XCTestCase { req.addParameter("userId", value: userId) req.addQuery("since", value: since) let res = try await provider.request(req) + try res.validate() return try res.decode(String.self, using: req.responseDecoder) }