From f8b7599c074f7369bb02912a45c6be613a690e0d Mon Sep 17 00:00:00 2001 From: Codecat15 <44252456+codecat15@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:19:20 +0530 Subject: [PATCH 1/3] code clean up performing minor code clean up --- HttpUtility.xcodeproj/project.pbxproj | 4 + HttpUtility/HttpRequestHandler.swift | 81 ++++++++++++++++ HttpUtility/HttpUtility.swift | 132 ++++++++------------------ 3 files changed, 124 insertions(+), 93 deletions(-) create mode 100644 HttpUtility/HttpRequestHandler.swift diff --git a/HttpUtility.xcodeproj/project.pbxproj b/HttpUtility.xcodeproj/project.pbxproj index c0f83f8..3ce358e 100644 --- a/HttpUtility.xcodeproj/project.pbxproj +++ b/HttpUtility.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 8280CC552B39647B0014F1D6 /* HttpRequestHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8280CC542B39647B0014F1D6 /* HttpRequestHandler.swift */; }; 86010D0725CE240300A4E362 /* batman.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 86010D0625CE240300A4E362 /* batman.jpg */; }; 86521B5625C6FD7200E05422 /* HURequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86521B5525C6FD7100E05422 /* HURequest.swift */; }; 8656BC582483E3C60023549D /* EncodableExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8656BC572483E3C60023549D /* EncodableExtension.swift */; }; @@ -32,6 +33,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 8280CC542B39647B0014F1D6 /* HttpRequestHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HttpRequestHandler.swift; sourceTree = ""; }; 86010D0625CE240300A4E362 /* batman.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = batman.jpg; sourceTree = ""; }; 86521B5525C6FD7100E05422 /* HURequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HURequest.swift; sourceTree = ""; }; 8656BC572483E3C60023549D /* EncodableExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncodableExtension.swift; sourceTree = ""; }; @@ -129,6 +131,7 @@ 86719E9924720BD1002A2AB0 /* HttpUtility.h */, 86719E9A24720BD1002A2AB0 /* Info.plist */, 86719EB024720E40002A2AB0 /* HttpUtility.swift */, + 8280CC542B39647B0014F1D6 /* HttpRequestHandler.swift */, ); path = HttpUtility; sourceTree = ""; @@ -258,6 +261,7 @@ files = ( 86CAEFE625BBBE98006A7791 /* HUNetworkError.swift in Sources */, 86719EB124720E40002A2AB0 /* HttpUtility.swift in Sources */, + 8280CC552B39647B0014F1D6 /* HttpRequestHandler.swift in Sources */, 8656BC582483E3C60023549D /* EncodableExtension.swift in Sources */, 86521B5625C6FD7200E05422 /* HURequest.swift in Sources */, 86CAEFEA25BBBEDE006A7791 /* HUHttpMethods.swift in Sources */, diff --git a/HttpUtility/HttpRequestHandler.swift b/HttpUtility/HttpRequestHandler.swift new file mode 100644 index 0000000..fff3244 --- /dev/null +++ b/HttpUtility/HttpRequestHandler.swift @@ -0,0 +1,81 @@ +// +// HttpRequestHandler.swift +// HttpUtility +// +// Created by Rocket Racoon on 12/25/23. +// Copyright © 2023 CodeCat15. All rights reserved. +// + +import Foundation + +final public class HttpRequestHandler { + + private let authenticationToken: String? + private let customJsonDecoder: JSONDecoder? + + init(authenticationToken: String?, customJsonDecoder: JSONDecoder?) { + self.authenticationToken = authenticationToken + self.customJsonDecoder = customJsonDecoder + } + + // MARK: - Perform Operation + func performOperation(requestUrl: URLRequest, + responseType: T.Type, + completionHandler:@escaping(Result) -> Void) + { + URLSession.shared.dataTask(with: requestUrl) { (data, httpUrlResponse, error) in + + let statusCode = (httpUrlResponse as? HTTPURLResponse)?.statusCode + + if(error == nil && data != nil && data?.isEmpty == false) { + let response = self.decodeJsonResponse(data: data!, responseType: responseType) + + // return success + if(response != nil) { + completionHandler(.success(response)) + }else { + + // return failure + completionHandler(.failure(HUNetworkError(withServerResponse: data, + forRequestUrl: requestUrl.url!, + withHttpBody: requestUrl.httpBody, + errorMessage: error.debugDescription, + forStatusCode: statusCode!))) + } + } + else { + let networkError = HUNetworkError(withServerResponse: data, + forRequestUrl: requestUrl.url!, + withHttpBody: requestUrl.httpBody, + errorMessage:error.debugDescription, + forStatusCode: statusCode!) + + // return failure + completionHandler(.failure(networkError)) + } + + }.resume() + } + + // MARK: Private method + private func createJsonDecoder() -> JSONDecoder + { + let decoder = customJsonDecoder != nil ? customJsonDecoder! : JSONDecoder() + if(customJsonDecoder == nil) { + decoder.dateDecodingStrategy = .iso8601 + } + return decoder + } + + private func decodeJsonResponse(data: Data, responseType: T.Type) -> T? + { + let decoder = createJsonDecoder() + do { + return try decoder.decode(responseType, from: data) + } + catch let error { + debugPrint("error while decoding JSON response =>\(error.localizedDescription)") + } + return nil + } +} diff --git a/HttpUtility/HttpUtility.swift b/HttpUtility/HttpUtility.swift index 803ef8e..112eb95 100644 --- a/HttpUtility/HttpUtility.swift +++ b/HttpUtility/HttpUtility.swift @@ -8,12 +8,17 @@ import Foundation -public class HttpUtility +final public class HttpUtility { public static let shared = HttpUtility() public var authenticationToken : String? = nil public var customJsonDecoder : JSONDecoder? = nil - + + private lazy var httpRequestHandler: HttpRequestHandler = { + return HttpRequestHandler(authenticationToken: authenticationToken, + customJsonDecoder: customJsonDecoder) + }() + private init(){} public func request(huRequest: HURequest, resultType: T.Type, completionHandler:@escaping(Result)-> Void) @@ -23,68 +28,49 @@ public class HttpUtility case .get: getData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0)} break - + case .post: postData(request: huRequest, resultType: resultType) { completionHandler($0)} break - + case .put: putData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0)} break - + case .delete: deleteData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0)} break } } - + // MARK: - Multipart public func requestWithMultiPartFormData(multiPartRequest: HUMultiPartRequest, responseType: T.Type, completionHandler:@escaping(Result)-> Void) { postMultiPartFormData(request: multiPartRequest) { completionHandler($0) } } - - // MARK: - Private functions - private func createJsonDecoder() -> JSONDecoder - { - let decoder = customJsonDecoder != nil ? customJsonDecoder! : JSONDecoder() - if(customJsonDecoder == nil) { - decoder.dateDecodingStrategy = .iso8601 - } - return decoder - } + // MARK: - Private functions private func createUrlRequest(requestUrl: URL) -> URLRequest { var urlRequest = URLRequest(url: requestUrl) - if(authenticationToken != nil) { - urlRequest.setValue(authenticationToken!, forHTTPHeaderField: "authorization") + + if let authToken = authenticationToken { + urlRequest.setValue(authToken, forHTTPHeaderField: "authorization") } return urlRequest } - - private func decodeJsonResponse(data: Data, responseType: T.Type) -> T? - { - let decoder = createJsonDecoder() - do { - return try decoder.decode(responseType, from: data) - }catch let error { - debugPrint("error while decoding JSON response =>\(error.localizedDescription)") - } - return nil - } - + // MARK: - GET Api private func getData(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result)-> Void) { var urlRequest = self.createUrlRequest(requestUrl: requestUrl) urlRequest.httpMethod = HUHttpMethods.get.rawValue - - performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in + + httpRequestHandler.performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in completionHandler(result) } } - + // MARK: - POST Api private func postData(request: HURequest, resultType: T.Type, completionHandler:@escaping(Result)-> Void) { @@ -92,12 +78,12 @@ public class HttpUtility urlRequest.httpMethod = HUHttpMethods.post.rawValue urlRequest.httpBody = request.requestBody urlRequest.addValue("application/json", forHTTPHeaderField: "content-type") - - performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in + + httpRequestHandler.performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in completionHandler(result) } } - + private func postMultiPartFormData(request: HUMultiPartRequest, completionHandler:@escaping(Result)-> Void) { let boundary = "-----------------------------\(UUID().uuidString)" @@ -105,40 +91,22 @@ public class HttpUtility var urlRequest = self.createUrlRequest(requestUrl: request.url) urlRequest.httpMethod = HUHttpMethods.post.rawValue urlRequest.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") - + var postBody = Data() - - let requestDictionary = request.request.convertToDictionary() - if(requestDictionary != nil) - { - requestDictionary?.forEach({ (key, value) in - if(value != nil) { - let strValue = value.map { String(describing: $0) } - if(strValue != nil && strValue?.count != 0) { - postBody.append("--\(boundary + lineBreak)" .data(using: .utf8)!) - postBody.append("Content-Disposition: form-data; name=\"\(key)\" \(lineBreak + lineBreak)" .data(using: .utf8)!) - postBody.append("\(strValue! + lineBreak)".data(using: .utf8)!) - } + + if let requestDictionary = request.request.convertToDictionary() { + for (key, value) in requestDictionary { + if let strValue = value.map({ String(describing: $0) }), !strValue.isEmpty { + postBody.append("--\(boundary + lineBreak)".data(using: .utf8)!) + postBody.append("Content-Disposition: form-data; name=\"\(key)\" \(lineBreak + lineBreak)".data(using: .utf8)!) + postBody.append("\(strValue + lineBreak)".data(using: .utf8)!) } - }) - - // TODO: Next release -// if(huRequest.media != nil) { -// huRequest.media?.forEach({ (media) in -// postBody.append("--\(boundary + lineBreak)" .data(using: .utf8)!) -// postBody.append("Content-Disposition: form-data; name=\"\(media.parameterName)\"; filename=\"\(media.fileName)\" \(lineBreak + lineBreak)" .data(using: .utf8)!) -// postBody.append("Content-Type: \(media.mimeType + lineBreak + lineBreak)" .data(using: .utf8)!) -// postBody.append(media.data) -// postBody.append(lineBreak .data(using: .utf8)!) -// }) -// } - - postBody.append("--\(boundary)--\(lineBreak)" .data(using: .utf8)!) - + } + postBody.append("--\(boundary)--\(lineBreak)".data(using: .utf8)!) urlRequest.addValue("\(postBody.count)", forHTTPHeaderField: "Content-Length") urlRequest.httpBody = postBody - - performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in + + httpRequestHandler.performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in completionHandler(result) } } @@ -149,42 +117,20 @@ public class HttpUtility { var urlRequest = self.createUrlRequest(requestUrl: requestUrl) urlRequest.httpMethod = HUHttpMethods.put.rawValue - - performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in + + httpRequestHandler.performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in completionHandler(result) } } - + // MARK: - DELETE Api private func deleteData(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result)-> Void) { var urlRequest = self.createUrlRequest(requestUrl: requestUrl) urlRequest.httpMethod = HUHttpMethods.delete.rawValue - - performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in + + httpRequestHandler.performOperation(requestUrl: urlRequest, responseType: T.self) { (result) in completionHandler(result) } } - - // MARK: - Perform data task - private func performOperation(requestUrl: URLRequest, responseType: T.Type, completionHandler:@escaping(Result) -> Void) - { - URLSession.shared.dataTask(with: requestUrl) { (data, httpUrlResponse, error) in - - let statusCode = (httpUrlResponse as? HTTPURLResponse)?.statusCode - if(error == nil && data != nil && data?.count != 0) { - let response = self.decodeJsonResponse(data: data!, responseType: responseType) - if(response != nil) { - completionHandler(.success(response)) - }else { - completionHandler(.failure(HUNetworkError(withServerResponse: data, forRequestUrl: requestUrl.url!, withHttpBody: requestUrl.httpBody, errorMessage: error.debugDescription, forStatusCode: statusCode!))) - } - } - else { - let networkError = HUNetworkError(withServerResponse: data, forRequestUrl: requestUrl.url!, withHttpBody: requestUrl.httpBody, errorMessage: error.debugDescription, forStatusCode: statusCode!) - completionHandler(.failure(networkError)) - } - - }.resume() - } } From 6f6b9253122ad43df78f8340b2cb1c8dd122f7a4 Mon Sep 17 00:00:00 2001 From: Codecat15 <44252456+codecat15@users.noreply.github.com> Date: Mon, 25 Dec 2023 18:21:09 +0530 Subject: [PATCH 2/3] Updating xcode settings and deployment target - updating the xocde setting to recommended - updating the iOS deployment target from 12 to 13 --- HttpUtility.xcodeproj/project.pbxproj | 19 +++++++++++++++---- .../xcschemes/HttpUtility.xcscheme | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/HttpUtility.xcodeproj/project.pbxproj b/HttpUtility.xcodeproj/project.pbxproj index 3ce358e..6ae2e59 100644 --- a/HttpUtility.xcodeproj/project.pbxproj +++ b/HttpUtility.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -204,8 +204,9 @@ 86719E8D24720BD1002A2AB0 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1140; - LastUpgradeCheck = 1220; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = CodeCat15; TargetAttributes = { 86719E9524720BD1002A2AB0 = { @@ -328,6 +329,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -342,7 +344,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -392,6 +394,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -400,7 +403,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -416,11 +419,13 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = HttpUtility/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -428,6 +433,8 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.codecat15.HttpUtility; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; @@ -442,11 +449,13 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = HttpUtility/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = ( @@ -454,6 +463,8 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.codecat15.HttpUtility; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SKIP_INSTALL = YES; diff --git a/HttpUtility.xcodeproj/xcshareddata/xcschemes/HttpUtility.xcscheme b/HttpUtility.xcodeproj/xcshareddata/xcschemes/HttpUtility.xcscheme index 8381b68..f8c73e1 100644 --- a/HttpUtility.xcodeproj/xcshareddata/xcschemes/HttpUtility.xcscheme +++ b/HttpUtility.xcodeproj/xcshareddata/xcschemes/HttpUtility.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 27 Dec 2023 09:24:40 +0530 Subject: [PATCH 3/3] Writing code comments Including code comments for the code done so far --- .../Extensions/EncodableExtension.swift | 26 +++- HttpUtility/HUHttpMethods.swift | 19 ++- HttpUtility/HUNetworkError.swift | 40 +++++- HttpUtility/HURequest.swift | 53 +++++++- HttpUtility/HttpRequestHandler.swift | 76 +++++++---- HttpUtility/HttpUtility.swift | 127 ++++++++++++------ 6 files changed, 255 insertions(+), 86 deletions(-) diff --git a/HttpUtility/Extensions/EncodableExtension.swift b/HttpUtility/Extensions/EncodableExtension.swift index adfc524..bc1ac84 100644 --- a/HttpUtility/Extensions/EncodableExtension.swift +++ b/HttpUtility/Extensions/EncodableExtension.swift @@ -10,13 +10,20 @@ import Foundation extension Encodable { + /// Converts the encodable object to a URL with query string parameters. + /// + /// - Parameters: + /// - urlString: The base URL string to which query string parameters will be appended. + /// - Returns: A URL with appended query string parameters, or `nil` if conversion fails. + /// + /// This method converts the encodable object to a dictionary and appends the dictionary's key-value pairs as query string parameters to the provided URL string. func convertToQueryStringUrl(urlString: String) -> URL? { var components = URLComponents(string: urlString) if(components != nil) { let requestDictionary = convertToDictionary() - + if(requestDictionary != nil) { var queryItems: [URLQueryItem] = [] @@ -28,29 +35,34 @@ extension Encodable } } }) - + components?.queryItems = queryItems return components?.url! } } - + debugPrint("convertToQueryStringUrl => Error => Conversion failed, please make sure to pass a valid urlString and try again") - + return nil } - func convertToDictionary() -> [String: Any?]? + /// Converts the encodable object to a dictionary. + /// + /// - Returns: A dictionary representation of the encodable object, or `nil` if conversion fails. + /// + /// This method uses JSON encoding to convert the encodable object to a JSON data representation and then deserializes it into a dictionary. + func convertToDictionary() -> [String: Any?]? { do { let encoder = try JSONEncoder().encode(self) let result = (try? JSONSerialization.jsonObject(with: encoder, options: .allowFragments)).flatMap{$0 as? [String: Any?]} return result - + } catch let error { debugPrint(error) } - + return nil } } diff --git a/HttpUtility/HUHttpMethods.swift b/HttpUtility/HUHttpMethods.swift index 67c297f..b38686c 100644 --- a/HttpUtility/HUHttpMethods.swift +++ b/HttpUtility/HUHttpMethods.swift @@ -8,10 +8,25 @@ import Foundation -public enum HUHttpMethods : String -{ +// MARK: - HUHttpMethods Enum + +/// `HUHttpMethods` is an enumeration representing various HTTP methods. +/// +/// For more information on HTTP methods, refer to [MDN Web Docs - HTTP Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods). +public enum HUHttpMethods: String { + + // MARK: Cases + + /// HTTP GET method. case get = "GET" + + /// HTTP POST method. case post = "POST" + + /// HTTP PUT method. case put = "PUT" + + /// HTTP DELETE method. case delete = "DELETE" } + diff --git a/HttpUtility/HUNetworkError.swift b/HttpUtility/HUNetworkError.swift index 614e082..f621aca 100644 --- a/HttpUtility/HUNetworkError.swift +++ b/HttpUtility/HUNetworkError.swift @@ -8,17 +8,47 @@ import Foundation -public struct HUNetworkError : Error -{ +// MARK: - HUNetworkError Struct + +/// `HUNetworkError` is a struct representing an error that can occur during a network request. +public struct HUNetworkError: Error { + + // MARK: Properties + + /// A human-readable reason for the error. let reason: String? + + /// The HTTP status code associated with the error. let httpStatusCode: Int? + + /// The URL for which the network error occurred. let requestUrl: URL? + + /// The body of the HTTP request that resulted in the network error. let requestBody: String? + + /// The server response data in string format, if available. let serverResponse: String? - - init(withServerResponse response: Data? = nil, forRequestUrl url: URL, withHttpBody body: Data? = nil, errorMessage message: String, forStatusCode statusCode: Int) - { + + // MARK: Initialization + + /// Initializes an instance of `HUNetworkError` with the provided parameters. + /// - Parameters: + /// - response: The server response data in raw Data format. + /// - url: The URL for which the network error occurred. + /// - body: The body of the HTTP request that resulted in the network error. + /// - message: A human-readable error message. + /// - statusCode: The HTTP status code associated with the error. + init(withServerResponse response: Data? = nil, + forRequestUrl url: URL, + withHttpBody body: Data? = nil, + errorMessage message: String, + forStatusCode statusCode: Int) { + + // Convert server response data to a string if available self.serverResponse = response != nil ? String(data: response!, encoding: .utf8) : nil + + // Set other properties with provided values self.requestUrl = url self.requestBody = body != nil ? String(data: body!, encoding: .utf8) : nil self.httpStatusCode = statusCode diff --git a/HttpUtility/HURequest.swift b/HttpUtility/HURequest.swift index a1b667c..c6fa8a3 100644 --- a/HttpUtility/HURequest.swift +++ b/HttpUtility/HURequest.swift @@ -8,16 +8,41 @@ import Foundation +// MARK: - Request Protocol + +/// `Request` protocol defines the basic structure for an HTTP request. protocol Request { + + /// The URL for the request. var url: URL { get set } + + /// The HTTP method for the request. var method: HUHttpMethods { get set } } -public struct HURequest : Request { +// MARK: - HURequest Struct + +/// `HURequest` is a struct representing a basic HTTP request conforming to the `Request` protocol. +public struct HURequest: Request { + + // MARK: Properties + + /// The URL for the request. var url: URL + + /// The HTTP method for the request. var method: HUHttpMethods + + /// The body of the HTTP request. var requestBody: Data? = nil - + + // MARK: Initialization + + /// Initializes an instance of `HURequest` with the provided parameters. + /// - Parameters: + /// - url: The URL for the request. + /// - method: The HTTP method for the request. + /// - requestBody: The body of the HTTP request. public init(withUrl url: URL, forHttpMethod method: HUHttpMethods, requestBody: Data? = nil) { self.url = url self.method = method @@ -25,15 +50,33 @@ public struct HURequest : Request { } } -public struct HUMultiPartRequest : Request { +// MARK: - HUMultiPartRequest Struct +/// `HUMultiPartRequest` is a struct representing an HTTP request with multipart form data conforming to the `Request` protocol. +public struct HUMultiPartRequest: Request { + + // MARK: Properties + + /// The URL for the request. var url: URL + + /// The HTTP method for the request. var method: HUHttpMethods - var request : Encodable - + + /// The body of the HTTP request, represented by an `Encodable` type. + var request: Encodable + + // MARK: Initialization + + /// Initializes an instance of `HUMultiPartRequest` with the provided parameters. + /// - Parameters: + /// - url: The URL for the request. + /// - method: The HTTP method for the request. + /// - requestBody: The body of the HTTP request, represented by an `Encodable` type. public init(withUrl url: URL, forHttpMethod method: HUHttpMethods, requestBody: Encodable) { self.url = url self.method = method self.request = requestBody } } + diff --git a/HttpUtility/HttpRequestHandler.swift b/HttpUtility/HttpRequestHandler.swift index fff3244..af4e49e 100644 --- a/HttpUtility/HttpRequestHandler.swift +++ b/HttpUtility/HttpRequestHandler.swift @@ -8,73 +8,97 @@ import Foundation +// MARK: - HttpRequestHandler Class + +/// `HttpRequestHandler` is a class responsible for handling HTTP requests. final public class HttpRequestHandler { + // MARK: Properties + + /// The authentication token to be included in the HTTP requests, if applicable. private let authenticationToken: String? + + /// Custom JSON decoder to be used for decoding JSON responses. If not provided, the default JSONDecoder is used. private let customJsonDecoder: JSONDecoder? + // MARK: Initialization + + /// Initializes an instance of `HttpRequestHandler`. + /// - Parameters: + /// - authenticationToken: The authentication token to be included in the HTTP requests. + /// - customJsonDecoder: Custom JSON decoder to be used for decoding JSON responses. init(authenticationToken: String?, customJsonDecoder: JSONDecoder?) { self.authenticationToken = authenticationToken self.customJsonDecoder = customJsonDecoder } // MARK: - Perform Operation + + /// Performs an HTTP request operation. + /// - Parameters: + /// - requestUrl: The URL request to be executed. + /// - responseType: The type of the expected response (must conform to `Decodable`). + /// - completionHandler: A closure to be called upon completion, providing the result as a `Result` type. func performOperation(requestUrl: URLRequest, responseType: T.Type, - completionHandler:@escaping(Result) -> Void) - { + completionHandler: @escaping (Result) -> Void) { + URLSession.shared.dataTask(with: requestUrl) { (data, httpUrlResponse, error) in + // Extracting status code from HTTP response let statusCode = (httpUrlResponse as? HTTPURLResponse)?.statusCode - if(error == nil && data != nil && data?.isEmpty == false) { + if (error == nil && data != nil && data?.isEmpty == false) { let response = self.decodeJsonResponse(data: data!, responseType: responseType) - // return success - if(response != nil) { + // Return success + if (response != nil) { completionHandler(.success(response)) - }else { - - // return failure - completionHandler(.failure(HUNetworkError(withServerResponse: data, + } else { + // Return failure + completionHandler(.failure(HUNetworkError(withServerResponse: data, forRequestUrl: requestUrl.url!, withHttpBody: requestUrl.httpBody, errorMessage: error.debugDescription, forStatusCode: statusCode!))) } - } - else { - let networkError = HUNetworkError(withServerResponse: data, + } else { + // Handle failure with network error + let serverError = HUNetworkError(withServerResponse: data, forRequestUrl: requestUrl.url!, withHttpBody: requestUrl.httpBody, - errorMessage:error.debugDescription, + errorMessage: error.debugDescription, forStatusCode: statusCode!) - - // return failure - completionHandler(.failure(networkError)) + // Return failure + completionHandler(.failure(serverError)) } }.resume() } - // MARK: Private method - private func createJsonDecoder() -> JSONDecoder - { - let decoder = customJsonDecoder != nil ? customJsonDecoder! : JSONDecoder() - if(customJsonDecoder == nil) { + // MARK: - Private Methods + + /// Creates and returns a JSON decoder based on the availability of a custom decoder. + /// If a custom decoder is not provided, the default JSONDecoder is used with ISO8601 date decoding strategy. + private func createJsonDecoder() -> JSONDecoder { + let decoder = customJsonDecoder != nil ? customJsonDecoder! : JSONDecoder() + if (customJsonDecoder == nil) { decoder.dateDecodingStrategy = .iso8601 } return decoder } - private func decodeJsonResponse(data: Data, responseType: T.Type) -> T? - { + /// Decodes the JSON response data using the provided decoder. + /// - Parameters: + /// - data: The JSON data to be decoded. + /// - responseType: The type of the expected response (must conform to `Decodable`). + /// - Returns: The decoded response object or `nil` if decoding fails. + private func decodeJsonResponse(data: Data, responseType: T.Type) -> T? { let decoder = createJsonDecoder() do { return try decoder.decode(responseType, from: data) - } - catch let error { - debugPrint("error while decoding JSON response =>\(error.localizedDescription)") + } catch let error { + debugPrint("Error while decoding JSON response =>\(error.localizedDescription)") } return nil } diff --git a/HttpUtility/HttpUtility.swift b/HttpUtility/HttpUtility.swift index 112eb95..1392974 100644 --- a/HttpUtility/HttpUtility.swift +++ b/HttpUtility/HttpUtility.swift @@ -8,51 +8,76 @@ import Foundation -final public class HttpUtility -{ +// MARK: - HttpUtility Class + +/// `HttpUtility` is a singleton class responsible for handling HTTP requests. +final public class HttpUtility { + + // MARK: Properties + + /// Shared instance of `HttpUtility` for singleton pattern. public static let shared = HttpUtility() - public var authenticationToken : String? = nil - public var customJsonDecoder : JSONDecoder? = nil + /// Authentication token to be included in HTTP requests, if applicable. + public var authenticationToken: String? = nil + + /// Custom JSON decoder to be used for decoding JSON responses. If not provided, the default JSONDecoder is used. + public var customJsonDecoder: JSONDecoder? = nil + + /// Lazy initialization of `HttpRequestHandler` with authentication token and custom JSON decoder. private lazy var httpRequestHandler: HttpRequestHandler = { - return HttpRequestHandler(authenticationToken: authenticationToken, + return HttpRequestHandler(authenticationToken: authenticationToken, customJsonDecoder: customJsonDecoder) }() - private init(){} + // MARK: Initialization + + /// Private initializer to enforce singleton pattern. + private init() {} + + // MARK: - Request Methods - public func request(huRequest: HURequest, resultType: T.Type, completionHandler:@escaping(Result)-> Void) - { - switch huRequest.method - { + /// Handles various HTTP request methods and calls corresponding functions. + /// - Parameters: + /// - huRequest: The HTTP request object containing details like URL, method, and request body. + /// - resultType: The type of the expected response (must conform to `Decodable`). + /// - completionHandler: A closure to be called upon completion, providing the result as a `Result` type. + public func request(huRequest: HURequest, + resultType: T.Type, + completionHandler: @escaping (Result) -> Void) { + + switch huRequest.method { case .get: - getData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0)} - break - + getData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0) } case .post: - postData(request: huRequest, resultType: resultType) { completionHandler($0)} - break - + postData(request: huRequest, resultType: resultType) { completionHandler($0) } case .put: - putData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0)} - break - + putData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0) } case .delete: - deleteData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0)} - break + deleteData(requestUrl: huRequest.url, resultType: resultType) { completionHandler($0) } } } - // MARK: - Multipart - public func requestWithMultiPartFormData(multiPartRequest: HUMultiPartRequest, responseType: T.Type, completionHandler:@escaping(Result)-> Void) { + // MARK: - Multipart Form Data Request + + /// Performs an HTTP request with multipart form data. + /// - Parameters: + /// - multiPartRequest: The multipart form data request object. + /// - responseType: The type of the expected response (must conform to `Decodable`). + /// - completionHandler: A closure to be called upon completion, providing the result as a `Result` type. + public func requestWithMultiPartFormData(multiPartRequest: HUMultiPartRequest, + responseType: T.Type, + completionHandler: @escaping (Result) -> Void) { + postMultiPartFormData(request: multiPartRequest) { completionHandler($0) } } - // MARK: - Private functions - private func createUrlRequest(requestUrl: URL) -> URLRequest - { - var urlRequest = URLRequest(url: requestUrl) + // MARK: - Private Functions + + /// Creates and returns a URL request with proper headers, including the authentication token if available. + private func createUrlRequest(requestUrl: URL) -> URLRequest { + var urlRequest = URLRequest(url: requestUrl) if let authToken = authenticationToken { urlRequest.setValue(authToken, forHTTPHeaderField: "authorization") } @@ -60,9 +85,13 @@ final public class HttpUtility return urlRequest } - // MARK: - GET Api - private func getData(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result)-> Void) - { + // MARK: - GET API + + /// Performs an HTTP GET request. + private func getData(requestUrl: URL, + resultType: T.Type, + completionHandler: @escaping (Result) -> Void) { + var urlRequest = self.createUrlRequest(requestUrl: requestUrl) urlRequest.httpMethod = HUHttpMethods.get.rawValue @@ -71,9 +100,13 @@ final public class HttpUtility } } - // MARK: - POST Api - private func postData(request: HURequest, resultType: T.Type, completionHandler:@escaping(Result)-> Void) - { + // MARK: - POST API + + /// Performs an HTTP POST request. + private func postData(request: HURequest, + resultType: T.Type, + completionHandler: @escaping (Result) -> Void) { + var urlRequest = self.createUrlRequest(requestUrl: request.url) urlRequest.httpMethod = HUHttpMethods.post.rawValue urlRequest.httpBody = request.requestBody @@ -84,8 +117,12 @@ final public class HttpUtility } } - private func postMultiPartFormData(request: HUMultiPartRequest, completionHandler:@escaping(Result)-> Void) - { + // MARK: - POST Multipart Form Data + + /// Performs an HTTP POST request with multipart form data. + private func postMultiPartFormData(request: HUMultiPartRequest, + completionHandler: @escaping (Result) -> Void) { + let boundary = "-----------------------------\(UUID().uuidString)" let lineBreak = "\r\n" var urlRequest = self.createUrlRequest(requestUrl: request.url) @@ -112,9 +149,13 @@ final public class HttpUtility } } - // MARK: - PUT Api - private func putData(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result)-> Void) - { + // MARK: - PUT API + + /// Performs an HTTP PUT request. + private func putData(requestUrl: URL, + resultType: T.Type, + completionHandler: @escaping (Result) -> Void) { + var urlRequest = self.createUrlRequest(requestUrl: requestUrl) urlRequest.httpMethod = HUHttpMethods.put.rawValue @@ -123,9 +164,13 @@ final public class HttpUtility } } - // MARK: - DELETE Api - private func deleteData(requestUrl: URL, resultType: T.Type, completionHandler:@escaping(Result)-> Void) - { + // MARK: - DELETE API + + /// Performs an HTTP DELETE request. + private func deleteData(requestUrl: URL, + resultType: T.Type, + completionHandler: @escaping (Result) -> Void) { + var urlRequest = self.createUrlRequest(requestUrl: requestUrl) urlRequest.httpMethod = HUHttpMethods.delete.rawValue