Response.swift 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import Foundation
  2. /// Represents a response to a `MoyaProvider.request`.
  3. public final class Response: CustomDebugStringConvertible, Equatable {
  4. /// The status code of the response.
  5. public let statusCode: Int
  6. /// The response data.
  7. public let data: Data
  8. /// The original URLRequest for the response.
  9. public let request: URLRequest?
  10. /// The HTTPURLResponse object.
  11. public let response: HTTPURLResponse?
  12. public init(statusCode: Int, data: Data, request: URLRequest? = nil, response: HTTPURLResponse? = nil) {
  13. self.statusCode = statusCode
  14. self.data = data
  15. self.request = request
  16. self.response = response
  17. }
  18. /// A text description of the `Response`.
  19. public var description: String {
  20. return "Status Code: \(statusCode), Data Length: \(data.count)"
  21. }
  22. /// A text description of the `Response`. Suitable for debugging.
  23. public var debugDescription: String {
  24. return description
  25. }
  26. public static func == (lhs: Response, rhs: Response) -> Bool {
  27. return lhs.statusCode == rhs.statusCode
  28. && lhs.data == rhs.data
  29. && lhs.response == rhs.response
  30. }
  31. }
  32. public extension Response {
  33. /**
  34. Returns the `Response` if the `statusCode` falls within the specified range.
  35. - parameters:
  36. - statusCodes: The range of acceptable status codes.
  37. - throws: `MoyaError.statusCode` when others are encountered.
  38. */
  39. func filter<R: RangeExpression>(statusCodes: R) throws -> Response where R.Bound == Int {
  40. guard statusCodes.contains(statusCode) else {
  41. throw MoyaError.statusCode(self)
  42. }
  43. return self
  44. }
  45. /**
  46. Returns the `Response` if it has the specified `statusCode`.
  47. - parameters:
  48. - statusCode: The acceptable status code.
  49. - throws: `MoyaError.statusCode` when others are encountered.
  50. */
  51. func filter(statusCode: Int) throws -> Response {
  52. return try filter(statusCodes: statusCode...statusCode)
  53. }
  54. /**
  55. Returns the `Response` if the `statusCode` falls within the range 200 - 299.
  56. - throws: `MoyaError.statusCode` when others are encountered.
  57. */
  58. func filterSuccessfulStatusCodes() throws -> Response {
  59. return try filter(statusCodes: 200...299)
  60. }
  61. /**
  62. Returns the `Response` if the `statusCode` falls within the range 200 - 399.
  63. - throws: `MoyaError.statusCode` when others are encountered.
  64. */
  65. func filterSuccessfulStatusAndRedirectCodes() throws -> Response {
  66. return try filter(statusCodes: 200...399)
  67. }
  68. /// Maps data received from the signal into an Image.
  69. func mapImage() throws -> Image {
  70. guard let image = Image(data: data) else {
  71. throw MoyaError.imageMapping(self)
  72. }
  73. return image
  74. }
  75. /// Maps data received from the signal into a JSON object.
  76. ///
  77. /// - parameter failsOnEmptyData: A Boolean value determining
  78. /// whether the mapping should fail if the data is empty.
  79. func mapJSON(failsOnEmptyData: Bool = true) throws -> Any {
  80. do {
  81. return try JSONSerialization.jsonObject(with: data, options: .allowFragments)
  82. } catch {
  83. if data.count < 1 && !failsOnEmptyData {
  84. return NSNull()
  85. }
  86. throw MoyaError.jsonMapping(self)
  87. }
  88. }
  89. /// Maps data received from the signal into a String.
  90. ///
  91. /// - parameter atKeyPath: Optional key path at which to parse string.
  92. func mapString(atKeyPath keyPath: String? = nil) throws -> String {
  93. if let keyPath = keyPath {
  94. // Key path was provided, try to parse string at key path
  95. guard let jsonDictionary = try mapJSON() as? NSDictionary,
  96. let string = jsonDictionary.value(forKeyPath: keyPath) as? String else {
  97. throw MoyaError.stringMapping(self)
  98. }
  99. return string
  100. } else {
  101. // Key path was not provided, parse entire response as string
  102. guard let string = String(data: data, encoding: .utf8) else {
  103. throw MoyaError.stringMapping(self)
  104. }
  105. return string
  106. }
  107. }
  108. /// Maps data received from the signal into a Decodable object.
  109. ///
  110. /// - parameter atKeyPath: Optional key path at which to parse object.
  111. /// - parameter using: A `JSONDecoder` instance which is used to decode data to an object.
  112. func map<D: Decodable>(_ type: D.Type, atKeyPath keyPath: String? = nil, using decoder: JSONDecoder = JSONDecoder(), failsOnEmptyData: Bool = true) throws -> D {
  113. let serializeToData: (Any) throws -> Data? = { (jsonObject) in
  114. guard JSONSerialization.isValidJSONObject(jsonObject) else {
  115. return nil
  116. }
  117. do {
  118. return try JSONSerialization.data(withJSONObject: jsonObject)
  119. } catch {
  120. throw MoyaError.jsonMapping(self)
  121. }
  122. }
  123. let jsonData: Data
  124. keyPathCheck: if let keyPath = keyPath {
  125. guard let jsonObject = (try mapJSON(failsOnEmptyData: failsOnEmptyData) as? NSDictionary)?.value(forKeyPath: keyPath) else {
  126. if failsOnEmptyData {
  127. throw MoyaError.jsonMapping(self)
  128. } else {
  129. jsonData = data
  130. break keyPathCheck
  131. }
  132. }
  133. if let data = try serializeToData(jsonObject) {
  134. jsonData = data
  135. } else {
  136. let wrappedJsonObject = ["value": jsonObject]
  137. let wrappedJsonData: Data
  138. if let data = try serializeToData(wrappedJsonObject) {
  139. wrappedJsonData = data
  140. } else {
  141. throw MoyaError.jsonMapping(self)
  142. }
  143. do {
  144. return try decoder.decode(DecodableWrapper<D>.self, from: wrappedJsonData).value
  145. } catch let error {
  146. throw MoyaError.objectMapping(error, self)
  147. }
  148. }
  149. } else {
  150. jsonData = data
  151. }
  152. do {
  153. if jsonData.count < 1 && !failsOnEmptyData {
  154. if let emptyJSONObjectData = "{}".data(using: .utf8), let emptyDecodableValue = try? decoder.decode(D.self, from: emptyJSONObjectData) {
  155. return emptyDecodableValue
  156. } else if let emptyJSONArrayData = "[{}]".data(using: .utf8), let emptyDecodableValue = try? decoder.decode(D.self, from: emptyJSONArrayData) {
  157. return emptyDecodableValue
  158. }
  159. }
  160. return try decoder.decode(D.self, from: jsonData)
  161. } catch let error {
  162. throw MoyaError.objectMapping(error, self)
  163. }
  164. }
  165. }
  166. private struct DecodableWrapper<T: Decodable>: Decodable {
  167. let value: T
  168. }