Created
May 10, 2019 06:55
-
-
Save alfian0/3b75b3d25e7cabf445110c0ba558b011 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| protocol EndPointType { | |
| var baseURL: URL { get } | |
| var path: String { get } | |
| var parameters: [String:Any]? { get } | |
| var httpMethod: HTTPMethod { get } | |
| var task: HTTPTask { get } | |
| var header: [String:String]? { get } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public enum HTTPMethod: String { | |
| case get = "GET" | |
| case post = "POST" | |
| case put = "PUT" | |
| case patch = "PATCH" | |
| case delete = "DELETE" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public enum HTTPTask { | |
| case request | |
| case requestParameters(parameters: [String: Any], encoding: ParameterEncoding) | |
| case requestCompositeParameters(bodyParameters: [String: Any], bodyEncoding: ParameterEncoding, urlParameters: [String: Any]) | |
| case uploadMultipart(multipartFormData: [MultipartFormData]) | |
| case uploadCompositeMultipart(multipartFormData: [MultipartFormData], urlParameters: [String:Any]) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public struct MultipartFormData { | |
| enum Provider { | |
| case text(String) | |
| case data(Data) | |
| } | |
| let provider: Provider | |
| let name: String | |
| let filename: String | |
| let mimeType: String | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public enum NetworkError: String, Error { | |
| case parameterNil = "Parameters were nil." | |
| case encodingFailed = "Parameter encoding failed." | |
| case missingURL = "URL is nil" | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class NetworkLogger { | |
| static func log(request: URLRequest) { | |
| print("\n - - - - - - - - - - OUTGOING - - - - - - - - - - \n") | |
| defer { print("\n - - - - - - - - - - END - - - - - - - - - - \n") } | |
| let urlAsString = request.url?.absoluteString ?? "" | |
| let urlComponents = NSURLComponents(string: urlAsString) | |
| let method = request.httpMethod != nil ? "\(request.httpMethod ?? "")" : "" | |
| let path = "\(urlComponents?.path ?? "")" | |
| let query = "\(urlComponents?.query ?? "")" | |
| let host = "\(urlComponents?.host ?? "")" | |
| var logOutput = """ | |
| \(urlAsString) \n\n | |
| \(method) \(path)?\(query) HTTP/1.1 \n | |
| HOST: \(host)\n | |
| """ | |
| for (key,value) in request.allHTTPHeaderFields ?? [:] { | |
| logOutput += "\(key): \(value) \n" | |
| } | |
| if let body = request.httpBody { | |
| logOutput += "\n \(NSString(data: body, encoding: String.Encoding.utf8.rawValue) ?? "")" | |
| } | |
| print(logOutput) | |
| } | |
| static func log(response: URLResponse) { | |
| print("\n - - - - - - - - - - INCOMMING - - - - - - - - - - \n") | |
| defer { print("\n - - - - - - - - - - END - - - - - - - - - - \n") } | |
| let urlAsString = response.url?.absoluteString ?? "" | |
| let urlComponents = NSURLComponents(string: urlAsString) | |
| let statusCode = (response as? HTTPURLResponse)?.statusCode ?? 0 | |
| let path = "\(urlComponents?.path ?? "")" | |
| let query = "\(urlComponents?.query ?? "")" | |
| let host = "\(urlComponents?.host ?? "")" | |
| let logOutput = """ | |
| \(urlAsString) \n\n | |
| \(path)?\(query) HTTP/1.1 \n | |
| HOST: \(host)\n | |
| \(statusCode)\n | |
| """ | |
| print(logOutput) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| struct NetworkManager { | |
| enum Result { | |
| case success | |
| case failure(String) | |
| } | |
| enum NetworkResponse: String { | |
| case success | |
| case authenticationError = "You net to be authenticated first" | |
| case badRequest = "Bad Request" | |
| case outDated = "The url you requested out outdated" | |
| case failed = "Network request failed" | |
| case noData = "Response returned with no data to decode" | |
| case unableToDecode = "We could not decode the response" | |
| } | |
| static let environtment: NetworkEnvironment = .production | |
| static let jasaraharjaAPIKey = "Token 264c28fc62a1b7f52b0b4364546eaca4c5429de7" | |
| static let router = Router<JasaRaharjaAPI>() | |
| static func handleNetworkResponse(_ response: HTTPURLResponse) -> Result { | |
| switch response.statusCode { | |
| case 200...299: return .success | |
| case 401...500: return .failure(NetworkResponse.authenticationError.rawValue) | |
| case 501...599: return .failure(NetworkResponse.badRequest.rawValue) | |
| case 600: return .failure(NetworkResponse.outDated.rawValue) | |
| default: return .failure(NetworkResponse.failed.rawValue) | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public protocol ParameterEncoder { | |
| static func encode(urlRequest: inout URLRequest, with parameters: [String:Any]) throws | |
| } | |
| public struct URLParameterEncoder: ParameterEncoder { | |
| public static func encode(urlRequest: inout URLRequest, with parameters: [String:Any]) throws { | |
| guard let url = urlRequest.url else { throw NetworkError.missingURL } | |
| if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty { | |
| urlComponents.queryItems = [URLQueryItem]() | |
| for (key, value) in parameters { | |
| let queryItem = URLQueryItem(name: key, value: "\(value)".addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)) | |
| urlComponents.queryItems?.append(queryItem) | |
| } | |
| urlRequest.url = urlComponents.url | |
| } | |
| if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { | |
| urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type") | |
| } | |
| } | |
| } | |
| public struct JSONParameterEncoder: ParameterEncoder { | |
| public static func encode(urlRequest: inout URLRequest, with parameters: [String:Any]) throws { | |
| do { | |
| let jsonData = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) | |
| urlRequest.httpBody = jsonData | |
| if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil { | |
| urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
| } | |
| } catch{ | |
| throw NetworkError.encodingFailed | |
| } | |
| } | |
| } | |
| public enum ParameterEncoding { | |
| case urlEncoding | |
| case jsonEncoding | |
| case urlAndJsonEncoding | |
| public func encode(urlRequest: inout URLRequest, bodyParameters: [String:Any]?, urlParameters: [String:Any]?) throws { | |
| do { | |
| switch self { | |
| case .urlEncoding: | |
| guard let urlParameters = urlParameters else { return } | |
| try URLParameterEncoder.encode(urlRequest: &urlRequest, with: urlParameters) | |
| case .jsonEncoding: | |
| guard let bodyParameters = bodyParameters else { return } | |
| try JSONParameterEncoder.encode(urlRequest: &urlRequest, with: bodyParameters) | |
| case .urlAndJsonEncoding: | |
| guard let bodyParameters = bodyParameters, | |
| let urlParameters = urlParameters else { return } | |
| try URLParameterEncoder.encode(urlRequest: &urlRequest, with: urlParameters) | |
| try JSONParameterEncoder.encode(urlRequest: &urlRequest, with: bodyParameters) | |
| } | |
| }catch { | |
| throw error | |
| } | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| public typealias NetworkRouterCompletion = (_ data: Data?,_ response: URLResponse?,_ error: Error?)->() | |
| protocol NetworkRouter { | |
| associatedtype EndPoint: EndPointType | |
| func request(_ route: EndPoint, completion: @escaping NetworkRouterCompletion) | |
| func cancel() | |
| } | |
| class Router<EndPoint: EndPointType>: NetworkRouter { | |
| private var task: URLSessionTask? | |
| func request(_ route: EndPoint, completion: @escaping NetworkRouterCompletion) { | |
| let session = URLSession.shared | |
| do { | |
| let request = try self.buildRequest(from: route) | |
| task = session.dataTask(with: request, completionHandler: { (data, response, error) in | |
| completion(data, response, error) | |
| NetworkLogger.log(response: (response as! HTTPURLResponse)) | |
| }) | |
| } catch { | |
| completion(nil, nil, error) | |
| } | |
| self.task?.resume() | |
| } | |
| func cancel() { | |
| task?.cancel() | |
| } | |
| fileprivate func buildRequest(from route: EndPoint) throws -> URLRequest { | |
| var request = URLRequest(url: route.baseURL.appendingPathComponent(route.path), | |
| cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, | |
| timeoutInterval: 10.0) | |
| request.httpMethod = route.httpMethod.rawValue | |
| do { | |
| self.addAdditionalHeaders(route.header, request: &request) | |
| switch route.task { | |
| case .request: | |
| request.setValue("application/json", forHTTPHeaderField: "Content-Type") | |
| case .requestParameters(let (parameters, encoding)): | |
| try self.configureParameters(bodyParameters: parameters, bodyEncoding: encoding, urlParameters: nil, request: &request) | |
| case .requestCompositeParameters(let (bodyParameters, bodyEncoding, urlParameters)): | |
| try self.configureParameters(bodyParameters: bodyParameters, bodyEncoding: bodyEncoding, urlParameters: urlParameters, request: &request) | |
| case .uploadMultipart(let multipartFormData): | |
| try self.configureParameters(multipartFormData: multipartFormData, urlParameters: nil, request: &request) | |
| case .uploadCompositeMultipart(let (multipartFormData, urlParameters)): | |
| try self.configureParameters(multipartFormData: multipartFormData, urlParameters: urlParameters, request: &request) | |
| } | |
| NetworkLogger.log(request: request) | |
| return request | |
| } catch { | |
| throw error | |
| } | |
| } | |
| fileprivate func configureParameters(multipartFormData: [MultipartFormData], | |
| urlParameters: [String:Any]?, | |
| request: inout URLRequest) throws { | |
| let boundary = "Boundary-\(UUID().uuidString)" | |
| var httpBody = Data() | |
| let linebreak = "\r\n" | |
| request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") | |
| do { | |
| try ParameterEncoding.urlEncoding.encode(urlRequest: &request, bodyParameters: nil, urlParameters: urlParameters) | |
| for media in multipartFormData { | |
| switch media.provider { | |
| case .data(let data): | |
| httpBody.append("--\(boundary + linebreak)") | |
| httpBody.append("Content-Disposition: form-data; name=\"\(media.name)\"; filename=\"\(media.filename)\"\(linebreak)") | |
| httpBody.append("Content-Type: \(media.mimeType + linebreak + linebreak)") | |
| httpBody.append(data) | |
| httpBody.append(linebreak) | |
| case .text(let text): | |
| httpBody.append("--\(boundary + linebreak)") | |
| httpBody.append("Content-Disposition: form-data; name=\"\(media.name)\"\(linebreak + linebreak)") | |
| httpBody.append("\(text + linebreak)") | |
| } | |
| } | |
| request.httpBody = httpBody | |
| } catch { | |
| throw error | |
| } | |
| } | |
| fileprivate func configureParameters(bodyParameters: [String:Any]?, | |
| bodyEncoding: ParameterEncoding, | |
| urlParameters: [String:Any]?, | |
| request: inout URLRequest) throws { | |
| do { | |
| try bodyEncoding.encode(urlRequest: &request, | |
| bodyParameters: bodyParameters, urlParameters: urlParameters) | |
| } catch { | |
| throw error | |
| } | |
| } | |
| fileprivate func addAdditionalHeaders(_ additionalHeaders: [String:String]?, request: inout URLRequest) { | |
| guard let headers = additionalHeaders else { return } | |
| for (key, value) in headers { | |
| request.setValue(value, forHTTPHeaderField: key) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment