2
0

ParameterEncoder.swift 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //
  2. // ParameterEncoder.swift
  3. //
  4. // Copyright (c) 2014-2018 Alamofire Software Foundation (http://alamofire.org/)
  5. //
  6. // Permission is hereby granted, free of charge, to any person obtaining a copy
  7. // of this software and associated documentation files (the "Software"), to deal
  8. // in the Software without restriction, including without limitation the rights
  9. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. // copies of the Software, and to permit persons to whom the Software is
  11. // furnished to do so, subject to the following conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be included in
  14. // all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. // THE SOFTWARE.
  23. //
  24. import Foundation
  25. /// A type that can encode any `Encodable` type into a `URLRequest`.
  26. public protocol ParameterEncoder {
  27. /// Encode the provided `Encodable` parameters into `request`.
  28. ///
  29. /// - Parameters:
  30. /// - parameters: The `Encodable` parameter value.
  31. /// - request: The `URLRequest` into which to encode the parameters.
  32. ///
  33. /// - Returns: A `URLRequest` with the result of the encoding.
  34. /// - Throws: An `Error` when encoding fails. For Alamofire provided encoders, this will be an instance of
  35. /// `AFError.parameterEncoderFailed` with an associated `ParameterEncoderFailureReason`.
  36. func encode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest
  37. }
  38. /// A `ParameterEncoder` that encodes types as JSON body data.
  39. ///
  40. /// If no `Content-Type` header is already set on the provided `URLRequest`s, it's set to `application/json`.
  41. open class JSONParameterEncoder: ParameterEncoder {
  42. /// Returns an encoder with default parameters.
  43. public static var `default`: JSONParameterEncoder { JSONParameterEncoder() }
  44. /// Returns an encoder with `JSONEncoder.outputFormatting` set to `.prettyPrinted`.
  45. public static var prettyPrinted: JSONParameterEncoder {
  46. let encoder = JSONEncoder()
  47. encoder.outputFormatting = .prettyPrinted
  48. return JSONParameterEncoder(encoder: encoder)
  49. }
  50. /// Returns an encoder with `JSONEncoder.outputFormatting` set to `.sortedKeys`.
  51. public static var sortedKeys: JSONParameterEncoder {
  52. let encoder = JSONEncoder()
  53. encoder.outputFormatting = .sortedKeys
  54. return JSONParameterEncoder(encoder: encoder)
  55. }
  56. /// `JSONEncoder` used to encode parameters.
  57. public let encoder: JSONEncoder
  58. /// Creates an instance with the provided `JSONEncoder`.
  59. ///
  60. /// - Parameter encoder: The `JSONEncoder`. `JSONEncoder()` by default.
  61. public init(encoder: JSONEncoder = JSONEncoder()) {
  62. self.encoder = encoder
  63. }
  64. open func encode<Parameters: Encodable>(_ parameters: Parameters?,
  65. into request: URLRequest) throws -> URLRequest {
  66. guard let parameters = parameters else { return request }
  67. var request = request
  68. do {
  69. let data = try encoder.encode(parameters)
  70. request.httpBody = data
  71. if request.headers["Content-Type"] == nil {
  72. request.headers.update(.contentType("application/json"))
  73. }
  74. } catch {
  75. throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
  76. }
  77. return request
  78. }
  79. }
  80. extension ParameterEncoder where Self == JSONParameterEncoder {
  81. /// Provides a default `JSONParameterEncoder` instance.
  82. public static var json: JSONParameterEncoder { JSONParameterEncoder() }
  83. /// Creates a `JSONParameterEncoder` using the provided `JSONEncoder`.
  84. ///
  85. /// - Parameter encoder: `JSONEncoder` used to encode parameters. `JSONEncoder()` by default.
  86. /// - Returns: The `JSONParameterEncoder`.
  87. public static func json(encoder: JSONEncoder = JSONEncoder()) -> JSONParameterEncoder {
  88. JSONParameterEncoder(encoder: encoder)
  89. }
  90. }
  91. /// A `ParameterEncoder` that encodes types as URL-encoded query strings to be set on the URL or as body data, depending
  92. /// on the `Destination` set.
  93. ///
  94. /// If no `Content-Type` header is already set on the provided `URLRequest`s, it will be set to
  95. /// `application/x-www-form-urlencoded; charset=utf-8`.
  96. ///
  97. /// Encoding behavior can be customized by passing an instance of `URLEncodedFormEncoder` to the initializer.
  98. open class URLEncodedFormParameterEncoder: ParameterEncoder {
  99. /// Defines where the URL-encoded string should be set for each `URLRequest`.
  100. public enum Destination {
  101. /// Applies the encoded query string to any existing query string for `.get`, `.head`, and `.delete` request.
  102. /// Sets it to the `httpBody` for all other methods.
  103. case methodDependent
  104. /// Applies the encoded query string to any existing query string from the `URLRequest`.
  105. case queryString
  106. /// Applies the encoded query string to the `httpBody` of the `URLRequest`.
  107. case httpBody
  108. /// Determines whether the URL-encoded string should be applied to the `URLRequest`'s `url`.
  109. ///
  110. /// - Parameter method: The `HTTPMethod`.
  111. ///
  112. /// - Returns: Whether the URL-encoded string should be applied to a `URL`.
  113. func encodesParametersInURL(for method: HTTPMethod) -> Bool {
  114. switch self {
  115. case .methodDependent: return [.get, .head, .delete].contains(method)
  116. case .queryString: return true
  117. case .httpBody: return false
  118. }
  119. }
  120. }
  121. /// Returns an encoder with default parameters.
  122. public static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() }
  123. /// The `URLEncodedFormEncoder` to use.
  124. public let encoder: URLEncodedFormEncoder
  125. /// The `Destination` for the URL-encoded string.
  126. public let destination: Destination
  127. /// Creates an instance with the provided `URLEncodedFormEncoder` instance and `Destination` value.
  128. ///
  129. /// - Parameters:
  130. /// - encoder: The `URLEncodedFormEncoder`. `URLEncodedFormEncoder()` by default.
  131. /// - destination: The `Destination`. `.methodDependent` by default.
  132. public init(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: Destination = .methodDependent) {
  133. self.encoder = encoder
  134. self.destination = destination
  135. }
  136. open func encode<Parameters: Encodable>(_ parameters: Parameters?,
  137. into request: URLRequest) throws -> URLRequest {
  138. guard let parameters = parameters else { return request }
  139. var request = request
  140. guard let url = request.url else {
  141. throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
  142. }
  143. guard let method = request.method else {
  144. let rawValue = request.method?.rawValue ?? "nil"
  145. throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.httpMethod(rawValue: rawValue)))
  146. }
  147. if destination.encodesParametersInURL(for: method),
  148. var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
  149. let query: String = try Result<String, Error> { try encoder.encode(parameters) }
  150. .mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get()
  151. let newQueryString = [components.percentEncodedQuery, query].compactMap { $0 }.joinedWithAmpersands()
  152. components.percentEncodedQuery = newQueryString.isEmpty ? nil : newQueryString
  153. guard let newURL = components.url else {
  154. throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
  155. }
  156. request.url = newURL
  157. } else {
  158. if request.headers["Content-Type"] == nil {
  159. request.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
  160. }
  161. request.httpBody = try Result<Data, Error> { try encoder.encode(parameters) }
  162. .mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get()
  163. }
  164. return request
  165. }
  166. }
  167. extension ParameterEncoder where Self == URLEncodedFormParameterEncoder {
  168. /// Provides a default `URLEncodedFormParameterEncoder` instance.
  169. public static var urlEncodedForm: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() }
  170. /// Creates a `URLEncodedFormParameterEncoder` with the provided encoder and destination.
  171. ///
  172. /// - Parameters:
  173. /// - encoder: `URLEncodedFormEncoder` used to encode the parameters. `URLEncodedFormEncoder()` by default.
  174. /// - destination: `Destination` to which to encode the parameters. `.methodDependent` by default.
  175. /// - Returns: The `URLEncodedFormParameterEncoder`.
  176. public static func urlEncodedForm(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(),
  177. destination: URLEncodedFormParameterEncoder.Destination = .methodDependent) -> URLEncodedFormParameterEncoder {
  178. URLEncodedFormParameterEncoder(encoder: encoder, destination: destination)
  179. }
  180. }