Skip to content

Instantly share code, notes, and snippets.

@alfian0
Created May 10, 2019 06:55
Show Gist options
  • Select an option

  • Save alfian0/3b75b3d25e7cabf445110c0ba558b011 to your computer and use it in GitHub Desktop.

Select an option

Save alfian0/3b75b3d25e7cabf445110c0ba558b011 to your computer and use it in GitHub Desktop.
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 }
}
public enum HTTPMethod: String {
case get = "GET"
case post = "POST"
case put = "PUT"
case patch = "PATCH"
case delete = "DELETE"
}
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])
}
public struct MultipartFormData {
enum Provider {
case text(String)
case data(Data)
}
let provider: Provider
let name: String
let filename: String
let mimeType: String
}
public enum NetworkError: String, Error {
case parameterNil = "Parameters were nil."
case encodingFailed = "Parameter encoding failed."
case missingURL = "URL is nil"
}
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)
}
}
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)
}
}
}
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
}
}
}
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