From 2ba5eae67621018b014adbab329ecb7a5aa904ea Mon Sep 17 00:00:00 2001 From: Robin Kamboj Date: Thu, 27 Jul 2023 17:20:14 +0530 Subject: [PATCH 1/5] Update APIClient.swift -> track api failed open func --- Source/Classes/API/APIClient.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Classes/API/APIClient.swift b/Source/Classes/API/APIClient.swift index 3f03a7f..246ce3a 100755 --- a/Source/Classes/API/APIClient.swift +++ b/Source/Classes/API/APIClient.swift @@ -72,6 +72,8 @@ open class APIClient { self.authHeaders = self.authenticationHeaders(response: response) } + open func trackApiFailed(url: String, timeDiff: Int, size: Int) {} + //Override this method in the subclass to set auth headers from the responses. open func authenticationHeaders (response: HTTPURLResponse) -> U? { return nil From d35fe6c9e14821e7267a28c439f919d20f47759f Mon Sep 17 00:00:00 2001 From: Robin Kamboj Date: Thu, 27 Jul 2023 17:25:32 +0530 Subject: [PATCH 2/5] Update APIClient.swift -> api response beautification --- Source/Classes/API/APIClient.swift | 32 ++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/Source/Classes/API/APIClient.swift b/Source/Classes/API/APIClient.swift index 246ce3a..aee45c2 100755 --- a/Source/Classes/API/APIClient.swift +++ b/Source/Classes/API/APIClient.swift @@ -230,6 +230,14 @@ extension APIClient { } } } + + private func printRequest(router: Router) { + print("\n⎾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾⏋\n\n🛜🛜---HTTP REQUEST---🛜🛜\n\n\(router.method) ::: \(router.baseUrl)\(router.path)\n\nHEADERS ::: \(router.headers.json) \n\nPARAMS ::: \(router.params.json)\n⎿______________________________________________________________________________________________________________________⏌\n\n") + } + + private func printResponse(router: Router, response: DefaultDataResponse) { + print("\n⎾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾⏋\n🛜🛜---HTTP RESPONSE---🛜🛜\n[\(router.method) ::: \(router.baseUrl)\(router.path)]\n\nRESPONSE :::\nn\(response.data?.prettyPrintedJsonString ?? NSString())\n⎿____________________________________________________________________________________________________________________⏌\n\n") + } fileprivate func makeRequest (request: DataRequest, router: Router, completion: @escaping (_ result: APIResult) -> Void) { @@ -246,7 +254,7 @@ extension APIClient { } if self.enableLogs { - request.log() + printRequest(router: router) } request.response { [weak self] response in @@ -255,7 +263,7 @@ extension APIClient { return } if this.enableLogs { - response.log() + self?.printResponse(router: router, response: response) } func handleJson(_ json: JSON, code: Int) { @@ -454,3 +462,23 @@ extension DefaultDataResponse { } } } + +public extension Collection { + var json: String { + do { + let jsonData = try JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted]) + return String(data: jsonData, encoding: .utf8) ?? "" + } catch { + return "json serialization error: \(error)" + } + } +} + +public extension Data { + var prettyPrintedJsonString: NSString { + guard let object = try? JSONSerialization.jsonObject(with: self, options: []), + let data = try? JSONSerialization.data(withJSONObject: object, options: [.prettyPrinted]), + let prettyPrintedString = NSString(data: data, encoding: String.Encoding.utf8.rawValue) else { return NSString() } + return prettyPrintedString + } +} From 433859d73fb391a6ca30da429bcc858e1799a7aa Mon Sep 17 00:00:00 2001 From: Aman Kumar Date: Tue, 12 Sep 2023 16:17:08 +0530 Subject: [PATCH 3/5] added async await in client bumped up support version --- FoxAPIKit.xcodeproj/project.pbxproj | 8 +- Source/Classes/API/APIClient.swift | 678 +++++++++++++--------------- 2 files changed, 313 insertions(+), 373 deletions(-) diff --git a/FoxAPIKit.xcodeproj/project.pbxproj b/FoxAPIKit.xcodeproj/project.pbxproj index 69d06bd..78dd93d 100644 --- a/FoxAPIKit.xcodeproj/project.pbxproj +++ b/FoxAPIKit.xcodeproj/project.pbxproj @@ -402,7 +402,7 @@ ); INFOPLIST_FILE = FoxAPIKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nickelfox.FoxAPIKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -428,7 +428,7 @@ ); INFOPLIST_FILE = FoxAPIKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.nickelfox.FoxAPIKit; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -461,7 +461,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Debug; }; @@ -488,7 +488,7 @@ SKIP_INSTALL = YES; SWIFT_VERSION = 4.0; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Release; }; diff --git a/Source/Classes/API/APIClient.swift b/Source/Classes/API/APIClient.swift index aee45c2..a9c7f68 100755 --- a/Source/Classes/API/APIClient.swift +++ b/Source/Classes/API/APIClient.swift @@ -18,376 +18,318 @@ public let DefaultStatusCode = 0 public typealias JSON = JSONParsing.JSON public typealias AnyError = AnyErrorKit.AnyError +@available(iOS 13.0, *) open class APIClient { - - public var enableLogs = false - - public init() { - self.networkManager = NetworkReachabilityManager() - self.sessionManager = SessionManager(configuration: URLSessionConfiguration.default) - if let profileJSON = self.currentProfile { - do { - self.setAuthHeaders(try JSON(profileJSON)^) - } catch { - print("No saved auth headers found.") - } - } - } - - private func setAuthHeaders(_ authHeaders: U?) { - self.authHeaders = authHeaders - } - - private var currentProfile: Any? { - get { - return UserDefaults.standard.object(forKey: AuthHeadersKey) - } set { - UserDefaults.standard.set(newValue, forKey: AuthHeadersKey) - UserDefaults.standard.synchronize() - } - } - - public var authHeaders: U? = nil { - didSet { - guard let authHeaders = self.authHeaders else { - self.sessionManager.adapter = nil - return - } - self.sessionManager.adapter = authHeaders - self.currentProfile = authHeaders.toJSON() - } - } - - public var isAuthenticated: Bool { - if let headers = self.authHeaders { - return headers.isValid - } - return false - } - - fileprivate let sessionManager: SessionManager - fileprivate let networkManager: NetworkReachabilityManager? - - fileprivate func parseAuthenticationHeaders (_ response: HTTPURLResponse) { - self.authHeaders = self.authenticationHeaders(response: response) - } - - open func trackApiFailed(url: String, timeDiff: Int, size: Int) {} - - //Override this method in the subclass to set auth headers from the responses. - open func authenticationHeaders (response: HTTPURLResponse) -> U? { - return nil - } - - //Override this function to provide custom implementation of error parsing. - open func parseError(_ json: JSON, _ statusCode: Int) -> AnyError { - if let errorResponse = try? V.parse(json, code: statusCode) { - return errorResponse - } else { - return APIClientError.unknown - } - } - - fileprivate var isNetworkReachable: Bool { - guard let networkManager = self.networkManager else { - return false - } - return networkManager.isReachable - } - - open func clearAuthHeaders() { - self.authHeaders = nil - } - open func request (_ router: Router, completion: @escaping (_ result: APIResult) -> Void) { - let _ = self.requestInternal(router: router, completion: completion) + public var enableLogs = false + + public init() { + self.networkManager = NetworkReachabilityManager() + self.sessionManager = SessionManager(configuration: URLSessionConfiguration.default) + if let profileJSON = self.currentProfile { + do { + self.setAuthHeaders(try JSON(profileJSON)^) + } catch { + print("No saved auth headers found.") + } + } } - open func requestForceJSONParseable (_ router: Router, completion: @escaping (_ result: APIResult) -> Void) { - let _ = self.requestInternal(router: router, completion: completion) + private func setAuthHeaders(_ authHeaders: U?) { + self.authHeaders = authHeaders } - - open func request (_ router: Router, completion: @escaping (_ result: APIResult) -> Void) { - let _ = self.requestInternal(router: router, completion: completion) + + private var currentProfile: Any? { + get { + return UserDefaults.standard.object(forKey: AuthHeadersKey) + } set { + UserDefaults.standard.set(newValue, forKey: AuthHeadersKey) + UserDefaults.standard.synchronize() + } } - - open func request (_ urlRouter: URLRouter, completion: @escaping (_ result: APIResult) -> Void) { - if urlRouter.url.isFileURL { - self.requestWithFileUrl(urlRouter, completion: completion) + + public var authHeaders: U? = nil { + didSet { + guard let authHeaders = self.authHeaders else { + self.sessionManager.adapter = nil + return + } + self.sessionManager.adapter = authHeaders + self.currentProfile = authHeaders.toJSON() + } + } + + public var isAuthenticated: Bool { + if let headers = self.authHeaders { + return headers.isValid + } + return false + } + + fileprivate let sessionManager: SessionManager + fileprivate let networkManager: NetworkReachabilityManager? + + fileprivate func parseAuthenticationHeaders (_ response: HTTPURLResponse) { + self.authHeaders = self.authenticationHeaders(response: response) + } + + open func trackApiFailed(url: String, timeDiff: Int, size: Int) {} + + //Override this method in the subclass to set auth headers from the responses. + open func authenticationHeaders (response: HTTPURLResponse) -> U? { + return nil + } + + //Override this function to provide custom implementation of error parsing. + open func parseError(_ json: JSON, _ statusCode: Int) -> AnyError { + if let errorResponse = try? V.parse(json, code: statusCode) { + return errorResponse } else { - self.request(urlRouter, completion: completion) + return APIClientError.unknown + } + } + + fileprivate var isNetworkReachable: Bool { + guard let networkManager = self.networkManager else { + return false } + return networkManager.isReachable + } + + open func clearAuthHeaders() { + self.authHeaders = nil + } + + open func request (_ router: Router) async throws -> T { + try await self.requestInternal(router: router) + } + + open func requestForceJSONParseable (_ router: Router) async throws -> T { + try await self.requestInternal(router: router) + } + + open func request (_ router: Router) async throws -> T { + try await self.requestInternal(router: router) + } + + open func request (_ urlRouter: URLRouter) async throws -> T { + urlRouter.url.isFileURL ? try await self.requestWithFileUrl(urlRouter) : try await self.request(urlRouter) } } //MARK: Offline Request +@available(iOS 13.0, *) extension APIClient { - - fileprivate func requestWithFileUrl (_ urlRouter: URLRouter, completion: @escaping (_ result: APIResult) -> Void) { - let completionHandler: (_ result: APIResult) -> Void = { result in - DispatchQueue.main.async { - completion(result) - } - } - - if self.enableLogs { - print("Loading data from url: \(urlRouter.url.absoluteString)") - } - let queue: DispatchQueue = DispatchQueue(label: "url_load", attributes: []) - queue.async { - do { - let data = try Data(contentsOf: urlRouter.url) - if self.enableLogs { - print("Response at Url: \(urlRouter.url.absoluteString)") - print("\(String(data: data, encoding: .utf8) ?? ""))") + fileprivate func requestWithFileUrl (_ urlRouter: URLRouter) async throws -> T { + return try await withCheckedThrowingContinuation { continuation in + if self.enableLogs { + print("Loading data from url: \(urlRouter.url.absoluteString)") + } + let queue: DispatchQueue = DispatchQueue(label: "url_load", attributes: []) + queue.async { + do { + let data = try Data(contentsOf: urlRouter.url) + if self.enableLogs { + print("Response at Url: \(urlRouter.url.absoluteString)") + print("\(String(data: data, encoding: .utf8) ?? ""))") + } + let json = try JSON(data: data, options: JSONSerialization.ReadingOptions.allowFragments) + let result: T = try self.parse(json, router: urlRouter, 200) + continuation.resume(returning: result) + } catch { + continuation.resume(throwing: APIClientError.errorReadingUrl(urlRouter.url)) } - let json = try JSON(data: data, options: JSONSerialization.ReadingOptions.allowFragments) - let result: T = try self.parse(json, router: urlRouter, 200) - completionHandler(.success(result)) - } catch let error as AnyError { - completionHandler(.failure(error)) - } catch { - completionHandler(.failure(APIClientError.errorReadingUrl(urlRouter.url))) - } - } - } - + } + } + } } //MARK: JSON Request +@available(iOS 13.0, *) extension APIClient { - - fileprivate func requestInternal (router: Router, completion: @escaping (_ result: APIResult) -> Void) -> Request { - + fileprivate func requestInternal (router: Router) async throws -> T { //Make request let request = self.sessionManager.request(router) - self.makeRequest(request: request, router: router, completion: completion) - return request + return try await self.makeRequest(request: request, router: router) } - - fileprivate func requestInternal (router: Router, completion: @escaping (_ result: APIResult) -> Void) -> Request { - - //Make request - let request = self.sessionManager.request(router) - self.makeRequest(request: request, router: router, completion: completion) - return request - } - - fileprivate func makeRequest (request: DataRequest, router: Router, completion: @escaping (_ result: APIResult) -> Void) { - - let completionHandler: (_ result: APIResult) -> Void = { result in - DispatchQueue.main.async { - completion(result) - } - } - - //Reachability Check - if !self.isNetworkReachable { - completionHandler(.failure(APIClientError.noInternet)) - return - } - - if self.enableLogs { - request.log() - } - request.response { [weak self] response in - guard let this = self else { - completionHandler(.failure(APIClientError.unknown)) + + fileprivate func requestInternal (router: Router) async throws -> T { + //Make request + let request = self.sessionManager.request(router) + return try await self.makeRequest(request: request, router: router) + } + + fileprivate func makeRequest (request: DataRequest, router: Router) async throws -> T { + return try await withCheckedThrowingContinuation { continuation in + //Reachability Check + if !self.isNetworkReachable { + continuation.resume(throwing: APIClientError.noInternet) return } - if this.enableLogs { - response.log() - } - func handleJson(_ json: JSON, code: Int) { - if let httpResponse = response.response { - //Parse Auth Headers - this.parseAuthenticationHeaders(httpResponse) + if self.enableLogs { + request.log() + } + request.response { [weak self] httpResponse in + guard let this = self else { + continuation.resume(throwing: APIClientError.unknown) + return } - do { - let result: T = try this.parse(json, router: router, code) - completionHandler(.success(result)) - } catch let apiError as AnyError { - completionHandler(.failure(apiError)) - } catch { - completionHandler(.failure(error as NSError)) + if this.enableLogs { + httpResponse.log() } - } - - let code = response.response?.statusCode ?? DefaultStatusCode - var json = JSON.null - if let data = response.data { - do { - json = try JSON(data: data, options: .allowFragments) - } catch { - completionHandler(.failure(error as NSError)) + + func handleJson(_ json: JSON, code: Int) { + if let httpResponse = httpResponse.response { + //Parse Auth Headers + this.parseAuthenticationHeaders(httpResponse) + } + do { + let result: T = try this.parse(json, router: router, code) + continuation.resume(returning: result) + } catch { + continuation.resume(throwing: error) + } + } + + let code = httpResponse.response?.statusCode ?? DefaultStatusCode + var json = JSON.null + if let data = httpResponse.data { + do { + json = try JSON(data: data, options: .allowFragments) + } catch { + continuation.resume(throwing: error) + } + } + if 200...299 ~= code { + handleJson(json, code: code) + } else { + continuation.resume(throwing: this.parseError(json, code)) } - } - if 200...299 ~= code { - handleJson(json, code: code) - } else { - completionHandler(.failure(this.parseError(json, code))) } } } - - private func printRequest(router: Router) { - print("\n⎾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾⏋\n\n🛜🛜---HTTP REQUEST---🛜🛜\n\n\(router.method) ::: \(router.baseUrl)\(router.path)\n\nHEADERS ::: \(router.headers.json) \n\nPARAMS ::: \(router.params.json)\n⎿______________________________________________________________________________________________________________________⏌\n\n") - } - - private func printResponse(router: Router, response: DefaultDataResponse) { - print("\n⎾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾⏋\n🛜🛜---HTTP RESPONSE---🛜🛜\n[\(router.method) ::: \(router.baseUrl)\(router.path)]\n\nRESPONSE :::\nn\(response.data?.prettyPrintedJsonString ?? NSString())\n⎿____________________________________________________________________________________________________________________⏌\n\n") - } - fileprivate func makeRequest (request: DataRequest, router: Router, completion: @escaping (_ result: APIResult) -> Void) { - - let completionHandler: (_ result: APIResult) -> Void = { result in - DispatchQueue.main.async { - completion(result) - } - } - - //Reachability Check - if !self.isNetworkReachable { - completionHandler(.failure(APIClientError.noInternet)) - return - } - - if self.enableLogs { - printRequest(router: router) - } + private func printRequest(router: Router) { + print("\n⎾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾⏋\n\n🛜🛜---HTTP REQUEST---🛜🛜\n\n\(router.method) ::: \(router.baseUrl)\(router.path)\n\nHEADERS ::: \(router.headers.json) \n\nPARAMS ::: \(router.params.json)\n⎿______________________________________________________________________________________________________________________⏌\n\n") + } - request.response { [weak self] response in - guard let this = self else { - completionHandler(.failure(APIClientError.unknown)) - return - } - if this.enableLogs { - self?.printResponse(router: router, response: response) - } - - func handleJson(_ json: JSON, code: Int) { - if let httpResponse = response.response { - //Parse Auth Headers - this.parseAuthenticationHeaders(httpResponse) - } - do { - let result: T = try this.parse(json, router: router, code) - completionHandler(.success(result)) - } catch let apiError as AnyError { - completionHandler(.failure(apiError)) - } catch { - completionHandler(.failure(error as NSError)) - } - } - - let code = response.response?.statusCode ?? DefaultStatusCode - var json = JSON.null - if let data = response.data { - do { - json = try JSON(data: data, options: .allowFragments) - } catch { - completionHandler(.failure(error as NSError)) + private func printResponse(router: Router, response: DefaultDataResponse) { + print("\n⎾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾⏋\n🛜🛜---HTTP RESPONSE---🛜🛜\n[\(router.method) ::: \(router.baseUrl)\(router.path)]\n\nRESPONSE :::\nn\(response.data?.prettyPrintedJsonString ?? NSString())\n⎿____________________________________________________________________________________________________________________⏌\n\n") + } + + fileprivate func makeRequest (request: DataRequest, router: Router) async throws -> T { + + return try await withCheckedThrowingContinuation { continuation in + //Reachability Check + if !self.isNetworkReachable { + continuation.resume(throwing: APIClientError.noInternet) + return + } + + printRequest(router: router) + + request.response { [weak self] response1 in + guard let this = self else { + continuation.resume(throwing: APIClientError.unknown) + return + } + this.printResponse(router: router, response: response1) + + func handleJson(_ json: JSON, code: Int) { + if let httpResponse = response1.response { + //Parse Auth Headers + this.parseAuthenticationHeaders(httpResponse) + } + do { + let result: T = try this.parse(json, router: router, code) + continuation.resume(returning: result) + } catch { + continuation.resume(throwing: error) + } + } + + let code = response1.response?.statusCode ?? DefaultStatusCode + var json = JSON.null + if let data = response1.data { + do { + json = try JSON(data: data, options: .allowFragments) + } catch { + continuation.resume(throwing: error) + } } - } - if 200...299 ~= code { - handleJson(json, code: code) - } else { - completionHandler(.failure(this.parseError(json, code))) - } - } - } + if 200...299 ~= code { + handleJson(json, code: code) + } else { + continuation.resume(throwing: this.parseError(json, code)) + } + } + } + } } //MARK: Multipart Request +@available(iOS 13.0, *) extension APIClient { - + public func multipartRequest (_ router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { + try await self.multipartRequestInternal(router: router, multipartFormData: multipartFormData) + } - public func multipartRequest ( - _ router: Router, - multipartFormData: @escaping (MultipartFormData) -> Void, - completion: @escaping (_ result: APIResult) -> Void) { - - self.multipartRequestInternal( - router: router, - multipartFormData: multipartFormData, - completion: completion - ) - } - - public func multipartRequestForceJSONParseable ( - _ router: Router, - multipartFormData: @escaping (MultipartFormData) -> Void, - completion: @escaping (_ result: APIResult) -> Void) { - - self.multipartRequestInternal( - router: router, - multipartFormData: multipartFormData, - completion: completion - ) + public func multipartRequestForceJSONParseable (_ router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { + try await self.multipartRequestInternal(router: router, multipartFormData: multipartFormData) } - - public func multipartRequest ( - _ router: Router, - multipartFormData: @escaping (MultipartFormData) -> Void, - completion: @escaping (_ result: APIResult) -> Void) { - - self.multipartRequestInternal( - router: router, - multipartFormData: multipartFormData, - completion: completion - ) + + public func multipartRequest (_ router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { + try await self.multipartRequestInternal(router: router, multipartFormData: multipartFormData) } - - fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void, completion: @escaping (_ result: APIResult) -> Void) { - let completionHandler: (_ result: APIResult) -> Void = { result in - DispatchQueue.main.async { - completion(result) - } - } - - //Make request - self.sessionManager.upload( - multipartFormData: multipartFormData, with: router) { [weak self] encodingResult in - guard let this = self else { - completionHandler(.failure(APIClientError.unknown)) - return - } - switch encodingResult { - case .success(let upload, _, _): - this.makeRequest(request: upload, router: router, completion: completion) - case .failure(let encodingError): - completionHandler(.failure(this.parseError(encodingError as NSError?))) - } - } - } - fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void, completion: @escaping (_ result: APIResult) -> Void) { - let completionHandler: (_ result: APIResult) -> Void = { result in - DispatchQueue.main.async { - completion(result) + fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { + //Make request + return try await withCheckedThrowingContinuation { continuation in + let completion: (SessionManager.MultipartFormDataEncodingResult) -> Void = { [weak self] encodingResult in + guard let this = self else { + continuation.resume(throwing: APIClientError.unknown) + return + } + switch encodingResult { + case .success(let upload, _, _): + Task { + let res:T = try await this.makeRequest(request: upload, router: router) + continuation.resume(returning: res) + } + case .failure(let encodingError): + continuation.resume(throwing: this.parseError(encodingError as NSError)) + } } + self.sessionManager.upload(multipartFormData: multipartFormData, with: router, encodingCompletion: completion) } - + } + + fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { //Make request - self.sessionManager.upload( - multipartFormData: multipartFormData, with: router) { [weak self] encodingResult in - guard let this = self else { - completionHandler(.failure(APIClientError.unknown)) - return - } - switch encodingResult { - case .success(let upload, _, _): - this.makeRequest(request: upload, router: router, completion: completion) - case .failure(let encodingError): - completionHandler(.failure(this.parseError(encodingError as NSError?))) + return try await withCheckedThrowingContinuation { continuation in + let completion:(SessionManager.MultipartFormDataEncodingResult) -> Void = { [weak self] encodingResult in + guard let this = self else { + continuation.resume(throwing: APIClientError.unknown) + return + } + switch encodingResult { + case .success(let upload, _, _): + Task { + let res:T = try await this.makeRequest(request: upload, router: router) + continuation.resume(returning: res) + } + case .failure(let encodingError): + continuation.resume(throwing: this.parseError(encodingError as NSError?)) + } } + self.sessionManager.upload(multipartFormData: multipartFormData, with: router, encodingCompletion: completion) } } } +@available(iOS 13.0, *) extension APIClient { - fileprivate func parse (_ json: JSON, router: Router, _ statusCode: Int) throws -> T { do { var jsonToParse = json @@ -406,61 +348,59 @@ extension APIClient { } } - fileprivate func parse (_ json: JSON, router: Router, _ statusCode: Int) throws -> T { - do { - var jsonToParse = json - //if map keypath is provided then try to map data at that keypath - if let keypathToMap = router.keypathToMap { - jsonToParse = json.jsonAtKeyPath(keypath: keypathToMap) - } - return try T.parse(jsonToParse) - } catch let apiError as AnyError { - throw apiError - } catch let error as NSError { - throw error - } catch { - throw APIClientError.unknown - } - } - - fileprivate func parseError(_ error: NSError?) -> AnyError { - if let error = error { - return error - } else { - return APIClientError.unknown - } - } + fileprivate func parse (_ json: JSON, router: Router, _ statusCode: Int) throws -> T { + do { + var jsonToParse = json + //if map keypath is provided then try to map data at that keypath + if let keypathToMap = router.keypathToMap { + jsonToParse = json.jsonAtKeyPath(keypath: keypathToMap) + } + return try T.parse(jsonToParse) + } catch let apiError as AnyError { + throw apiError + } catch let error as NSError { + throw error + } catch { + throw APIClientError.unknown + } + } + + fileprivate func parseError(_ error: NSError?) -> AnyError { + if let error = error { + return error + } else { + return APIClientError.unknown + } + } } extension Request { - - func log() { - if let request = self.request, - let url = request.url, - let headers = request.allHTTPHeaderFields, - let method = request.httpMethod { - print("Request: \(method) \(url)") - print("Headers: \(headers)") - if let data = request.httpBody, let params = String.init(data: data, encoding: .utf8) { - print("Params: \(params)") - } - } - } + func log() { + if let request = self.request, + let url = request.url, + let headers = request.allHTTPHeaderFields, + let method = request.httpMethod { + print("Request: \(method) \(url)") + print("Headers: \(headers)") + if let data = request.httpBody, let params = String.init(data: data, encoding: .utf8) { + print("Params: \(params)") + } + } + } } extension DefaultDataResponse { - - func log() { - if let response = self.response, - let data = self.data, - let utf8 = String.init(data: data, encoding: .utf8), - let url = self.request?.url { - let statusCode = response.statusCode - print("Response for \(url):") - print("Status Code: \(statusCode)") - print("Data: \(utf8)") - } - } + func log() { + if let response = self.response, + let data = self.data, + let utf8 = String.init(data: data, encoding: .utf8), + let url = self.request?.url { + let statusCode = response.statusCode + print("Response for \(url):") + print("Status Code: \(statusCode)") + print("Data: \(utf8)") + } + } } public extension Collection { From 322e32e1c0acff9b47187cf06c112f3a63f51d29 Mon Sep 17 00:00:00 2001 From: Aman Kumar Date: Tue, 12 Sep 2023 16:53:25 +0530 Subject: [PATCH 4/5] added back original implementation of methods --- Source/Classes/API/APIClient.swift | 264 +++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) diff --git a/Source/Classes/API/APIClient.swift b/Source/Classes/API/APIClient.swift index a9c7f68..6a45b0b 100755 --- a/Source/Classes/API/APIClient.swift +++ b/Source/Classes/API/APIClient.swift @@ -100,18 +100,38 @@ open class APIClient { self.authHeaders = nil } + open func request (_ router: Router, completion: @escaping (_ result: APIResult) -> Void) { + let _ = self.requestInternal(router: router, completion: completion) + } + open func request (_ router: Router) async throws -> T { try await self.requestInternal(router: router) } + open func requestForceJSONParseable (_ router: Router, completion: @escaping (_ result: APIResult) -> Void) { + let _ = self.requestInternal(router: router, completion: completion) + } + open func requestForceJSONParseable (_ router: Router) async throws -> T { try await self.requestInternal(router: router) } + open func request (_ router: Router, completion: @escaping (_ result: APIResult) -> Void) { + let _ = self.requestInternal(router: router, completion: completion) + } + open func request (_ router: Router) async throws -> T { try await self.requestInternal(router: router) } + open func request (_ urlRouter: URLRouter, completion: @escaping (_ result: APIResult) -> Void) { + if urlRouter.url.isFileURL { + self.requestWithFileUrl(urlRouter, completion: completion) + } else { + self.request(urlRouter, completion: completion) + } + } + open func request (_ urlRouter: URLRouter) async throws -> T { urlRouter.url.isFileURL ? try await self.requestWithFileUrl(urlRouter) : try await self.request(urlRouter) } @@ -120,6 +140,35 @@ open class APIClient { //MARK: Offline Request @available(iOS 13.0, *) extension APIClient { + fileprivate func requestWithFileUrl (_ urlRouter: URLRouter, completion: @escaping (_ result: APIResult) -> Void) { + let completionHandler: (_ result: APIResult) -> Void = { result in + DispatchQueue.main.async { + completion(result) + } + } + + if self.enableLogs { + print("Loading data from url: \(urlRouter.url.absoluteString)") + } + let queue: DispatchQueue = DispatchQueue(label: "url_load", attributes: []) + queue.async { + do { + let data = try Data(contentsOf: urlRouter.url) + if self.enableLogs { + print("Response at Url: \(urlRouter.url.absoluteString)") + print("\(String(data: data, encoding: .utf8) ?? ""))") + } + let json = try JSON(data: data, options: JSONSerialization.ReadingOptions.allowFragments) + let result: T = try self.parse(json, router: urlRouter, 200) + completionHandler(.success(result)) + } catch let error as AnyError { + completionHandler(.failure(error)) + } catch { + completionHandler(.failure(APIClientError.errorReadingUrl(urlRouter.url))) + } + } + } + fileprivate func requestWithFileUrl (_ urlRouter: URLRouter) async throws -> T { return try await withCheckedThrowingContinuation { continuation in if self.enableLogs { @@ -147,18 +196,91 @@ extension APIClient { //MARK: JSON Request @available(iOS 13.0, *) extension APIClient { + + fileprivate func requestInternal (router: Router, completion: @escaping (_ result: APIResult) -> Void) -> Request { + //Make request + let request = self.sessionManager.request(router) + self.makeRequest(request: request, router: router, completion: completion) + return request + } + fileprivate func requestInternal (router: Router) async throws -> T { //Make request let request = self.sessionManager.request(router) return try await self.makeRequest(request: request, router: router) } + fileprivate func requestInternal (router: Router, completion: @escaping (_ result: APIResult) -> Void) -> Request { + //Make request + let request = self.sessionManager.request(router) + self.makeRequest(request: request, router: router, completion: completion) + return request + } + fileprivate func requestInternal (router: Router) async throws -> T { //Make request let request = self.sessionManager.request(router) return try await self.makeRequest(request: request, router: router) } + fileprivate func makeRequest (request: DataRequest, router: Router, completion: @escaping (_ result: APIResult) -> Void) { + + let completionHandler: (_ result: APIResult) -> Void = { result in + DispatchQueue.main.async { + completion(result) + } + } + + //Reachability Check + if !self.isNetworkReachable { + completionHandler(.failure(APIClientError.noInternet)) + return + } + + if self.enableLogs { + request.log() + } + request.response { [weak self] response in + guard let this = self else { + completionHandler(.failure(APIClientError.unknown)) + return + } + if this.enableLogs { + response.log() + } + + func handleJson(_ json: JSON, code: Int) { + if let httpResponse = response.response { + //Parse Auth Headers + this.parseAuthenticationHeaders(httpResponse) + } + do { + let result: T = try this.parse(json, router: router, code) + completionHandler(.success(result)) + } catch let apiError as AnyError { + completionHandler(.failure(apiError)) + } catch { + completionHandler(.failure(error as NSError)) + } + } + + let code = response.response?.statusCode ?? DefaultStatusCode + var json = JSON.null + if let data = response.data { + do { + json = try JSON(data: data, options: .allowFragments) + } catch { + completionHandler(.failure(error as NSError)) + } + } + if 200...299 ~= code { + handleJson(json, code: code) + } else { + completionHandler(.failure(this.parseError(json, code))) + } + } + } + fileprivate func makeRequest (request: DataRequest, router: Router) async throws -> T { return try await withCheckedThrowingContinuation { continuation in //Reachability Check @@ -218,6 +340,65 @@ extension APIClient { print("\n⎾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾⏋\n🛜🛜---HTTP RESPONSE---🛜🛜\n[\(router.method) ::: \(router.baseUrl)\(router.path)]\n\nRESPONSE :::\nn\(response.data?.prettyPrintedJsonString ?? NSString())\n⎿____________________________________________________________________________________________________________________⏌\n\n") } + fileprivate func makeRequest (request: DataRequest, router: Router, completion: @escaping (_ result: APIResult) -> Void) { + + let completionHandler: (_ result: APIResult) -> Void = { result in + DispatchQueue.main.async { + completion(result) + } + } + + //Reachability Check + if !self.isNetworkReachable { + completionHandler(.failure(APIClientError.noInternet)) + return + } + + if self.enableLogs { + printRequest(router: router) + } + + request.response { [weak self] response in + guard let this = self else { + completionHandler(.failure(APIClientError.unknown)) + return + } + if this.enableLogs { + self?.printResponse(router: router, response: response) + } + + func handleJson(_ json: JSON, code: Int) { + if let httpResponse = response.response { + //Parse Auth Headers + this.parseAuthenticationHeaders(httpResponse) + } + do { + let result: T = try this.parse(json, router: router, code) + completionHandler(.success(result)) + } catch let apiError as AnyError { + completionHandler(.failure(apiError)) + } catch { + completionHandler(.failure(error as NSError)) + } + } + + let code = response.response?.statusCode ?? DefaultStatusCode + var json = JSON.null + if let data = response.data { + do { + json = try JSON(data: data, options: .allowFragments) + } catch { + completionHandler(.failure(error as NSError)) + } + } + if 200...299 ~= code { + handleJson(json, code: code) + } else { + completionHandler(.failure(this.parseError(json, code))) + } + } + } + fileprivate func makeRequest (request: DataRequest, router: Router) async throws -> T { return try await withCheckedThrowingContinuation { continuation in @@ -271,18 +452,78 @@ extension APIClient { //MARK: Multipart Request @available(iOS 13.0, *) extension APIClient { + + public func multipartRequest ( + _ router: Router, + multipartFormData: @escaping (MultipartFormData) -> Void, + completion: @escaping (_ result: APIResult) -> Void) { + + self.multipartRequestInternal( + router: router, + multipartFormData: multipartFormData, + completion: completion + ) + } + public func multipartRequest (_ router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { try await self.multipartRequestInternal(router: router, multipartFormData: multipartFormData) } + public func multipartRequestForceJSONParseable ( + _ router: Router, + multipartFormData: @escaping (MultipartFormData) -> Void, + completion: @escaping (_ result: APIResult) -> Void) { + + self.multipartRequestInternal( + router: router, + multipartFormData: multipartFormData, + completion: completion + ) + } + public func multipartRequestForceJSONParseable (_ router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { try await self.multipartRequestInternal(router: router, multipartFormData: multipartFormData) } + public func multipartRequest ( + _ router: Router, + multipartFormData: @escaping (MultipartFormData) -> Void, + completion: @escaping (_ result: APIResult) -> Void) { + + self.multipartRequestInternal( + router: router, + multipartFormData: multipartFormData, + completion: completion + ) + } + public func multipartRequest (_ router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { try await self.multipartRequestInternal(router: router, multipartFormData: multipartFormData) } + fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void, completion: @escaping (_ result: APIResult) -> Void) { + let completionHandler: (_ result: APIResult) -> Void = { result in + DispatchQueue.main.async { + completion(result) + } + } + + //Make request + self.sessionManager.upload( + multipartFormData: multipartFormData, with: router) { [weak self] encodingResult in + guard let this = self else { + completionHandler(.failure(APIClientError.unknown)) + return + } + switch encodingResult { + case .success(let upload, _, _): + this.makeRequest(request: upload, router: router, completion: completion) + case .failure(let encodingError): + completionHandler(.failure(this.parseError(encodingError as NSError?))) + } + } + } + fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { //Make request return try await withCheckedThrowingContinuation { continuation in @@ -305,6 +546,29 @@ extension APIClient { } } + fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void, completion: @escaping (_ result: APIResult) -> Void) { + let completionHandler: (_ result: APIResult) -> Void = { result in + DispatchQueue.main.async { + completion(result) + } + } + + //Make request + self.sessionManager.upload( + multipartFormData: multipartFormData, with: router) { [weak self] encodingResult in + guard let this = self else { + completionHandler(.failure(APIClientError.unknown)) + return + } + switch encodingResult { + case .success(let upload, _, _): + this.makeRequest(request: upload, router: router, completion: completion) + case .failure(let encodingError): + completionHandler(.failure(this.parseError(encodingError as NSError?))) + } + } + } + fileprivate func multipartRequestInternal (router: Router, multipartFormData: @escaping (MultipartFormData) -> Void) async throws -> T { //Make request return try await withCheckedThrowingContinuation { continuation in From acb458cd966ed4443322dd4472924fd6694e22e2 Mon Sep 17 00:00:00 2001 From: Aman Kumar Date: Tue, 12 Sep 2023 19:24:46 +0530 Subject: [PATCH 5/5] removed extra return --- Source/Classes/API/APIClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Classes/API/APIClient.swift b/Source/Classes/API/APIClient.swift index 6a45b0b..a191d1d 100755 --- a/Source/Classes/API/APIClient.swift +++ b/Source/Classes/API/APIClient.swift @@ -436,7 +436,7 @@ extension APIClient { do { json = try JSON(data: data, options: .allowFragments) } catch { - continuation.resume(throwing: error) + //continuation.resume(throwing: error) } } if 200...299 ~= code {