Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  ParameterEncoding.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 dictionary of parameters to apply to a `URLRequest`.
28
public typealias Parameters = [String: Any]
29
 
30
/// A type used to define how a set of parameters are applied to a `URLRequest`.
31
public protocol ParameterEncoding {
32
    /// Creates a `URLRequest` by encoding parameters and applying them on the passed request.
33
    ///
34
    /// - Parameters:
35
    ///   - urlRequest: `URLRequestConvertible` value onto which parameters will be encoded.
36
    ///   - parameters: `Parameters` to encode onto the request.
37
    ///
38
    /// - Returns:      The encoded `URLRequest`.
39
    /// - Throws:       Any `Error` produced during parameter encoding.
40
    func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
41
}
42
 
43
// MARK: -
44
 
45
/// Creates a url-encoded query string to be set as or appended to any existing URL query string or set as the HTTP
46
/// body of the URL request. Whether the query string is set or appended to any existing URL query string or set as
47
/// the HTTP body depends on the destination of the encoding.
48
///
49
/// The `Content-Type` HTTP header field of an encoded request with HTTP body is set to
50
/// `application/x-www-form-urlencoded; charset=utf-8`.
51
///
52
/// There is no published specification for how to encode collection types. By default the convention of appending
53
/// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for
54
/// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the
55
/// square brackets appended to array keys.
56
///
57
/// `BoolEncoding` can be used to configure how boolean values are encoded. The default behavior is to encode
58
/// `true` as 1 and `false` as 0.
59
public struct URLEncoding: ParameterEncoding {
60
    // MARK: Helper Types
61
 
62
    /// Defines whether the url-encoded query string is applied to the existing query string or HTTP body of the
63
    /// resulting URL request.
64
    public enum Destination {
65
        /// Applies encoded query string result to existing query string for `GET`, `HEAD` and `DELETE` requests and
66
        /// sets as the HTTP body for requests with any other HTTP method.
67
        case methodDependent
68
        /// Sets or appends encoded query string result to existing query string.
69
        case queryString
70
        /// Sets encoded query string result as the HTTP body of the URL request.
71
        case httpBody
72
 
73
        func encodesParametersInURL(for method: HTTPMethod) -> Bool {
74
            switch self {
75
            case .methodDependent: return [.get, .head, .delete].contains(method)
76
            case .queryString: return true
77
            case .httpBody: return false
78
            }
79
        }
80
    }
81
 
82
    /// Configures how `Array` parameters are encoded.
83
    public enum ArrayEncoding {
84
        /// An empty set of square brackets is appended to the key for every value. This is the default behavior.
85
        case brackets
86
        /// No brackets are appended. The key is encoded as is.
87
        case noBrackets
88
        /// Brackets containing the item index are appended. This matches the jQuery and Node.js behavior.
89
        case indexInBrackets
90
 
91
        func encode(key: String, atIndex index: Int) -> String {
92
            switch self {
93
            case .brackets:
94
                return "\(key)[]"
95
            case .noBrackets:
96
                return key
97
            case .indexInBrackets:
98
                return "\(key)[\(index)]"
99
            }
100
        }
101
    }
102
 
103
    /// Configures how `Bool` parameters are encoded.
104
    public enum BoolEncoding {
105
        /// Encode `true` as `1` and `false` as `0`. This is the default behavior.
106
        case numeric
107
        /// Encode `true` and `false` as string literals.
108
        case literal
109
 
110
        func encode(value: Bool) -> String {
111
            switch self {
112
            case .numeric:
113
                return value ? "1" : "0"
114
            case .literal:
115
                return value ? "true" : "false"
116
            }
117
        }
118
    }
119
 
120
    // MARK: Properties
121
 
122
    /// Returns a default `URLEncoding` instance with a `.methodDependent` destination.
123
    public static var `default`: URLEncoding { URLEncoding() }
124
 
125
    /// Returns a `URLEncoding` instance with a `.queryString` destination.
126
    public static var queryString: URLEncoding { URLEncoding(destination: .queryString) }
127
 
128
    /// Returns a `URLEncoding` instance with an `.httpBody` destination.
129
    public static var httpBody: URLEncoding { URLEncoding(destination: .httpBody) }
130
 
131
    /// The destination defining where the encoded query string is to be applied to the URL request.
132
    public let destination: Destination
133
 
134
    /// The encoding to use for `Array` parameters.
135
    public let arrayEncoding: ArrayEncoding
136
 
137
    /// The encoding to use for `Bool` parameters.
138
    public let boolEncoding: BoolEncoding
139
 
140
    // MARK: Initialization
141
 
142
    /// Creates an instance using the specified parameters.
143
    ///
144
    /// - Parameters:
145
    ///   - destination:   `Destination` defining where the encoded query string will be applied. `.methodDependent` by
146
    ///                    default.
147
    ///   - arrayEncoding: `ArrayEncoding` to use. `.brackets` by default.
148
    ///   - boolEncoding:  `BoolEncoding` to use. `.numeric` by default.
149
    public init(destination: Destination = .methodDependent,
150
                arrayEncoding: ArrayEncoding = .brackets,
151
                boolEncoding: BoolEncoding = .numeric) {
152
        self.destination = destination
153
        self.arrayEncoding = arrayEncoding
154
        self.boolEncoding = boolEncoding
155
    }
156
 
157
    // MARK: Encoding
158
 
159
    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
160
        var urlRequest = try urlRequest.asURLRequest()
161
 
162
        guard let parameters = parameters else { return urlRequest }
163
 
164
        if let method = urlRequest.method, destination.encodesParametersInURL(for: method) {
165
            guard let url = urlRequest.url else {
166
                throw AFError.parameterEncodingFailed(reason: .missingURL)
167
            }
168
 
169
            if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {
170
                let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
171
                urlComponents.percentEncodedQuery = percentEncodedQuery
172
                urlRequest.url = urlComponents.url
173
            }
174
        } else {
175
            if urlRequest.headers["Content-Type"] == nil {
176
                urlRequest.headers.update(.contentType("application/x-www-form-urlencoded; charset=utf-8"))
177
            }
178
 
179
            urlRequest.httpBody = Data(query(parameters).utf8)
180
        }
181
 
182
        return urlRequest
183
    }
