mirror of
https://github.com/luxfi/safe-ios.git
synced 2026-10-30 15:19:20 +00:00
153 lines
4.9 KiB
Swift
153 lines
4.9 KiB
Swift
//
|
|
// Copyright © 2018 Gnosis Ltd. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// JSON model of a request.
|
|
public protocol JSONRequest: Encodable {
|
|
|
|
/// GET, POST, PUT, DELETE
|
|
var httpMethod: String { get }
|
|
/// Path to resource, without baseURL
|
|
var urlPath: String { get }
|
|
/// Query parameters
|
|
var query: String? { get }
|
|
/// URL that overrides the urlPath and query
|
|
var url: URL? { get }
|
|
|
|
/// Response associated with this JSON request
|
|
associatedtype ResponseType: Decodable
|
|
|
|
}
|
|
|
|
public extension JSONRequest {
|
|
var query: String? { return nil }
|
|
var url: URL? { return nil }
|
|
var urlPath: String { "/" }
|
|
var httpMethod: String { "GET" }
|
|
}
|
|
|
|
public extension DateFormatter {
|
|
|
|
/// Date formatter with format "yyyy-MM-dd'T'HH:mm:ss+00:00"
|
|
static let networkDateFormatter: DateFormatter = {
|
|
let formatter = DateFormatter()
|
|
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss+00:00"
|
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
formatter.timeZone = TimeZone(secondsFromGMT: 0)
|
|
return formatter
|
|
}()
|
|
|
|
}
|
|
|
|
/// Synchronous http client that sends JSON requests and receives JSON responses.
|
|
public class JSONHTTPClient {
|
|
|
|
private let logger: Logger?
|
|
private let client: HTTPClient
|
|
|
|
private lazy var jsonEncoder: JSONEncoder = {
|
|
let encoder = JSONEncoder()
|
|
let dateFormatter = DateFormatter.networkDateFormatter
|
|
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
|
return encoder
|
|
}()
|
|
|
|
public lazy var jsonDecoder: JSONDecoder = {
|
|
JSONDecoder()
|
|
}()
|
|
|
|
private struct Request: HTTPRequest {
|
|
var httpMethod: String
|
|
var urlPath: String
|
|
var query: String?
|
|
var body: Data?
|
|
var headers: [String: String]
|
|
var url: URL?
|
|
}
|
|
|
|
/// Creates new client with baseURL and logger
|
|
///
|
|
/// - Parameters:
|
|
/// - url: base url for creating all request urls
|
|
/// - logger: logger for debugging and error purposes
|
|
public init(url: URL, logger: Logger? = nil) {
|
|
client = HTTPClient(url: url, logger: logger)
|
|
self.logger = logger
|
|
}
|
|
|
|
/// Executes request and returns server response converted to ResponseType. The call is synchronous.
|
|
///
|
|
/// - Parameter request: a request to send
|
|
/// - Returns: response
|
|
/// - Throws:
|
|
/// - `HTTPClient.Error.networkRequestFailed` in case request fails
|
|
/// - JSONDecoder error in case response could not be decoded properly
|
|
/// - Network errors are rethrown (URLSession errors, for example)
|
|
@discardableResult
|
|
public func execute<T: JSONRequest>(request jsonRequest: T) throws -> T.ResponseType {
|
|
let request = try self.request(from: jsonRequest)
|
|
let data = try client.execute(request: request)
|
|
return try response(from: data)
|
|
}
|
|
|
|
public func asyncExecute<T: JSONRequest>(request jsonRequest: T, completion: @escaping (Result<T.ResponseType, Error>) -> Void) -> URLSessionTask? {
|
|
// note: vertical spacing for better readability
|
|
do {
|
|
let request = try self.request(from: jsonRequest)
|
|
|
|
let task = client.asyncExecute(request: request) { [weak self] result in
|
|
guard let `self` = self else { return }
|
|
|
|
switch result {
|
|
|
|
case .success(let data):
|
|
do {
|
|
let output: T.ResponseType = try self.response(from: data)
|
|
completion(.success(output))
|
|
} catch {
|
|
completion(.failure(error))
|
|
}
|
|
|
|
case .failure(let error):
|
|
completion(.failure(error))
|
|
|
|
}
|
|
}
|
|
return task
|
|
} catch {
|
|
completion(.failure(error))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func request<T: JSONRequest>(from request: T) throws -> Request {
|
|
let requestData = request.httpMethod != "GET" ? (try jsonEncoder.encode(request)) : nil
|
|
let requestHeaders = request.httpMethod != "GET" ? ["Content-Type": "application/json"] : [:]
|
|
let httpRequest = Request(httpMethod: request.httpMethod,
|
|
urlPath: request.urlPath,
|
|
query: request.query,
|
|
body: requestData,
|
|
headers: requestHeaders,
|
|
url: request.url)
|
|
return httpRequest
|
|
}
|
|
|
|
private func response<T: Decodable>(from data: Data) throws -> T {
|
|
var json = data
|
|
if json.isEmpty {
|
|
json = "{}".data(using: .utf8)!
|
|
}
|
|
let response: T
|
|
do {
|
|
response = try jsonDecoder.decode(T.self, from: json)
|
|
} catch {
|
|
logger?.error("Failed to decode response: \(error)")
|
|
throw error
|
|
}
|
|
return response
|
|
}
|
|
|
|
}
|