1 |
efrain |
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 |
|
|
|
25 |
import Foundation
|
|
|
26 |
|
|
|
27 |
/// A type that can encode any `Encodable` type into a `URLRequest`.
|
|
|
28 |
public protocol ParameterEncoder {
|
|
|
29 |
/// Encode the provided `Encodable` parameters into `request`.
|
|
|
30 |
///
|
|
|
31 |
/// - Parameters:
|
|
|
32 |
/// - parameters: The `Encodable` parameter value.
|
|
|
33 |
/// - request: The `URLRequest` into which to encode the parameters.
|
|
|
34 |
///
|
|
|
35 |
/// - Returns: A `URLRequest` with the result of the encoding.
|
|
|
36 |
/// - Throws: An `Error` when encoding fails. For Alamofire provided encoders, this will be an instance of
|
|
|
37 |
/// `AFError.parameterEncoderFailed` with an associated `ParameterEncoderFailureReason`.
|
|
|
38 |
func encode<Parameters: Encodable>(_ parameters: Parameters?, into request: URLRequest) throws -> URLRequest
|
|
|
39 |
}
|
|
|
40 |
|
|
|
41 |
/// A `ParameterEncoder` that encodes types as JSON body data.
|
|
|
42 |
///
|
|
|
43 |
/// If no `Content-Type` header is already set on the provided `URLRequest`s, it's set to `application/json`.
|
|
|
44 |
open class JSONParameterEncoder: ParameterEncoder {
|
|
|
45 |
/// Returns an encoder with default parameters.
|
|
|
46 |
public static var `default`: JSONParameterEncoder { JSONParameterEncoder() }
|
|
|
47 |
|
|
|
48 |
/// Returns an encoder with `JSONEncoder.outputFormatting` set to `.prettyPrinted`.
|
|
|
49 |
public static var prettyPrinted: JSONParameterEncoder {
|
|
|
50 |
let encoder = JSONEncoder()
|
|
|
51 |
encoder.outputFormatting = .prettyPrinted
|
|
|
52 |
|
|
|
53 |
return JSONParameterEncoder(encoder: encoder)
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
/// Returns an encoder with `JSONEncoder.outputFormatting` set to `.sortedKeys`.
|
|
|
57 |
@available(macOS 10.13, iOS 11.0, tvOS 11.0, watchOS 4.0, *)
|
|
|
58 |
public static var sortedKeys: JSONParameterEncoder {
|
|
|
59 |
let encoder = JSONEncoder()
|
|
|
60 |
encoder.outputFormatting = .sortedKeys
|
|
|
61 |
|
|
|
62 |
return JSONParameterEncoder(encoder: encoder)
|
|
|
63 |
}
|
|
|
64 |
|
|
|
65 |
/// `JSONEncoder` used to encode parameters.
|
|
|
66 |
public let encoder: JSONEncoder
|
|
|
67 |
|
|
|
68 |
/// Creates an instance with the provided `JSONEncoder`.
|
|
|
69 |
///
|
|
|
70 |
/// - Parameter encoder: The `JSONEncoder`. `JSONEncoder()` by default.
|
|
|
71 |
public init(encoder: JSONEncoder = JSONEncoder()) {
|
|
|
72 |
self.encoder = encoder
|
|
|
73 |
}
|
|
|
74 |
|
|
|
75 |
open func encode<Parameters: Encodable>(_ parameters: Parameters?,
|
|
|
76 |
into request: URLRequest) throws -> URLRequest {
|
|
|
77 |
guard let parameters = parameters else { return request }
|
|
|
78 |
|
|
|
79 |
var request = request
|
|
|
80 |
|
|
|
81 |
do {
|
|
|
82 |
let data = try encoder.encode(parameters)
|
|
|
83 |
request.httpBody = data
|
|
|
84 |
if request.headers["Content-Type"] == nil {
|
|
|
85 |
request.headers.update(.contentType("application/json"))
|
|
|
86 |
}
|
|
|
87 |
} catch {
|
|
|
88 |
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
return request
|
|
|
92 |
}
|
|
|
93 |
}
|
|
|
94 |
|
|
|
95 |
#if swift(>=5.5)
|
|
|
96 |
extension ParameterEncoder where Self == JSONParameterEncoder {
|
|
|
97 |
/// Provides a default `JSONParameterEncoder` instance.
|
|
|
98 |
public static var json: JSONParameterEncoder { JSONParameterEncoder() }
|
|
|
99 |
|
|
|
100 |
/// Creates a `JSONParameterEncoder` using the provided `JSONEncoder`.
|
|
|
101 |
///
|
|
|
102 |
/// - Parameter encoder: `JSONEncoder` used to encode parameters. `JSONEncoder()` by default.
|
|
|
103 |
/// - Returns: The `JSONParameterEncoder`.
|
|
|
104 |
public static func json(encoder: JSONEncoder = JSONEncoder()) -> JSONParameterEncoder {
|
|
|
105 |
JSONParameterEncoder(encoder: encoder)
|
|
|
106 |
}
|
|
|
107 |
}
|
|
|
108 |
#endif
|
|
|
109 |
|
|
|
110 |
/// A `ParameterEncoder` that encodes types as URL-encoded query strings to be set on the URL or as body data, depending
|
|
|
111 |
/// on the `Destination` set.
|
|
|
112 |
///
|
|
|
113 |
/// If no `Content-Type` header is already set on the provided `URLRequest`s, it will be set to
|
|
|
114 |
/// `application/x-www-form-urlencoded; charset=utf-8`.
|
|
|
115 |
///
|
|
|
116 |
/// Encoding behavior can be customized by passing an instance of `URLEncodedFormEncoder` to the initializer.
|
|
|
117 |
open class URLEncodedFormParameterEncoder: ParameterEncoder {
|
|
|
118 |
/// Defines where the URL-encoded string should be set for each `URLRequest`.
|
|
|
119 |
public enum Destination {
|
|
|
120 |
/// Applies the encoded query string to any existing query string for `.get`, `.head`, and `.delete` request.
|
|
|
121 |
/// Sets it to the `httpBody` for all other methods.
|
|
|
122 |
case methodDependent
|
|
|
123 |
/// Applies the encoded query string to any existing query string from the `URLRequest`.
|
|
|
124 |
case queryString
|
|
|
125 |
/// Applies the encoded query string to the `httpBody` of the `URLRequest`.
|
|
|
126 |
case httpBody
|
|
|
127 |
|
|
|
128 |
/// Determines whether the URL-encoded string should be applied to the `URLRequest`'s `url`.
|
|
|
129 |
///
|
|
|
130 |
/// - Parameter method: The `HTTPMethod`.
|
|
|
131 |
///
|
|
|
132 |
/// - Returns: Whether the URL-encoded string should be applied to a `URL`.
|
|
|
133 |
func encodesParametersInURL(for method: HTTPMethod) -> Bool {
|
|
|
134 |
switch self {
|
|
|
135 |
case .methodDependent: return [.get, .head, .delete].contains(method)
|
|
|
136 |
case .queryString: return true
|
|
|
137 |
case .httpBody: return false
|
|
|
138 |
}
|
|
|
139 |
}
|
|
|
140 |
}
|
|
|
141 |
|
|
|
142 |
/// Returns an encoder with default parameters.
|
|
|
143 |
public static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() }
|
|
|
144 |
|
|
|
145 |
/// The `URLEncodedFormEncoder` to use.
|
|
|
146 |
public let encoder: URLEncodedFormEncoder
|
|
|
147 |
|
|
|
148 |
/// The `Destination` for the URL-encoded string.
|
|
|
149 |
public let destination: Destination
|
|
|
150 |
|
|
|
151 |
/// Creates an instance with the provided `URLEncodedFormEncoder` instance and `Destination` value.
|
|
|
152 |
///
|
|
|
153 |
/// - Parameters:
|
|
|
154 |
/// - encoder: The `URLEncodedFormEncoder`. `URLEncodedFormEncoder()` by default.
|
|
|
155 |
/// - destination: The `Destination`. `.methodDependent` by default.
|
|
|
156 |
public init(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(), destination: Destination = .methodDependent) {
|
|
|
157 |
self.encoder = encoder
|
|
|
158 |
self.destination = destination
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
open func encode<Parameters: Encodable>(_ parameters: Parameters?,
|
|
|
162 |
into request: URLRequest) throws -> URLRequest {
|
|
|
163 |
guard let parameters = parameters else { return request }
|
|
|
164 |
|
|
|
165 |
var request = request
|
|
|
166 |
|
|
|
167 |
guard let url = request.url else {
|
|
|
168 |
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
|
|
|
169 |
}
|
|
|
170 |
|
|
|
171 |
guard let method = request.method else {
|
|
|
172 |
let rawValue = request.method?.rawValue ?? "nil"
|
|
|
173 |
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.httpMethod(rawValue: rawValue)))
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
if destination.encodesParametersInURL(for: method),
|
|
|
177 |
var components = URLComponents(url: url, resolvingAgainstBaseURL: false) {
|
|
|
178 |
let query: String = try Result<String, Error> { try encoder.encode(parameters) }
|
|
|
179 |
.mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get()
|
|
|
180 |
let newQueryString = [components.percentEncodedQuery, query].compactMap { $0 }.joinedWithAmpersands()
|
|
|
181 |
components.percentEncodedQuery = newQueryString.isEmpty ? nil : newQueryString
|
|
|
182 |
|
|
|
183 |
guard let newURL = components.url else {
|
|
|
184 |
throw AFError.parameterEncoderFailed(reason: .missingRequiredComponent(.url))
|
|
|
185 |
}
|
|
|
186 |
|
|
|
187 |
request.url = newURL
|
|
|
188 |
} else {
|
|
|
189 |
if request.headers["Content-Type"] == nil {
|
|
|
190 |
request.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
|
|
|
191 |
}
|
|
|
192 |
|
|
|
193 |
request.httpBody = try Result<Data, Error> { try encoder.encode(parameters) }
|
|
|
194 |
.mapError { AFError.parameterEncoderFailed(reason: .encoderFailed(error: $0)) }.get()
|
|
|
195 |
}
|
|
|
196 |
|
|
|
197 |
return request
|
|
|
198 |
}
|
|
|
199 |
}
|
|
|
200 |
|
|
|
201 |
#if swift(>=5.5)
|
|
|
202 |
extension ParameterEncoder where Self == URLEncodedFormParameterEncoder {
|
|
|
203 |
/// Provides a default `URLEncodedFormParameterEncoder` instance.
|
|
|
204 |
public static var urlEncodedForm: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() }
|
|
|
205 |
|
|
|
206 |
/// Creates a `URLEncodedFormParameterEncoder` with the provided encoder and destination.
|
|
|
207 |
///
|
|
|
208 |
/// - Parameters:
|
|
|
209 |
/// - encoder: `URLEncodedFormEncoder` used to encode the parameters. `URLEncodedFormEncoder()` by default.
|
|
|
210 |
/// - destination: `Destination` to which to encode the parameters. `.methodDependent` by default.
|
|
|
211 |
/// - Returns: The `URLEncodedFormParameterEncoder`.
|
|
|
212 |
public static func urlEncodedForm(encoder: URLEncodedFormEncoder = URLEncodedFormEncoder(),
|
|
|
213 |
destination: URLEncodedFormParameterEncoder.Destination = .methodDependent) -> URLEncodedFormParameterEncoder {
|
|
|
214 |
URLEncodedFormParameterEncoder(encoder: encoder, destination: destination)
|
|
|
215 |
}
|
|
|
216 |
}
|
|
|
217 |
#endif
|