184
 
185
    /// Creates a percent-escaped, URL encoded query string components from the given key-value pair recursively.
186
    ///
187
    /// - Parameters:
188
    ///   - key:   Key of the query component.
189
    ///   - value: Value of the query component.
190
    ///
191
    /// - Returns: The percent-escaped, URL encoded query string components.
192
    public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
193
        var components: [(String, String)] = []
194
        switch value {
195
        case let dictionary as [String: Any]:
196
            for (nestedKey, value) in dictionary {
197
                components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
198
            }
199
        case let array as [Any]:
200
            for (index, value) in array.enumerated() {
201
                components += queryComponents(fromKey: arrayEncoding.encode(key: key, atIndex: index), value: value)
202
            }
203
        case let number as NSNumber:
204
            if number.isBool {
205
                components.append((escape(key), escape(boolEncoding.encode(value: number.boolValue))))
206
            } else {
207
                components.append((escape(key), escape("\(number)")))
208
            }
209
        case let bool as Bool:
210
            components.append((escape(key), escape(boolEncoding.encode(value: bool))))
211
        default:
212
            components.append((escape(key), escape("\(value)")))
213
        }
214
        return components
215
    }
216
 
217
    /// Creates a percent-escaped string following RFC 3986 for a query string key or value.
218
    ///
219
    /// - Parameter string: `String` to be percent-escaped.
220
    ///
221
    /// - Returns:          The percent-escaped `String`.
222
    public func escape(_ string: String) -> String {
223
        string.addingPercentEncoding(withAllowedCharacters: .afURLQueryAllowed) ?? string
224
    }
225
 
226
    private func query(_ parameters: [String: Any]) -> String {
227
        var components: [(String, String)] = []
228
 
229
        for key in parameters.keys.sorted(by: <) {
230
            let value = parameters[key]!
231
            components += queryComponents(fromKey: key, value: value)
232
        }
233
        return components.map { "\($0)=\($1)" }.joined(separator: "&")
234
    }
235
}
236
 
237
// MARK: -
238
 
239
/// Uses `JSONSerialization` to create a JSON representation of the parameters object, which is set as the body of the
240
/// request. The `Content-Type` HTTP header field of an encoded request is set to `application/json`.
241
public struct JSONEncoding: ParameterEncoding {
242
    // MARK: Properties
243
 
244
    /// Returns a `JSONEncoding` instance with default writing options.
245
    public static var `default`: JSONEncoding { JSONEncoding() }
246
 
247
    /// Returns a `JSONEncoding` instance with `.prettyPrinted` writing options.
248
    public static var prettyPrinted: JSONEncoding { JSONEncoding(options: .prettyPrinted) }
249
 
250
    /// The options for writing the parameters as JSON data.
251
    public let options: JSONSerialization.WritingOptions
252
 
253
    // MARK: Initialization
254
 
255
    /// Creates an instance using the specified `WritingOptions`.
256
    ///
257
    /// - Parameter options: `JSONSerialization.WritingOptions` to use.
258
    public init(options: JSONSerialization.WritingOptions = []) {
259
        self.options = options
260
    }
261
 
262
    // MARK: Encoding
263
 
264
    public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
265
        var urlRequest = try urlRequest.asURLRequest()
266
 
267
        guard let parameters = parameters else { return urlRequest }
268
 
269
        do {
270
            let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
271
 
272
            if urlRequest.headers["Content-Type"] == nil {
273
                urlRequest.headers.update(.contentType("application/json"))
274
            }
275
 
276
            urlRequest.httpBody = data
277
        } catch {
278
            throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
279
        }
280
 
281
        return urlRequest
282
    }
283
 
284
    /// Encodes any JSON compatible object into a `URLRequest`.
285
    ///
286
    /// - Parameters:
287
    ///   - urlRequest: `URLRequestConvertible` value into which the object will be encoded.
288
    ///   - jsonObject: `Any` value (must be JSON compatible` to be encoded into the `URLRequest`. `nil` by default.
289
    ///
290
    /// - Returns:      The encoded `URLRequest`.
291
    /// - Throws:       Any `Error` produced during encoding.
292
    public func encode(_ urlRequest: URLRequestConvertible, withJSONObject jsonObject: Any? = nil) throws -> URLRequest {
293
        var urlRequest = try urlRequest.asURLRequest()
294
 
295
        guard let jsonObject = jsonObject else { return urlRequest }
296
 
297
        do {
298
            let data = try JSONSerialization.data(withJSONObject: jsonObject, options: options)
299
 
300
            if urlRequest.headers["Content-Type"] == nil {
301
                urlRequest.headers.update(.contentType("application/json"))
302
            }
303
 
304
            urlRequest.httpBody = data
305
        } catch {
306
            throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
307
        }
308
 
309
        return urlRequest
310
    }
311
}
312
 
313
// MARK: -
314
 
315
extension NSNumber {
316
    fileprivate var isBool: Bool {
317
        // Use Obj-C type encoding to check whether the underlying type is a `Bool`, as it's guaranteed as part of
318
        // swift-corelibs-foundation, per [this discussion on the Swift forums](https://forums.swift.org/t/alamofire-on-linux-possible-but-not-release-ready/34553/22).
319
        String(cString: objCType) == "c"
320
    }
321
}