Last active
December 4, 2018 14:02
-
-
Save VAndrJ/7664f82d5847551541d5e1e53a57ca57 to your computer and use it in GitHub Desktop.
Piece of network module written for Medinternet project.
This file contains 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
// | |
// NetworkEndpoint.swift | |
// Medinternet | |
// | |
// Created by VAndrJ on 6/10/18. | |
// Copyright © 2018 VAndrJ. All rights reserved. | |
// | |
import Foundation | |
enum NetworkEndpoint: String { | |
#if RELEASE | |
let domainEndpoint = "https://releaseu_rl/" | |
#else | |
let domainEndpoint = "https://development_url/" | |
#endif | |
case someEndpoint = "some_api_endpoint" | |
case someOtherEndpoint = "some_other_api_endpoint" | |
var path: URL { | |
// MARK: - must instantly crash to detect problem with url path | |
return URL(string: "\(domainEndpoint)\(self.rawValue)")! | |
} | |
var type: RequestType { | |
switch self { | |
case .someEndpoint: | |
return .get | |
case .someOtherEndpoint: | |
return .post | |
} | |
} | |
} |
This file contains 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
// | |
// NetworkError.swift | |
// Medinternet | |
// | |
// Created by VAndrJ on 6/10/18. | |
// Copyright © 2018 VAndrJ. All rights reserved. | |
// | |
import Foundation | |
enum NetworkError: Error { | |
case internalServerError | |
case requestError(global: Error) | |
case responseParsingError | |
case responseEmptyData | |
case responseError(code: Int) | |
case describedError(message: String) | |
var localizedDescription: String { | |
switch self { | |
case .internalServerError: | |
return "Внутрiшня помилка сервера. Спробуйте пiзнiше." | |
case .requestError(let globalError): | |
return globalError.localizedDescription | |
case .responseParsingError: | |
return "Помилка даних вiдповiдi сервера. Зверніться в підтримку." | |
case .responseEmptyData: | |
return "Порожні дані відповіді сервера." | |
case .responseError(let code): | |
return "Помилка вiдповiдi сервера #\(code). Зверніться в підтримку." | |
case .describedError(let message): | |
return message | |
} | |
} | |
} |
This file contains 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
// | |
// Networking.swift | |
// Medinternet | |
// | |
// Created by VAndrJ on 6/10/18. | |
// Copyright © 2018 VAndrJ. All rights reserved. | |
// | |
import Foundation | |
import Bond | |
import ReactiveKit | |
final class Networking: NSObject { | |
let networkService: NetworkService | |
let logger: NetworkLogProtocol & ParsingLogProtocol | |
init(logger: NetworkLogProtocol & ParsingLogProtocol) { | |
self.logger = logger | |
self.networkService = NetworkService(logger: logger) | |
} | |
func getSomeInfo(token: String, | |
completion: @escaping (SomeResponseEntity) -> Void, | |
errorCompletion: @escaping (NetworkError) -> Void, | |
always: (() -> Void)?) { | |
let request = TokenEntity(date: Date(), token: token) | |
let resource = Resource<SomeResponseEntity, TokenEntity>(endpoint: .someEndpoint, body: request, logger: logger) | |
coreRequest( | |
resourse: resource, | |
completion: completion, | |
errorCompletion: errorCompletion, | |
always: always | |
) | |
} | |
//...other methods | |
private func coreRequest<Response: Codable, Request: Codable>( | |
resourse: Resource<Response, Request>, | |
completion: @escaping (Response) -> Void, | |
errorCompletion: @escaping (NetworkError) -> Void, | |
always: (() -> Void)?) { | |
networkService | |
.load(resourse) | |
.observeOn(.main) | |
.observe(with: { (event) in | |
switch event { | |
case .next(let response): | |
always?() | |
completion(response) | |
case .failed(let error): | |
always?() | |
errorCompletion(error) | |
case .completed: | |
break | |
} | |
}) | |
.dispose(in: bag) | |
} | |
} |
This file contains 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
// | |
// NetworkLog.swift | |
// Medinternet | |
// | |
// Created by VAndrJ on 6/10/18. | |
// Copyright © 2018 VAndrJ. All rights reserved. | |
// | |
import Foundation | |
protocol NetworkLogProtocol { | |
func logResponse(request: URLRequest, data: Data?, response: URLResponse?, error: Error?) | |
} | |
protocol ParsingLogProtocol { | |
func logParsingError(_ error: Error) | |
} | |
// MARK: - It's very simple, so use one class | |
final class NetworkLog: NetworkLogProtocol, ParsingLogProtocol { | |
let dateFormatter: DateFormatter | |
init(dateFormatter: DateFormatter) { | |
self.dateFormatter = dateFormatter | |
} | |
func logParsingError(_ error: Error) { | |
#if DEBUG | |
let errorString = """ | |
- - - Parsing error start - - - | |
Time: \(dateFormatter.string(from: Date())) | |
\(error.localizedDescription) | |
\(error) | |
- - - Parsing error end - - - | |
""" | |
print(errorString) | |
#endif | |
} | |
func logResponse(request: URLRequest, data: Data?, response: URLResponse?, error: Error?) { | |
#if DEBUG | |
let requestDataString = String(data: request.httpBody ?? Data(), encoding: .utf8) ?? "" | |
let requestString = """ | |
- - - Request start - - - | |
Time: \(dateFormatter.string(from: Date())) | |
URL: \(request.url?.absoluteString ?? "error url") | |
Header: \(request.allHTTPHeaderFields ?? [:]) | |
Method: \(request.httpMethod ?? "") | |
Body: \(requestDataString) | |
- - - Request end - - - | |
""" | |
let responseDataString = String(data: data ?? Data(), encoding: .utf8) ?? "" | |
let responseString = """ | |
- - - Response start - - - | |
Code: \((response as? HTTPURLResponse)?.statusCode ?? -1) | |
Error: \(error?.localizedDescription ?? "") | |
Body: \(responseDataString) | |
- - - Response end - - - | |
""" | |
print(requestString) | |
print(responseString) | |
#endif | |
} | |
} |
This file contains 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
// | |
// NetworkService.swift | |
// Medinternet | |
// | |
// Created by VAndrJ on 6/10/18. | |
// Copyright © 2018 VAndrJ. All rights reserved. | |
// | |
import UIKit | |
import Bond | |
import ReactiveKit | |
protocol NetworkServiceProtocol { | |
func load<A, B>(_ resource: Resource<A, B>) -> Signal<A, NetworkError> | |
} | |
final class NetworkService: NetworkServiceProtocol { | |
private let session = URLSession(configuration: .ephemeral) | |
private let logger: NetworkLogProtocol? | |
init(logger: NetworkLogProtocol? = nil) { | |
self.logger = logger | |
} | |
func load<A, B>(_ resource: Resource<A, B>) -> Signal<A, NetworkError> { | |
return Signal { observer in | |
var urlRequest = URLRequest(url: resource.endpoint.path, cachePolicy: .reloadIgnoringCacheData, timeoutInterval: Configuration.requestTimeout) | |
urlRequest.httpMethod = resource.endpoint.type.rawValue | |
switch resource.endpoint.type { | |
case .get: | |
break | |
case .post: | |
urlRequest.allHTTPHeaderFields = resource.getHeaders() | |
if let body = resource.body { | |
urlRequest.httpBody = body.getFormEncoded()?.data(using: .utf8) | |
} | |
} | |
let task = self.session.dataTask(with: urlRequest) { [weak self] (data, response, error) in | |
guard let `self` = self else { return } | |
self.logger?.logResponse(request: urlRequest, data: data, response: response, error: error) | |
guard error == nil else { | |
observer.failed(.requestError(global: error!)) | |
return | |
} | |
guard self.validateResponse(response) else { | |
observer.failed(.responseError(code: self.checkResponseCode(response))) | |
return | |
} | |
guard let data = data else { | |
observer.failed(.responseEmptyData) | |
return | |
} | |
guard let parsedResponse = resource.parse(data) else { | |
// MARK: - For error response with code 200 ¯\_(ツ)_/¯ | |
guard let errorResponse = resource.parseError(data) else { | |
observer.failed(.responseParsingError) | |
return | |
} | |
observer.failed(NetworkError.describedError(message: errorResponse.formError?.all ?? errorResponse.error)) | |
return | |
} | |
observer.next(parsedResponse) | |
observer.completed() | |
} | |
task.resume() | |
return BlockDisposable { | |
task.cancel() | |
} | |
} | |
} | |
private func validateResponse(_ response: URLResponse?) -> Bool { | |
guard let response = response as? HTTPURLResponse, 200...299 ~= response.statusCode else { | |
return false | |
} | |
return true | |
} | |
private func checkResponseCode(_ response: URLResponse?) -> Int { | |
// MARK: - For peace of mind | |
guard let response = response as? HTTPURLResponse else { | |
return -1 | |
} | |
return response.statusCode | |
} | |
} | |
This file contains 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
// | |
// RequestType.swift | |
// Medinternet | |
// | |
// Created by VAndrJ on 6/10/18. | |
// Copyright © 2018 VAndrJ. All rights reserved. | |
// | |
import Foundation | |
enum RequestType: String { | |
case post = "POST" | |
case get = "GET" | |
} |
This file contains 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
// | |
// Resource.swift | |
// Medinternet | |
// | |
// Created by VAndrJ on 6/10/18. | |
// Copyright © 2018 VAndrJ. All rights reserved. | |
// | |
import Foundation | |
struct Resource<A: Codable, B: Codable & FormEncoded> { | |
private let logger: ParsingLogProtocol? | |
private let defaultHeaders: [String: String] = ["Content-Type": "application/x-www-form-urlencoded"] | |
private let additionalHeaders: [String: String]? | |
let endpoint: NetworkEndpoint | |
let body: B? | |
init(endpoint: NetworkEndpoint, body: B?, additionalHeaders: [String: String]? = nil, logger: ParsingLogProtocol? = nil) { | |
self.endpoint = endpoint | |
self.body = body | |
self.additionalHeaders = additionalHeaders | |
self.logger = logger | |
} | |
func parse(_ data: Data) -> A? { | |
do { | |
return try JSONDecoder().decode(A.self, from: data) | |
} catch { | |
logger?.logParsingError(error) | |
return nil | |
} | |
} | |
func parseError(_ data: Data) -> ErrorEntity? { | |
do { | |
return try JSONDecoder().decode(ErrorEntity.self, from: data) | |
} catch { | |
logger?.logParsingError(error) | |
return nil | |
} | |
} | |
func getHeaders() -> [String: String]? { | |
switch endpoint.type { | |
case .get: | |
return nil | |
case .post: | |
return defaultHeaders.merging(additionalHeaders ?? [:], uniquingKeysWith: { $1 }) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
nice work