Endpoint.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import Foundation
  2. /// Used for stubbing responses.
  3. public enum EndpointSampleResponse {
  4. /// The network returned a response, including status code and data.
  5. case networkResponse(Int, Data)
  6. /// The network returned response which can be fully customized.
  7. case response(HTTPURLResponse, Data)
  8. /// The network failed to send the request, or failed to retrieve a response (eg a timeout).
  9. case networkError(NSError)
  10. }
  11. /// Class for reifying a target of the `Target` enum unto a concrete `Endpoint`.
  12. /// - Note: As of Moya 11.0.0 Endpoint is no longer generic.
  13. /// Existing code should work as is after removing the generic.
  14. /// See #1529 and #1524 for the discussion.
  15. open class Endpoint {
  16. public typealias SampleResponseClosure = () -> EndpointSampleResponse
  17. /// A string representation of the URL for the request.
  18. public let url: String
  19. /// A closure responsible for returning an `EndpointSampleResponse`.
  20. public let sampleResponseClosure: SampleResponseClosure
  21. /// The HTTP method for the request.
  22. public let method: Moya.Method
  23. /// The `Task` for the request.
  24. public let task: Task
  25. /// The HTTP header fields for the request.
  26. public let httpHeaderFields: [String: String]?
  27. public init(url: String,
  28. sampleResponseClosure: @escaping SampleResponseClosure,
  29. method: Moya.Method,
  30. task: Task,
  31. httpHeaderFields: [String: String]?) {
  32. self.url = url
  33. self.sampleResponseClosure = sampleResponseClosure
  34. self.method = method
  35. self.task = task
  36. self.httpHeaderFields = httpHeaderFields
  37. }
  38. /// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with added HTTP header fields.
  39. open func adding(newHTTPHeaderFields: [String: String]) -> Endpoint {
  40. return Endpoint(url: url, sampleResponseClosure: sampleResponseClosure, method: method, task: task, httpHeaderFields: add(httpHeaderFields: newHTTPHeaderFields))
  41. }
  42. /// Convenience method for creating a new `Endpoint` with the same properties as the receiver, but with replaced `task` parameter.
  43. open func replacing(task: Task) -> Endpoint {
  44. return Endpoint(url: url, sampleResponseClosure: sampleResponseClosure, method: method, task: task, httpHeaderFields: httpHeaderFields)
  45. }
  46. fileprivate func add(httpHeaderFields headers: [String: String]?) -> [String: String]? {
  47. guard let unwrappedHeaders = headers, unwrappedHeaders.isEmpty == false else {
  48. return self.httpHeaderFields
  49. }
  50. var newHTTPHeaderFields = self.httpHeaderFields ?? [:]
  51. unwrappedHeaders.forEach { key, value in
  52. newHTTPHeaderFields[key] = value
  53. }
  54. return newHTTPHeaderFields
  55. }
  56. }
  57. /// Extension for converting an `Endpoint` into a `URLRequest`.
  58. public extension Endpoint {
  59. // swiftlint:disable cyclomatic_complexity
  60. /// Returns the `Endpoint` converted to a `URLRequest` if valid. Throws an error otherwise.
  61. func urlRequest() throws -> URLRequest {
  62. guard let requestURL = Foundation.URL(string: url) else {
  63. throw MoyaError.requestMapping(url)
  64. }
  65. var request = URLRequest(url: requestURL)
  66. request.httpMethod = method.rawValue
  67. request.allHTTPHeaderFields = httpHeaderFields
  68. switch task {
  69. case .requestPlain, .uploadFile, .uploadMultipart, .downloadDestination:
  70. return request
  71. case .requestData(let data):
  72. request.httpBody = data
  73. return request
  74. case let .requestJSONEncodable(encodable):
  75. return try request.encoded(encodable: encodable)
  76. case let .requestCustomJSONEncodable(encodable, encoder: encoder):
  77. return try request.encoded(encodable: encodable, encoder: encoder)
  78. case let .requestParameters(parameters, parameterEncoding):
  79. return try request.encoded(parameters: parameters, parameterEncoding: parameterEncoding)
  80. case let .uploadCompositeMultipart(_, urlParameters):
  81. let parameterEncoding = URLEncoding(destination: .queryString)
  82. return try request.encoded(parameters: urlParameters, parameterEncoding: parameterEncoding)
  83. case let .downloadParameters(parameters, parameterEncoding, _):
  84. return try request.encoded(parameters: parameters, parameterEncoding: parameterEncoding)
  85. case let .requestCompositeData(bodyData: bodyData, urlParameters: urlParameters):
  86. request.httpBody = bodyData
  87. let parameterEncoding = URLEncoding(destination: .queryString)
  88. return try request.encoded(parameters: urlParameters, parameterEncoding: parameterEncoding)
  89. case let .requestCompositeParameters(bodyParameters: bodyParameters, bodyEncoding: bodyParameterEncoding, urlParameters: urlParameters):
  90. if let bodyParameterEncoding = bodyParameterEncoding as? URLEncoding, bodyParameterEncoding.destination != .httpBody {
  91. 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.")
  92. }
  93. let bodyfulRequest = try request.encoded(parameters: bodyParameters, parameterEncoding: bodyParameterEncoding)
  94. let urlEncoding = URLEncoding(destination: .queryString)
  95. return try bodyfulRequest.encoded(parameters: urlParameters, parameterEncoding: urlEncoding)
  96. }
  97. }
  98. // swiftlint:enable cyclomatic_complexity
  99. }
  100. /// Required for using `Endpoint` as a key type in a `Dictionary`.
  101. extension Endpoint: Equatable, Hashable {
  102. public func hash(into hasher: inout Hasher) {
  103. guard let request = try? urlRequest() else {
  104. hasher.combine(url)
  105. return
  106. }
  107. hasher.combine(request)
  108. }
  109. /// Note: If both Endpoints fail to produce a URLRequest the comparison will
  110. /// fall back to comparing each Endpoint's hashValue.
  111. public static func == (lhs: Endpoint, rhs: Endpoint) -> Bool {
  112. let lhsRequest = try? lhs.urlRequest()
  113. let rhsRequest = try? rhs.urlRequest()
  114. if lhsRequest != nil, rhsRequest == nil { return false }
  115. if lhsRequest == nil, rhsRequest != nil { return false }
  116. if lhsRequest == nil, rhsRequest == nil { return lhs.hashValue == rhs.hashValue }
  117. return (lhsRequest == rhsRequest)
  118. }
  119. }