161 lines
7.3 KiB
Swift
Executable File
161 lines
7.3 KiB
Swift
Executable File
import Foundation
|
|
|
|
/// Used for stubbing responses.
|
|
public enum EndpointSampleResponse {
|
|
|
|
/// The network returned a response, including status code and data.
|
|
case networkResponse(Int, Data)
|
|
|
|
/// The network returned response which can be fully customized.
|
|
case response(HTTPURLResponse, Data)
|
|
|
|
/// The network failed to send the request, or failed to retrieve a response (eg a timeout).
|
|
case networkError(NSError)
|
|
}
|
|
|
|
/// Class for reifying a target of the `Target` enum unto a concrete `Endpoint`.
|
|
/// - Note: As of Moya 11.0.0 Endpoint is no longer generic.
|
|
/// Existing code should work as is after removing the generic.
|
|
/// See #1529 and #1524 for the discussion.
|
|
open class Endpoint {
|
|
public typealias SampleResponseClosure = () -> EndpointSampleResponse
|
|
|
|
/// A string representation of the URL for the request.
|
|
public let url: String
|
|
|
|
/// A closure responsible for returning an `EndpointSampleResponse`.
|
|
public let sampleResponseClosure: SampleResponseClosure
|
|
|
|
/// The HTTP method for the request.
|
|
public let method: Moya.Method
|
|
|
|
/// The `Task` for the request.
|
|
public let task: Task
|
|
|
|
/// The HTTP header fields for the request.
|
|
public let httpHeaderFields: [String: String]?
|
|
|
|
public init(url: String,
|
|
sampleResponseClosure: @escaping SampleResponseClosure,
|
|
method: Moya.Method,
|
|
task: Task,
|
|
httpHeaderFields: [String: String]?) {
|
|
|
|
self.url = url
|
|
self.sampleResponseClosure = sampleResponseClosure
|
|
self.method = method
|
|
self.task = task
|
|
self.httpHeaderFields = httpHeaderFields
|
|
}
|
|
|
|
/// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with added HTTP header fields.
|
|
open func adding(newHTTPHeaderFields: [String: String]) -> Endpoint {
|
|
Endpoint(url: url, sampleResponseClosure: sampleResponseClosure, method: method, task: task, httpHeaderFields: add(httpHeaderFields: newHTTPHeaderFields))
|
|
}
|
|
|
|
/// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with replaced `task` parameter.
|
|
open func replacing(task: Task) -> Endpoint {
|
|
Endpoint(url: url, sampleResponseClosure: sampleResponseClosure, method: method, task: task, httpHeaderFields: httpHeaderFields)
|
|
}
|
|
|
|
fileprivate func add(httpHeaderFields headers: [String: String]?) -> [String: String]? {
|
|
guard let unwrappedHeaders = headers, unwrappedHeaders.isEmpty == false else {
|
|
return self.httpHeaderFields
|
|
}
|
|
|
|
var newHTTPHeaderFields = self.httpHeaderFields ?? [:]
|
|
unwrappedHeaders.forEach { key, value in
|
|
newHTTPHeaderFields[key] = value
|
|
}
|
|
return newHTTPHeaderFields
|
|
}
|
|
}
|
|
|
|
/// Extension for converting an `Endpoint` into a `URLRequest`.
|
|
public extension Endpoint {
|
|
// swiftlint:disable cyclomatic_complexity
|
|
/// Returns the `Endpoint` converted to a `URLRequest` if valid. Throws an error otherwise.
|
|
func urlRequest() throws -> URLRequest {
|
|
guard let requestURL = Foundation.URL(string: url) else {
|
|
throw MoyaError.requestMapping(url)
|
|
}
|
|
|
|
var request = URLRequest(url: requestURL)
|
|
request.httpMethod = method.rawValue
|
|
request.allHTTPHeaderFields = httpHeaderFields
|
|
|
|
switch task {
|
|
case .requestPlain, .uploadFile, .uploadMultipart, .downloadDestination:
|
|
return request
|
|
case .requestData(let data):
|
|
request.httpBody = data
|
|
return request
|
|
case let .requestJSONEncodable(encodable):
|
|
return try request.encoded(encodable: encodable)
|
|
case let .requestCustomJSONEncodable(encodable, encoder: encoder):
|
|
return try request.encoded(encodable: encodable, encoder: encoder)
|
|
case let .requestParameters(parameters, parameterEncoding):
|
|
return try request.encoded(parameters: parameters, parameterEncoding: parameterEncoding)
|
|
case let .uploadCompositeMultipart(_, urlParameters):
|
|
let parameterEncoding = URLEncoding(destination: .queryString)
|
|
return try request.encoded(parameters: urlParameters, parameterEncoding: parameterEncoding)
|
|
case let .downloadParameters(parameters, parameterEncoding, _):
|
|
return try request.encoded(parameters: parameters, parameterEncoding: parameterEncoding)
|
|
case let .requestCompositeData(bodyData: bodyData, urlParameters: urlParameters):
|
|
request.httpBody = bodyData
|
|
let parameterEncoding = URLEncoding(destination: .queryString)
|
|
return try request.encoded(parameters: urlParameters, parameterEncoding: parameterEncoding)
|
|
case let .requestCompositeParameters(bodyParameters: bodyParameters, bodyEncoding: bodyParameterEncoding, urlParameters: urlParameters):
|
|
if let bodyParameterEncoding = bodyParameterEncoding as? URLEncoding, bodyParameterEncoding.destination != .httpBody {
|
|
fatalError("Only URLEncoding that `bodyEncoding` accepts is URLEncoding.httpBody. Others like `default`, `queryString` or `methodDependent` are prohibited - if you want to use them, add your parameters to `urlParameters` instead.")
|
|
}
|
|
let bodyfulRequest = try request.encoded(parameters: bodyParameters, parameterEncoding: bodyParameterEncoding)
|
|
let urlEncoding = URLEncoding(destination: .queryString)
|
|
return try bodyfulRequest.encoded(parameters: urlParameters, parameterEncoding: urlEncoding)
|
|
}
|
|
}
|
|
// swiftlint:enable cyclomatic_complexity
|
|
}
|
|
|
|
/// Required for using `Endpoint` as a key type in a `Dictionary`.
|
|
extension Endpoint: Equatable, Hashable {
|
|
public func hash(into hasher: inout Hasher) {
|
|
switch task {
|
|
case let .uploadFile(file):
|
|
hasher.combine(file)
|
|
case let .uploadMultipart(multipartData), let .uploadCompositeMultipart(multipartData, _):
|
|
hasher.combine(multipartData)
|
|
default:
|
|
break
|
|
}
|
|
|
|
if let request = try? urlRequest() {
|
|
hasher.combine(request)
|
|
} else {
|
|
hasher.combine(url)
|
|
}
|
|
}
|
|
|
|
/// Note: If both Endpoints fail to produce a URLRequest the comparison will
|
|
/// fall back to comparing each Endpoint's hashValue.
|
|
public static func == (lhs: Endpoint, rhs: Endpoint) -> Bool {
|
|
let areEndpointsEqualInAdditionalProperties: Bool = {
|
|
switch (lhs.task, rhs.task) {
|
|
case (let .uploadFile(file1), let .uploadFile(file2)):
|
|
return file1 == file2
|
|
case (let .uploadMultipart(multipartData1), let .uploadMultipart(multipartData2)),
|
|
(let .uploadCompositeMultipart(multipartData1, _), let .uploadCompositeMultipart(multipartData2, _)):
|
|
return multipartData1 == multipartData2
|
|
default:
|
|
return true
|
|
}
|
|
}()
|
|
let lhsRequest = try? lhs.urlRequest()
|
|
let rhsRequest = try? rhs.urlRequest()
|
|
if lhsRequest != nil, rhsRequest == nil { return false }
|
|
if lhsRequest == nil, rhsRequest != nil { return false }
|
|
if lhsRequest == nil, rhsRequest == nil { return lhs.hashValue == rhs.hashValue && areEndpointsEqualInAdditionalProperties }
|
|
return lhsRequest == rhsRequest && areEndpointsEqualInAdditionalProperties
|
|
}
|
|
}
|