diff --git a/HttpUtility.xcodeproj/project.pbxproj b/HttpUtility.xcodeproj/project.pbxproj index c0f83f8..6ae2e59 100644 --- a/HttpUtility.xcodeproj/project.pbxproj +++ b/HttpUtility.xcodeproj/project.pbxproj @@ -3,10 +3,11 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; 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 = ""; @@ -201,8 +204,9 @@ 86719E8D24720BD1002A2AB0 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1140; - LastUpgradeCheck = 1220; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = CodeCat15; TargetAttributes = { 86719E9524720BD1002A2AB0 = { @@ -258,6 +262,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 */, @@ -324,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; @@ -338,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; @@ -388,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; @@ -396,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; @@ -412,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 = ( @@ -424,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; @@ -438,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 = ( @@ -450,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 @@ 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 new file mode 100644 index 0000000..af4e49e --- /dev/null +++ b/HttpUtility/HttpRequestHandler.swift @@ -0,0 +1,105 @@ +// +// HttpRequestHandler.swift +// HttpUtility +// +// Created by Rocket Racoon on 12/25/23. +// Copyright © 2023 CodeCat15. All rights reserved. +// + +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) { + + 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) { + 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 { + // Handle failure with network error + let serverError = HUNetworkError(withServerResponse: data, + forRequestUrl: requestUrl.url!, + withHttpBody: requestUrl.httpBody, + errorMessage: error.debugDescription, + forStatusCode: statusCode!) + // Return failure + completionHandler(.failure(serverError)) + } + + }.resume() + } + + // 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 + } + + /// 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)") + } + return nil + } +} diff --git a/HttpUtility/HttpUtility.swift b/HttpUtility/HttpUtility.swift index 803ef8e..1392974 100644 --- a/HttpUtility/HttpUtility.swift +++ b/HttpUtility/HttpUtility.swift @@ -8,183 +8,174 @@ import Foundation -public class HttpUtility -{ - public static let shared = HttpUtility() - public var authenticationToken : String? = nil - public var customJsonDecoder : JSONDecoder? = nil +// MARK: - HttpUtility Class - private init(){} +/// `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() + + /// 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, + customJsonDecoder: customJsonDecoder) + }() + + // MARK: Initialization + + /// Private initializer to enforce singleton pattern. + private init() {} - public func request(huRequest: HURequest, resultType: T.Type, completionHandler:@escaping(Result)-> Void) - { - switch huRequest.method - { + // MARK: - Request Methods + + /// 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 createJsonDecoder() -> JSONDecoder - { - let decoder = customJsonDecoder != nil ? customJsonDecoder! : JSONDecoder() - if(customJsonDecoder == nil) { - decoder.dateDecodingStrategy = .iso8601 - } - return decoder - } - private func createUrlRequest(requestUrl: URL) -> URLRequest - { + // 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(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) - { + + // 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 - - 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) - { + + // 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 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) - { + + // 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) 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) } } } - // 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 - - 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) - { + + // 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 - - 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() - } }