Created
June 18, 2020 21:33
-
-
Save mayoralito/ada0d7a557911665fce957f1eda1553e to your computer and use it in GitHub Desktop.
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
// File: APIService.swift | |
import Foundation | |
import Combine | |
enum APIServiceError: Error { | |
case responseError | |
case parseError(Error) | |
} | |
protocol APIRequestType { | |
associatedtype Response: Decodable | |
var path: String { get } | |
var queryItems: [URLQueryItem]? { get } | |
var headers: [String: Any]? { get } | |
var mockFilename: String? { get } | |
} | |
extension APIRequestType { | |
var mockFilename: String? { | |
return nil | |
} | |
} | |
protocol APIServiceType { | |
func response<Request>(from request: Request) -> AnyPublisher<Request.Response, APIServiceError> where Request: APIRequestType | |
} | |
// ------------------------------------------------ | |
// API | |
struct APIService: APIServiceType { | |
private let baseURL: URL | |
init(baseURL: URL = URL(string: ApplicationSettings.Environment.baseURL())!) { | |
self.baseURL = baseURL | |
} | |
func response<Request>(from request: Request) -> AnyPublisher<Request.Response, APIServiceError> where Request : APIRequestType { | |
let pathURL = URL(string: request.path, relativeTo: baseURL)! | |
var urlComponents = URLComponents(url: pathURL, resolvingAgainstBaseURL: true)! | |
urlComponents.queryItems = request.queryItems | |
let _headers = request.headers! | |
var request = URLRequest(url: urlComponents.url!) | |
request.addValue("application/json", forHTTPHeaderField: "Content-Type") | |
request.addValue("no-cache", forHTTPHeaderField: "Cache-Control") | |
request.addValue("*/*", forHTTPHeaderField: "Accept") | |
request.addValue("gzip, deflate", forHTTPHeaderField: "Accept-Encoding") | |
request.addValue("keep-alive", forHTTPHeaderField: "Connection") | |
request.addValue("iOS-\(UUID().uuidString)", forHTTPHeaderField: "reqId") | |
if let sessionData = UserPreferencesParser.shared.get(preference: .sessionData) as? String, !sessionData.isEmpty { | |
request.addValue("bearer \(sessionData)", forHTTPHeaderField: "authorization") | |
} | |
_ = _headers.map { | |
request.addValue(String(describing: $0.value), forHTTPHeaderField: $0.key) | |
} | |
let decoder = JSONDecoder() | |
decoder.keyDecodingStrategy = .convertFromSnakeCase | |
return URLSession.shared.dataTaskPublisher(for: request) | |
.map { data, urlResponse in | |
return data | |
} | |
.mapError { _ in APIServiceError.responseError } | |
.decode(type: Request.Response.self, decoder: decoder) | |
.mapError(APIServiceError.parseError) | |
.receive(on: RunLoop.main) | |
.eraseToAnyPublisher() | |
} | |
} | |
// File: APIServiceMock.swift | |
import Foundation | |
import Combine | |
struct APIServiceMock: APIServiceType { | |
private let baseURL: URL | |
init(baseURL: URL = URL(string: ApplicationSettings.Environment.baseURL())!) { | |
self.baseURL = baseURL | |
} | |
func response<Request>(from request: Request) -> AnyPublisher<Request.Response, APIServiceError> where Request : APIRequestType { | |
let filename = request.mockFilename | |
let decoder = JSONDecoder() | |
decoder.keyDecodingStrategy = .convertFromSnakeCase | |
guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { | |
fatalError("Couldn't find \(String(describing: filename)) in main bundle.") | |
} | |
return URLSession.shared.dataTaskPublisher(for: URLRequest(url: file)) | |
.map { data, urlResponse in | |
return data | |
} | |
.mapError { _ in APIServiceError.responseError } | |
.decode(type: Request.Response.self, decoder: decoder) | |
.mapError(APIServiceError.parseError) | |
.receive(on: RunLoop.main) | |
.eraseToAnyPublisher() | |
} | |
} | |
func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T { | |
let data: Data | |
guard let file = Bundle.main.url(forResource: filename, withExtension: nil) else { | |
fatalError("Couldn't find \(filename) in main bundle.") | |
} | |
do { | |
data = try Data(contentsOf: file) | |
} catch { | |
fatalError("Couldn't load \(filename) from main bundle:\n\(error)") | |
} | |
do { | |
let decoder = JSONDecoder() | |
return try decoder.decode(T.self, from: data) | |
} catch { | |
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)") | |
} | |
} | |
// File: ProfileRequest.swift | |
import Foundation | |
struct ProfileRequest: APIRequestType { | |
typealias Response = ProfileResponse | |
var path: String { return "/path/to/profile" } | |
var queryItems: [URLQueryItem]? { | |
return [] | |
} | |
var headers: [String : Any]? { | |
return [ | |
"key": "value" | |
] | |
} | |
var mockFilename: String? { | |
return "some-file.json" // if this attribute return content then the class would fetch the info from the local JSON instead of API. | |
} | |
} | |
// File: ProfileViewModel.swift | |
final class ProfileViewModel { | |
private let apiService: APIServiceType | |
init(apiService: APIServiceType = APIService()) { | |
self.apiService = apiService | |
// other configs | |
} | |
} | |
// File: AppDelegete.swift | |
func someMethod() { | |
let mockData = false | |
var profileView: ProfileView? | |
if !mockData { | |
// Load the View passing API default configs | |
profileView = ProfileView(viewModel: .init()) | |
} else { | |
// Load the View passing MockAPI Settings | |
profileView = ProfileView(viewModel: .init(apiService: APIServiceMock())) | |
} | |
// add to your hostviewcontroller | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment