1 |
efrain |
1 |
//
|
|
|
2 |
// URLEncodedFormEncoder.swift
|
|
|
3 |
//
|
|
|
4 |
// Copyright (c) 2019 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 |
/// An object that encodes instances into URL-encoded query strings.
|
|
|
28 |
///
|
|
|
29 |
/// There is no published specification for how to encode collection types. By default, the convention of appending
|
|
|
30 |
/// `[]` to the key for array values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for
|
|
|
31 |
/// nested dictionary values (`foo[bar]=baz`) is used. Optionally, `ArrayEncoding` can be used to omit the
|
|
|
32 |
/// square brackets appended to array keys.
|
|
|
33 |
///
|
|
|
34 |
/// `BoolEncoding` can be used to configure how `Bool` values are encoded. The default behavior is to encode
|
|
|
35 |
/// `true` as 1 and `false` as 0.
|
|
|
36 |
///
|
|
|
37 |
/// `DateEncoding` can be used to configure how `Date` values are encoded. By default, the `.deferredToDate`
|
|
|
38 |
/// strategy is used, which formats dates from their structure.
|
|
|
39 |
///
|
|
|
40 |
/// `SpaceEncoding` can be used to configure how spaces are encoded. Modern encodings use percent replacement (`%20`),
|
|
|
41 |
/// while older encodings may expect spaces to be replaced with `+`.
|
|
|
42 |
///
|
|
|
43 |
/// This type is largely based on Vapor's [`url-encoded-form`](https://github.com/vapor/url-encoded-form) project.
|
|
|
44 |
public final class URLEncodedFormEncoder {
|
|
|
45 |
/// Encoding to use for `Array` values.
|
|
|
46 |
public enum ArrayEncoding {
|
|
|
47 |
/// An empty set of square brackets ("[]") are appended to the key for every value. This is the default encoding.
|
|
|
48 |
case brackets
|
|
|
49 |
/// No brackets are appended to the key and the key is encoded as is.
|
|
|
50 |
case noBrackets
|
|
|
51 |
/// Brackets containing the item index are appended. This matches the jQuery and Node.js behavior.
|
|
|
52 |
case indexInBrackets
|
|
|
53 |
|
|
|
54 |
/// Encodes the key according to the encoding.
|
|
|
55 |
///
|
|
|
56 |
/// - Parameters:
|
|
|
57 |
/// - key: The `key` to encode.
|
|
|
58 |
/// - index: When this enum instance is `.indexInBrackets`, the `index` to encode.
|
|
|
59 |
///
|
|
|
60 |
/// - Returns: The encoded key.
|
|
|
61 |
func encode(_ key: String, atIndex index: Int) -> String {
|
|
|
62 |
switch self {
|
|
|
63 |
case .brackets: return "\(key)[]"
|
|
|
64 |
case .noBrackets: return key
|
|
|
65 |
case .indexInBrackets: return "\(key)[\(index)]"
|
|
|
66 |
}
|
|
|
67 |
}
|
|
|
68 |
}
|
|
|
69 |
|
|
|
70 |
/// Encoding to use for `Bool` values.
|
|
|
71 |
public enum BoolEncoding {
|
|
|
72 |
/// Encodes `true` as `1`, `false` as `0`.
|
|
|
73 |
case numeric
|
|
|
74 |
/// Encodes `true` as "true", `false` as "false". This is the default encoding.
|
|
|
75 |
case literal
|
|
|
76 |
|
|
|
77 |
/// Encodes the given `Bool` as a `String`.
|
|
|
78 |
///
|
|
|
79 |
/// - Parameter value: The `Bool` to encode.
|
|
|
80 |
///
|
|
|
81 |
/// - Returns: The encoded `String`.
|
|
|
82 |
func encode(_ value: Bool) -> String {
|
|
|
83 |
switch self {
|
|
|
84 |
case .numeric: return value ? "1" : "0"
|
|
|
85 |
case .literal: return value ? "true" : "false"
|
|
|
86 |
}
|
|
|
87 |
}
|
|
|
88 |
}
|
|
|
89 |
|
|
|
90 |
/// Encoding to use for `Data` values.
|
|
|
91 |
public enum DataEncoding {
|
|
|
92 |
/// Defers encoding to the `Data` type.
|
|
|
93 |
case deferredToData
|
|
|
94 |
/// Encodes `Data` as a Base64-encoded string. This is the default encoding.
|
|
|
95 |
case base64
|
|
|
96 |
/// Encode the `Data` as a custom value encoded by the given closure.
|
|
|
97 |
case custom((Data) throws -> String)
|
|
|
98 |
|
|
|
99 |
/// Encodes `Data` according to the encoding.
|
|
|
100 |
///
|
|
|
101 |
/// - Parameter data: The `Data` to encode.
|
|
|
102 |
///
|
|
|
103 |
/// - Returns: The encoded `String`, or `nil` if the `Data` should be encoded according to its
|
|
|
104 |
/// `Encodable` implementation.
|
|
|
105 |
func encode(_ data: Data) throws -> String? {
|
|
|
106 |
switch self {
|
|
|
107 |
case .deferredToData: return nil
|
|
|
108 |
case .base64: return data.base64EncodedString()
|
|
|
109 |
case let .custom(encoding): return try encoding(data)
|
|
|
110 |
}
|
|
|
111 |
}
|
|
|
112 |
}
|
|
|
113 |
|
|
|
114 |
/// Encoding to use for `Date` values.
|
|
|
115 |
public enum DateEncoding {
|
|
|
116 |
/// ISO8601 and RFC3339 formatter.
|
|
|
117 |
private static let iso8601Formatter: ISO8601DateFormatter = {
|
|
|
118 |
let formatter = ISO8601DateFormatter()
|
|
|
119 |
formatter.formatOptions = .withInternetDateTime
|
|
|
120 |
return formatter
|
|
|
121 |
}()
|
|
|
122 |
|
|
|
123 |
/// Defers encoding to the `Date` type. This is the default encoding.
|
|
|
124 |
case deferredToDate
|
|
|
125 |
/// Encodes `Date`s as seconds since midnight UTC on January 1, 1970.
|
|
|
126 |
case secondsSince1970
|
|
|
127 |
/// Encodes `Date`s as milliseconds since midnight UTC on January 1, 1970.
|
|
|
128 |
case millisecondsSince1970
|
|
|
129 |
/// Encodes `Date`s according to the ISO8601 and RFC3339 standards.
|
|
|
130 |
case iso8601
|
|
|
131 |
/// Encodes `Date`s using the given `DateFormatter`.
|
|
|
132 |
case formatted(DateFormatter)
|
|
|
133 |
/// Encodes `Date`s using the given closure.
|
|
|
134 |
case custom((Date) throws -> String)
|
|
|
135 |
|
|
|
136 |
/// Encodes the date according to the encoding.
|
|
|
137 |
///
|
|
|
138 |
/// - Parameter date: The `Date` to encode.
|
|
|
139 |
///
|
|
|
140 |
/// - Returns: The encoded `String`, or `nil` if the `Date` should be encoded according to its
|
|
|
141 |
/// `Encodable` implementation.
|
|
|
142 |
func encode(_ date: Date) throws -> String? {
|
|
|
143 |
switch self {
|
|
|
144 |
case .deferredToDate:
|
|
|
145 |
return nil
|
|
|
146 |
case .secondsSince1970:
|
|
|
147 |
return String(date.timeIntervalSince1970)
|
|
|
148 |
case .millisecondsSince1970:
|
|
|
149 |
return String(date.timeIntervalSince1970 * 1000.0)
|
|
|
150 |
case .iso8601:
|
|
|
151 |
return DateEncoding.iso8601Formatter.string(from: date)
|
|
|
152 |
case let .formatted(formatter):
|
|
|
153 |
return formatter.string(from: date)
|
|
|
154 |
case let .custom(closure):
|
|
|
155 |
return try closure(date)
|
|
|
156 |
}
|
|
|
157 |
}
|
|
|
158 |
}
|
|
|
159 |
|
|
|
160 |
/// Encoding to use for keys.
|
|
|
161 |
///
|
|
|
162 |
/// This type is derived from [`JSONEncoder`'s `KeyEncodingStrategy`](https://github.com/apple/swift/blob/6aa313b8dd5f05135f7f878eccc1db6f9fbe34ff/stdlib/public/Darwin/Foundation/JSONEncoder.swift#L128)
|
|
|
163 |
/// and [`XMLEncoder`s `KeyEncodingStrategy`](https://github.com/MaxDesiatov/XMLCoder/blob/master/Sources/XMLCoder/Encoder/XMLEncoder.swift#L102).
|
|
|
164 |
public enum KeyEncoding {
|
|
|
165 |
/// Use the keys specified by each type. This is the default encoding.
|
|
|
166 |
case useDefaultKeys
|
|
|
167 |
/// Convert from "camelCaseKeys" to "snake_case_keys" before writing a key.
|
|
|
168 |
///
|
|
|
169 |
/// Capital characters are determined by testing membership in
|
|
|
170 |
/// `CharacterSet.uppercaseLetters` and `CharacterSet.lowercaseLetters`
|
|
|
171 |
/// (Unicode General Categories Lu and Lt).
|
|
|
172 |
/// The conversion to lower case uses `Locale.system`, also known as
|
|
|
173 |
/// the ICU "root" locale. This means the result is consistent
|
|
|
174 |
/// regardless of the current user's locale and language preferences.
|
|
|
175 |
///
|
|
|
176 |
/// Converting from camel case to snake case:
|
|
|
177 |
/// 1. Splits words at the boundary of lower-case to upper-case
|
|
|
178 |
/// 2. Inserts `_` between words
|
|
|
179 |
/// 3. Lowercases the entire string
|
|
|
180 |
/// 4. Preserves starting and ending `_`.
|
|
|
181 |
///
|
|
|
182 |
/// For example, `oneTwoThree` becomes `one_two_three`. `_oneTwoThree_` becomes `_one_two_three_`.
|
|
|
183 |
///
|
|
|
184 |
/// - Note: Using a key encoding strategy has a nominal performance cost, as each string key has to be converted.
|
|
|
185 |
case convertToSnakeCase
|
|
|
186 |
/// Same as convertToSnakeCase, but using `-` instead of `_`.
|
|
|
187 |
/// For example `oneTwoThree` becomes `one-two-three`.
|
|
|
188 |
case convertToKebabCase
|
|
|
189 |
/// Capitalize the first letter only.
|
|
|
190 |
/// For example `oneTwoThree` becomes `OneTwoThree`.
|
|
|
191 |
case capitalized
|
|
|
192 |
/// Uppercase all letters.
|
|
|
193 |
/// For example `oneTwoThree` becomes `ONETWOTHREE`.
|
|
|
194 |
case uppercased
|
|
|
195 |
/// Lowercase all letters.
|
|
|
196 |
/// For example `oneTwoThree` becomes `onetwothree`.
|
|
|
197 |
case lowercased
|
|
|
198 |
/// A custom encoding using the provided closure.
|
|
|
199 |
case custom((String) -> String)
|
|
|
200 |
|
|
|
201 |
func encode(_ key: String) -> String {
|
|
|
202 |
switch self {
|
|
|
203 |
case .useDefaultKeys: return key
|
|
|
204 |
case .convertToSnakeCase: return convertToSnakeCase(key)
|
|
|
205 |
case .convertToKebabCase: return convertToKebabCase(key)
|
|
|
206 |
case .capitalized: return String(key.prefix(1).uppercased() + key.dropFirst())
|
|
|
207 |
case .uppercased: return key.uppercased()
|
|
|
208 |
case .lowercased: return key.lowercased()
|
|
|
209 |
case let .custom(encoding): return encoding(key)
|
|
|
210 |
}
|
|
|
211 |
}
|
|
|
212 |
|
|
|
213 |
private func convertToSnakeCase(_ key: String) -> String {
|
|
|
214 |
convert(key, usingSeparator: "_")
|
|
|
215 |
}
|
|
|
216 |
|
|
|
217 |
private func convertToKebabCase(_ key: String) -> String {
|
|
|
218 |
convert(key, usingSeparator: "-")
|
|
|
219 |
}
|
|
|
220 |
|
|
|
221 |
private func convert(_ key: String, usingSeparator separator: String) -> String {
|
|
|
222 |
guard !key.isEmpty else { return key }
|
|
|
223 |
|
|
|
224 |
var words: [Range<String.Index>] = []
|
|
|
225 |
// The general idea of this algorithm is to split words on
|
|
|
226 |
// transition from lower to upper case, then on transition of >1
|
|
|
227 |
// upper case characters to lowercase
|
|
|
228 |
//
|
|
|
229 |
// myProperty -> my_property
|
|
|
230 |
// myURLProperty -> my_url_property
|
|
|
231 |
//
|
|
|
232 |
// It is assumed, per Swift naming conventions, that the first character of the key is lowercase.
|
|
|
233 |
var wordStart = key.startIndex
|
|
|
234 |
var searchRange = key.index(after: wordStart)..<key.endIndex
|
|
|
235 |
|
|
|
236 |
// Find next uppercase character
|
|
|
237 |
while let upperCaseRange = key.rangeOfCharacter(from: CharacterSet.uppercaseLetters, options: [], range: searchRange) {
|
|
|
238 |
let untilUpperCase = wordStart..<upperCaseRange.lowerBound
|
|
|
239 |
words.append(untilUpperCase)
|
|
|
240 |
|
|
|
241 |
// Find next lowercase character
|
|
|
242 |
searchRange = upperCaseRange.lowerBound..<searchRange.upperBound
|
|
|
243 |
guard let lowerCaseRange = key.rangeOfCharacter(from: CharacterSet.lowercaseLetters, options: [], range: searchRange) else {
|
|
|
244 |
// There are no more lower case letters. Just end here.
|
|
|
245 |
wordStart = searchRange.lowerBound
|
|
|
246 |
break
|
|
|
247 |
}
|
|
|
248 |
|
|
|
249 |
// Is the next lowercase letter more than 1 after the uppercase?
|
|
|
250 |
// If so, we encountered a group of uppercase letters that we
|
|
|
251 |
// should treat as its own word
|
|
|
252 |
let nextCharacterAfterCapital = key.index(after: upperCaseRange.lowerBound)
|
|
|
253 |
if lowerCaseRange.lowerBound == nextCharacterAfterCapital {
|
|
|
254 |
// The next character after capital is a lower case character and therefore not a word boundary.
|
|
|
255 |
// Continue searching for the next upper case for the boundary.
|
|
|
256 |
wordStart = upperCaseRange.lowerBound
|
|
|
257 |
} else {
|
|
|
258 |
// There was a range of >1 capital letters. Turn those into a word, stopping at the capital before the lower case character.
|
|
|
259 |
let beforeLowerIndex = key.index(before: lowerCaseRange.lowerBound)
|
|
|
260 |
words.append(upperCaseRange.lowerBound..<beforeLowerIndex)
|
|
|
261 |
|
|
|
262 |
// Next word starts at the capital before the lowercase we just found
|
|
|
263 |
wordStart = beforeLowerIndex
|
|
|
264 |
}
|
|
|
265 |
searchRange = lowerCaseRange.upperBound..<searchRange.upperBound
|
|
|
266 |
}
|
|
|
267 |
words.append(wordStart..<searchRange.upperBound)
|
|
|
268 |
let result = words.map { range in
|
|
|
269 |
key[range].lowercased()
|
|
|
270 |
}.joined(separator: separator)
|
|
|
271 |
|
|
|
272 |
return result
|
|
|
273 |
}
|
|
|
274 |
}
|
|
|
275 |
|
|
|
276 |
/// Encoding to use for spaces.
|
|
|
277 |
public enum SpaceEncoding {
|
|
|
278 |
/// Encodes spaces according to normal percent escaping rules (%20).
|
|
|
279 |
case percentEscaped
|
|
|
280 |
/// Encodes spaces as `+`,
|
|
|
281 |
case plusReplaced
|
|
|
282 |
|
|
|
283 |
/// Encodes the string according to the encoding.
|
|
|
284 |
///
|
|
|
285 |
/// - Parameter string: The `String` to encode.
|
|
|
286 |
///
|
|
|
287 |
/// - Returns: The encoded `String`.
|
|
|
288 |
func encode(_ string: String) -> String {
|
|
|
289 |
switch self {
|
|
|
290 |
case .percentEscaped: return string.replacingOccurrences(of: " ", with: "%20")
|
|
|
291 |
case .plusReplaced: return string.replacingOccurrences(of: " ", with: "+")
|
|
|
292 |
}
|
|
|
293 |
}
|
|
|
294 |
}
|
|
|
295 |
|
|
|
296 |
/// `URLEncodedFormEncoder` error.
|
|
|
297 |
public enum Error: Swift.Error {
|
|
|
298 |
/// An invalid root object was created by the encoder. Only keyed values are valid.
|
|
|
299 |
case invalidRootObject(String)
|
|
|
300 |
|
|
|
301 |
var localizedDescription: String {
|
|
|
302 |
switch self {
|
|
|
303 |
case let .invalidRootObject(object):
|
|
|
304 |
return "URLEncodedFormEncoder requires keyed root object. Received \(object) instead."
|
|
|
305 |
}
|
|
|
306 |
}
|
|
|
307 |
}
|
|
|
308 |
|
|
|
309 |
/// Whether or not to sort the encoded key value pairs.
|
|
|
310 |
///
|
|
|
311 |
/// - Note: This setting ensures a consistent ordering for all encodings of the same parameters. When set to `false`,
|
|
|
312 |
/// encoded `Dictionary` values may have a different encoded order each time they're encoded due to
|
|
|
313 |
/// ` Dictionary`'s random storage order, but `Encodable` types will maintain their encoded order.
|
|
|
314 |
public let alphabetizeKeyValuePairs: Bool
|
|
|
315 |
/// The `ArrayEncoding` to use.
|
|
|
316 |
public let arrayEncoding: ArrayEncoding
|
|
|
317 |
/// The `BoolEncoding` to use.
|
|
|
318 |
public let boolEncoding: BoolEncoding
|
|
|
319 |
/// THe `DataEncoding` to use.
|
|
|
320 |
public let dataEncoding: DataEncoding
|
|
|
321 |
/// The `DateEncoding` to use.
|
|
|
322 |
public let dateEncoding: DateEncoding
|
|
|
323 |
/// The `KeyEncoding` to use.
|
|
|
324 |
public let keyEncoding: KeyEncoding
|
|
|
325 |
/// The `SpaceEncoding` to use.
|
|
|
326 |
public let spaceEncoding: SpaceEncoding
|
|
|
327 |
/// The `CharacterSet` of allowed (non-escaped) characters.
|
|
|
328 |
public var allowedCharacters: CharacterSet
|
|
|
329 |
|
|
|
330 |
/// Creates an instance from the supplied parameters.
|
|
|
331 |
///
|
|
|
332 |
/// - Parameters:
|
|
|
333 |
/// - alphabetizeKeyValuePairs: Whether or not to sort the encoded key value pairs. `true` by default.
|
|
|
334 |
/// - arrayEncoding: The `ArrayEncoding` to use. `.brackets` by default.
|
|
|
335 |
/// - boolEncoding: The `BoolEncoding` to use. `.numeric` by default.
|
|
|
336 |
/// - dataEncoding: The `DataEncoding` to use. `.base64` by default.
|
|
|
337 |
/// - dateEncoding: The `DateEncoding` to use. `.deferredToDate` by default.
|
|
|
338 |
/// - keyEncoding: The `KeyEncoding` to use. `.useDefaultKeys` by default.
|
|
|
339 |
/// - spaceEncoding: The `SpaceEncoding` to use. `.percentEscaped` by default.
|
|
|
340 |
/// - allowedCharacters: The `CharacterSet` of allowed (non-escaped) characters. `.afURLQueryAllowed` by
|
|
|
341 |
/// default.
|
|
|
342 |
public init(alphabetizeKeyValuePairs: Bool = true,
|
|
|
343 |
arrayEncoding: ArrayEncoding = .brackets,
|
|
|
344 |
boolEncoding: BoolEncoding = .numeric,
|
|
|
345 |
dataEncoding: DataEncoding = .base64,
|
|
|
346 |
dateEncoding: DateEncoding = .deferredToDate,
|
|
|
347 |
keyEncoding: KeyEncoding = .useDefaultKeys,
|
|
|
348 |
spaceEncoding: SpaceEncoding = .percentEscaped,
|
|
|
349 |
allowedCharacters: CharacterSet = .afURLQueryAllowed) {
|
|
|
350 |
self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs
|
|
|
351 |
self.arrayEncoding = arrayEncoding
|
|
|
352 |
self.boolEncoding = boolEncoding
|
|
|
353 |
self.dataEncoding = dataEncoding
|
|
|
354 |
self.dateEncoding = dateEncoding
|
|
|
355 |
self.keyEncoding = keyEncoding
|
|
|
356 |
self.spaceEncoding = spaceEncoding
|
|
|
357 |
self.allowedCharacters = allowedCharacters
|
|
|
358 |
}
|
|
|
359 |
|
|
|
360 |
func encode(_ value: Encodable) throws -> URLEncodedFormComponent {
|
|
|
361 |
let context = URLEncodedFormContext(.object([]))
|
|
|
362 |
let encoder = _URLEncodedFormEncoder(context: context,
|
|
|
363 |
boolEncoding: boolEncoding,
|
|
|
364 |
dataEncoding: dataEncoding,
|
|
|
365 |
dateEncoding: dateEncoding)
|
|
|
366 |
try value.encode(to: encoder)
|
|
|
367 |
|
|
|
368 |
return context.component
|
|
|
369 |
}
|
|
|
370 |
|
|
|
371 |
/// Encodes the `value` as a URL form encoded `String`.
|
|
|
372 |
///
|
|
|
373 |
/// - Parameter value: The `Encodable` value.`
|
|
|
374 |
///
|
|
|
375 |
/// - Returns: The encoded `String`.
|
|
|
376 |
/// - Throws: An `Error` or `EncodingError` instance if encoding fails.
|
|
|
377 |
public func encode(_ value: Encodable) throws -> String {
|
|
|
378 |
let component: URLEncodedFormComponent = try encode(value)
|
|
|
379 |
|
|
|
380 |
guard case let .object(object) = component else {
|
|
|
381 |
throw Error.invalidRootObject("\(component)")
|
|
|
382 |
}
|
|
|
383 |
|
|
|
384 |
let serializer = URLEncodedFormSerializer(alphabetizeKeyValuePairs: alphabetizeKeyValuePairs,
|
|
|
385 |
arrayEncoding: arrayEncoding,
|
|
|
386 |
keyEncoding: keyEncoding,
|
|
|
387 |
spaceEncoding: spaceEncoding,
|
|
|
388 |
allowedCharacters: allowedCharacters)
|
|
|
389 |
let query = serializer.serialize(object)
|
|
|
390 |
|
|
|
391 |
return query
|
|
|
392 |
}
|
|
|
393 |
|
|
|
394 |
/// Encodes the value as `Data`. This is performed by first creating an encoded `String` and then returning the
|
|
|
395 |
/// `.utf8` data.
|
|
|
396 |
///
|
|
|
397 |
/// - Parameter value: The `Encodable` value.
|
|
|
398 |
///
|
|
|
399 |
/// - Returns: The encoded `Data`.
|
|
|
400 |
///
|
|
|
401 |
/// - Throws: An `Error` or `EncodingError` instance if encoding fails.
|
|
|
402 |
public func encode(_ value: Encodable) throws -> Data {
|
|
|
403 |
let string: String = try encode(value)
|
|
|
404 |
|
|
|
405 |
return Data(string.utf8)
|
|
|
406 |
}
|
|
|
407 |
}
|
|
|
408 |
|
|
|
409 |
final class _URLEncodedFormEncoder {
|
|
|
410 |
var codingPath: [CodingKey]
|
|
|
411 |
// Returns an empty dictionary, as this encoder doesn't support userInfo.
|
|
|
412 |
var userInfo: [CodingUserInfoKey: Any] { [:] }
|
|
|
413 |
|
|
|
414 |
let context: URLEncodedFormContext
|
|
|
415 |
|
|
|
416 |
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
|
|
|
417 |
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
|
|
|
418 |
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
|
|
|
419 |
|
|
|
420 |
init(context: URLEncodedFormContext,
|
|
|
421 |
codingPath: [CodingKey] = [],
|
|
|
422 |
boolEncoding: URLEncodedFormEncoder.BoolEncoding,
|
|
|
423 |
dataEncoding: URLEncodedFormEncoder.DataEncoding,
|
|
|
424 |
dateEncoding: URLEncodedFormEncoder.DateEncoding) {
|
|
|
425 |
self.context = context
|
|
|
426 |
self.codingPath = codingPath
|
|
|
427 |
self.boolEncoding = boolEncoding
|
|
|
428 |
self.dataEncoding = dataEncoding
|
|
|
429 |
self.dateEncoding = dateEncoding
|
|
|
430 |
}
|
|
|
431 |
}
|
|
|
432 |
|
|
|
433 |
extension _URLEncodedFormEncoder: Encoder {
|
|
|
434 |
func container<Key>(keyedBy type: Key.Type) -> KeyedEncodingContainer<Key> where Key: CodingKey {
|
|
|
435 |
let container = _URLEncodedFormEncoder.KeyedContainer<Key>(context: context,
|
|
|
436 |
codingPath: codingPath,
|
|
|
437 |
boolEncoding: boolEncoding,
|
|
|
438 |
dataEncoding: dataEncoding,
|
|
|
439 |
dateEncoding: dateEncoding)
|
|
|
440 |
return KeyedEncodingContainer(container)
|
|
|
441 |
}
|
|
|
442 |
|
|
|
443 |
func unkeyedContainer() -> UnkeyedEncodingContainer {
|
|
|
444 |
_URLEncodedFormEncoder.UnkeyedContainer(context: context,
|
|
|
445 |
codingPath: codingPath,
|
|
|
446 |
boolEncoding: boolEncoding,
|
|
|
447 |
dataEncoding: dataEncoding,
|
|
|
448 |
dateEncoding: dateEncoding)
|
|
|
449 |
}
|
|
|
450 |
|
|
|
451 |
func singleValueContainer() -> SingleValueEncodingContainer {
|
|
|
452 |
_URLEncodedFormEncoder.SingleValueContainer(context: context,
|
|
|
453 |
codingPath: codingPath,
|
|
|
454 |
boolEncoding: boolEncoding,
|
|
|
455 |
dataEncoding: dataEncoding,
|
|
|
456 |
dateEncoding: dateEncoding)
|
|
|
457 |
}
|
|
|
458 |
}
|
|
|
459 |
|
|
|
460 |
final class URLEncodedFormContext {
|
|
|
461 |
var component: URLEncodedFormComponent
|
|
|
462 |
|
|
|
463 |
init(_ component: URLEncodedFormComponent) {
|
|
|
464 |
self.component = component
|
|
|
465 |
}
|
|
|
466 |
}
|
|
|
467 |
|
|
|
468 |
enum URLEncodedFormComponent {
|
|
|
469 |
typealias Object = [(key: String, value: URLEncodedFormComponent)]
|
|
|
470 |
|
|
|
471 |
case string(String)
|
|
|
472 |
case array([URLEncodedFormComponent])
|
|
|
473 |
case object(Object)
|
|
|
474 |
|
|
|
475 |
/// Converts self to an `[URLEncodedFormData]` or returns `nil` if not convertible.
|
|
|
476 |
var array: [URLEncodedFormComponent]? {
|
|
|
477 |
switch self {
|
|
|
478 |
case let .array(array): return array
|
|
|
479 |
default: return nil
|
|
|
480 |
}
|
|
|
481 |
}
|
|
|
482 |
|
|
|
483 |
/// Converts self to an `Object` or returns `nil` if not convertible.
|
|
|
484 |
var object: Object? {
|
|
|
485 |
switch self {
|
|
|
486 |
case let .object(object): return object
|
|
|
487 |
default: return nil
|
|
|
488 |
}
|
|
|
489 |
}
|
|
|
490 |
|
|
|
491 |
/// Sets self to the supplied value at a given path.
|
|
|
492 |
///
|
|
|
493 |
/// data.set(to: "hello", at: ["path", "to", "value"])
|
|
|
494 |
///
|
|
|
495 |
/// - parameters:
|
|
|
496 |
/// - value: Value of `Self` to set at the supplied path.
|
|
|
497 |
/// - path: `CodingKey` path to update with the supplied value.
|
|
|
498 |
public mutating func set(to value: URLEncodedFormComponent, at path: [CodingKey]) {
|
|
|
499 |
set(&self, to: value, at: path)
|
|
|
500 |
}
|
|
|
501 |
|
|
|
502 |
/// Recursive backing method to `set(to:at:)`.
|
|
|
503 |
private func set(_ context: inout URLEncodedFormComponent, to value: URLEncodedFormComponent, at path: [CodingKey]) {
|
|
|
504 |
guard !path.isEmpty else {
|
|
|
505 |
context = value
|
|
|
506 |
return
|
|
|
507 |
}
|
|
|
508 |
|
|
|
509 |
let end = path[0]
|
|
|
510 |
var child: URLEncodedFormComponent
|
|
|
511 |
switch path.count {
|
|
|
512 |
case 1:
|
|
|
513 |
child = value
|
|
|
514 |
case 2...:
|
|
|
515 |
if let index = end.intValue {
|
|
|
516 |
let array = context.array ?? []
|
|
|
517 |
if array.count > index {
|
|
|
518 |
child = array[index]
|
|
|
519 |
} else {
|
|
|
520 |
child = .array([])
|
|
|
521 |
}
|
|
|
522 |
set(&child, to: value, at: Array(path[1...]))
|
|
|
523 |
} else {
|
|
|
524 |
child = context.object?.first { $0.key == end.stringValue }?.value ?? .object(.init())
|
|
|
525 |
set(&child, to: value, at: Array(path[1...]))
|
|
|
526 |
}
|
|
|
527 |
default: fatalError("Unreachable")
|
|
|
528 |
}
|
|
|
529 |
|
|
|
530 |
if let index = end.intValue {
|
|
|
531 |
if var array = context.array {
|
|
|
532 |
if array.count > index {
|
|
|
533 |
array[index] = child
|
|
|
534 |
} else {
|
|
|
535 |
array.append(child)
|
|
|
536 |
}
|
|
|
537 |
context = .array(array)
|
|
|
538 |
} else {
|
|
|
539 |
context = .array([child])
|
|
|
540 |
}
|
|
|
541 |
} else {
|
|
|
542 |
if var object = context.object {
|
|
|
543 |
if let index = object.firstIndex(where: { $0.key == end.stringValue }) {
|
|
|
544 |
object[index] = (key: end.stringValue, value: child)
|
|
|
545 |
} else {
|
|
|
546 |
object.append((key: end.stringValue, value: child))
|
|
|
547 |
}
|
|
|
548 |
context = .object(object)
|
|
|
549 |
} else {
|
|
|
550 |
context = .object([(key: end.stringValue, value: child)])
|
|
|
551 |
}
|
|
|
552 |
}
|
|
|
553 |
}
|
|
|
554 |
}
|
|
|
555 |
|
|
|
556 |
struct AnyCodingKey: CodingKey, Hashable {
|
|
|
557 |
let stringValue: String
|
|
|
558 |
let intValue: Int?
|
|
|
559 |
|
|
|
560 |
init?(stringValue: String) {
|
|
|
561 |
self.stringValue = stringValue
|
|
|
562 |
intValue = nil
|
|
|
563 |
}
|
|
|
564 |
|
|
|
565 |
init?(intValue: Int) {
|
|
|
566 |
stringValue = "\(intValue)"
|
|
|
567 |
self.intValue = intValue
|
|
|
568 |
}
|
|
|
569 |
|
|
|
570 |
init<Key>(_ base: Key) where Key: CodingKey {
|
|
|
571 |
if let intValue = base.intValue {
|
|
|
572 |
self.init(intValue: intValue)!
|
|
|
573 |
} else {
|
|
|
574 |
self.init(stringValue: base.stringValue)!
|
|
|
575 |
}
|
|
|
576 |
}
|
|
|
577 |
}
|
|
|
578 |
|
|
|
579 |
extension _URLEncodedFormEncoder {
|
|
|
580 |
final class KeyedContainer<Key> where Key: CodingKey {
|
|
|
581 |
var codingPath: [CodingKey]
|
|
|
582 |
|
|
|
583 |
private let context: URLEncodedFormContext
|
|
|
584 |
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
|
|
|
585 |
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
|
|
|
586 |
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
|
|
|
587 |
|
|
|
588 |
init(context: URLEncodedFormContext,
|
|
|
589 |
codingPath: [CodingKey],
|
|
|
590 |
boolEncoding: URLEncodedFormEncoder.BoolEncoding,
|
|
|
591 |
dataEncoding: URLEncodedFormEncoder.DataEncoding,
|
|
|
592 |
dateEncoding: URLEncodedFormEncoder.DateEncoding) {
|
|
|
593 |
self.context = context
|
|
|
594 |
self.codingPath = codingPath
|
|
|
595 |
self.boolEncoding = boolEncoding
|
|
|
596 |
self.dataEncoding = dataEncoding
|
|
|
597 |
self.dateEncoding = dateEncoding
|
|
|
598 |
}
|
|
|
599 |
|
|
|
600 |
private func nestedCodingPath(for key: CodingKey) -> [CodingKey] {
|
|
|
601 |
codingPath + [key]
|
|
|
602 |
}
|
|
|
603 |
}
|
|
|
604 |
}
|
|
|
605 |
|
|
|
606 |
extension _URLEncodedFormEncoder.KeyedContainer: KeyedEncodingContainerProtocol {
|
|
|
607 |
func encodeNil(forKey key: Key) throws {
|
|
|
608 |
let context = EncodingError.Context(codingPath: codingPath,
|
|
|
609 |
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
|
|
|
610 |
throw EncodingError.invalidValue("\(key): nil", context)
|
|
|
611 |
}
|
|
|
612 |
|
|
|
613 |
func encode<T>(_ value: T, forKey key: Key) throws where T: Encodable {
|
|
|
614 |
var container = nestedSingleValueEncoder(for: key)
|
|
|
615 |
try container.encode(value)
|
|
|
616 |
}
|
|
|
617 |
|
|
|
618 |
func nestedSingleValueEncoder(for key: Key) -> SingleValueEncodingContainer {
|
|
|
619 |
let container = _URLEncodedFormEncoder.SingleValueContainer(context: context,
|
|
|
620 |
codingPath: nestedCodingPath(for: key),
|
|
|
621 |
boolEncoding: boolEncoding,
|
|
|
622 |
dataEncoding: dataEncoding,
|
|
|
623 |
dateEncoding: dateEncoding)
|
|
|
624 |
|
|
|
625 |
return container
|
|
|
626 |
}
|
|
|
627 |
|
|
|
628 |
func nestedUnkeyedContainer(forKey key: Key) -> UnkeyedEncodingContainer {
|
|
|
629 |
let container = _URLEncodedFormEncoder.UnkeyedContainer(context: context,
|
|
|
630 |
codingPath: nestedCodingPath(for: key),
|
|
|
631 |
boolEncoding: boolEncoding,
|
|
|
632 |
dataEncoding: dataEncoding,
|
|
|
633 |
dateEncoding: dateEncoding)
|
|
|
634 |
|
|
|
635 |
return container
|
|
|
636 |
}
|
|
|
637 |
|
|
|
638 |
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type, forKey key: Key) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey {
|
|
|
639 |
let container = _URLEncodedFormEncoder.KeyedContainer<NestedKey>(context: context,
|
|
|
640 |
codingPath: nestedCodingPath(for: key),
|
|
|
641 |
boolEncoding: boolEncoding,
|
|
|
642 |
dataEncoding: dataEncoding,
|
|
|
643 |
dateEncoding: dateEncoding)
|
|
|
644 |
|
|
|
645 |
return KeyedEncodingContainer(container)
|
|
|
646 |
}
|
|
|
647 |
|
|
|
648 |
func superEncoder() -> Encoder {
|
|
|
649 |
_URLEncodedFormEncoder(context: context,
|
|
|
650 |
codingPath: codingPath,
|
|
|
651 |
boolEncoding: boolEncoding,
|
|
|
652 |
dataEncoding: dataEncoding,
|
|
|
653 |
dateEncoding: dateEncoding)
|
|
|
654 |
}
|
|
|
655 |
|
|
|
656 |
func superEncoder(forKey key: Key) -> Encoder {
|
|
|
657 |
_URLEncodedFormEncoder(context: context,
|
|
|
658 |
codingPath: nestedCodingPath(for: key),
|
|
|
659 |
boolEncoding: boolEncoding,
|
|
|
660 |
dataEncoding: dataEncoding,
|
|
|
661 |
dateEncoding: dateEncoding)
|
|
|
662 |
}
|
|
|
663 |
}
|
|
|
664 |
|
|
|
665 |
extension _URLEncodedFormEncoder {
|
|
|
666 |
final class SingleValueContainer {
|
|
|
667 |
var codingPath: [CodingKey]
|
|
|
668 |
|
|
|
669 |
private var canEncodeNewValue = true
|
|
|
670 |
|
|
|
671 |
private let context: URLEncodedFormContext
|
|
|
672 |
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
|
|
|
673 |
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
|
|
|
674 |
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
|
|
|
675 |
|
|
|
676 |
init(context: URLEncodedFormContext,
|
|
|
677 |
codingPath: [CodingKey],
|
|
|
678 |
boolEncoding: URLEncodedFormEncoder.BoolEncoding,
|
|
|
679 |
dataEncoding: URLEncodedFormEncoder.DataEncoding,
|
|
|
680 |
dateEncoding: URLEncodedFormEncoder.DateEncoding) {
|
|
|
681 |
self.context = context
|
|
|
682 |
self.codingPath = codingPath
|
|
|
683 |
self.boolEncoding = boolEncoding
|
|
|
684 |
self.dataEncoding = dataEncoding
|
|
|
685 |
self.dateEncoding = dateEncoding
|
|
|
686 |
}
|
|
|
687 |
|
|
|
688 |
private func checkCanEncode(value: Any?) throws {
|
|
|
689 |
guard canEncodeNewValue else {
|
|
|
690 |
let context = EncodingError.Context(codingPath: codingPath,
|
|
|
691 |
debugDescription: "Attempt to encode value through single value container when previously value already encoded.")
|
|
|
692 |
throw EncodingError.invalidValue(value as Any, context)
|
|
|
693 |
}
|
|
|
694 |
}
|
|
|
695 |
}
|
|
|
696 |
}
|
|
|
697 |
|
|
|
698 |
extension _URLEncodedFormEncoder.SingleValueContainer: SingleValueEncodingContainer {
|
|
|
699 |
func encodeNil() throws {
|
|
|
700 |
try checkCanEncode(value: nil)
|
|
|
701 |
defer { canEncodeNewValue = false }
|
|
|
702 |
|
|
|
703 |
let context = EncodingError.Context(codingPath: codingPath,
|
|
|
704 |
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
|
|
|
705 |
throw EncodingError.invalidValue("nil", context)
|
|
|
706 |
}
|
|
|
707 |
|
|
|
708 |
func encode(_ value: Bool) throws {
|
|
|
709 |
try encode(value, as: String(boolEncoding.encode(value)))
|
|
|
710 |
}
|
|
|
711 |
|
|
|
712 |
func encode(_ value: String) throws {
|
|
|
713 |
try encode(value, as: value)
|
|
|
714 |
}
|
|
|
715 |
|
|
|
716 |
func encode(_ value: Double) throws {
|
|
|
717 |
try encode(value, as: String(value))
|
|
|
718 |
}
|
|
|
719 |
|
|
|
720 |
func encode(_ value: Float) throws {
|
|
|
721 |
try encode(value, as: String(value))
|
|
|
722 |
}
|
|
|
723 |
|
|
|
724 |
func encode(_ value: Int) throws {
|
|
|
725 |
try encode(value, as: String(value))
|
|
|
726 |
}
|
|
|
727 |
|
|
|
728 |
func encode(_ value: Int8) throws {
|
|
|
729 |
try encode(value, as: String(value))
|
|
|
730 |
}
|
|
|
731 |
|
|
|
732 |
func encode(_ value: Int16) throws {
|
|
|
733 |
try encode(value, as: String(value))
|
|
|
734 |
}
|
|
|
735 |
|
|
|
736 |
func encode(_ value: Int32) throws {
|
|
|
737 |
try encode(value, as: String(value))
|
|
|
738 |
}
|
|
|
739 |
|
|
|
740 |
func encode(_ value: Int64) throws {
|
|
|
741 |
try encode(value, as: String(value))
|
|
|
742 |
}
|
|
|
743 |
|
|
|
744 |
func encode(_ value: UInt) throws {
|
|
|
745 |
try encode(value, as: String(value))
|
|
|
746 |
}
|
|
|
747 |
|
|
|
748 |
func encode(_ value: UInt8) throws {
|
|
|
749 |
try encode(value, as: String(value))
|
|
|
750 |
}
|
|
|
751 |
|
|
|
752 |
func encode(_ value: UInt16) throws {
|
|
|
753 |
try encode(value, as: String(value))
|
|
|
754 |
}
|
|
|
755 |
|
|
|
756 |
func encode(_ value: UInt32) throws {
|
|
|
757 |
try encode(value, as: String(value))
|
|
|
758 |
}
|
|
|
759 |
|
|
|
760 |
func encode(_ value: UInt64) throws {
|
|
|
761 |
try encode(value, as: String(value))
|
|
|
762 |
}
|
|
|
763 |
|
|
|
764 |
private func encode<T>(_ value: T, as string: String) throws where T: Encodable {
|
|
|
765 |
try checkCanEncode(value: value)
|
|
|
766 |
defer { canEncodeNewValue = false }
|
|
|
767 |
|
|
|
768 |
context.component.set(to: .string(string), at: codingPath)
|
|
|
769 |
}
|
|
|
770 |
|
|
|
771 |
func encode<T>(_ value: T) throws where T: Encodable {
|
|
|
772 |
switch value {
|
|
|
773 |
case let date as Date:
|
|
|
774 |
guard let string = try dateEncoding.encode(date) else {
|
|
|
775 |
try attemptToEncode(value)
|
|
|
776 |
return
|
|
|
777 |
}
|
|
|
778 |
|
|
|
779 |
try encode(value, as: string)
|
|
|
780 |
case let data as Data:
|
|
|
781 |
guard let string = try dataEncoding.encode(data) else {
|
|
|
782 |
try attemptToEncode(value)
|
|
|
783 |
return
|
|
|
784 |
}
|
|
|
785 |
|
|
|
786 |
try encode(value, as: string)
|
|
|
787 |
case let decimal as Decimal:
|
|
|
788 |
// Decimal's `Encodable` implementation returns an object, not a single value, so override it.
|
|
|
789 |
try encode(value, as: String(describing: decimal))
|
|
|
790 |
default:
|
|
|
791 |
try attemptToEncode(value)
|
|
|
792 |
}
|
|
|
793 |
}
|
|
|
794 |
|
|
|
795 |
private func attemptToEncode<T>(_ value: T) throws where T: Encodable {
|
|
|
796 |
try checkCanEncode(value: value)
|
|
|
797 |
defer { canEncodeNewValue = false }
|
|
|
798 |
|
|
|
799 |
let encoder = _URLEncodedFormEncoder(context: context,
|
|
|
800 |
codingPath: codingPath,
|
|
|
801 |
boolEncoding: boolEncoding,
|
|
|
802 |
dataEncoding: dataEncoding,
|
|
|
803 |
dateEncoding: dateEncoding)
|
|
|
804 |
try value.encode(to: encoder)
|
|
|
805 |
}
|
|
|
806 |
}
|
|
|
807 |
|
|
|
808 |
extension _URLEncodedFormEncoder {
|
|
|
809 |
final class UnkeyedContainer {
|
|
|
810 |
var codingPath: [CodingKey]
|
|
|
811 |
|
|
|
812 |
var count = 0
|
|
|
813 |
var nestedCodingPath: [CodingKey] {
|
|
|
814 |
codingPath + [AnyCodingKey(intValue: count)!]
|
|
|
815 |
}
|
|
|
816 |
|
|
|
817 |
private let context: URLEncodedFormContext
|
|
|
818 |
private let boolEncoding: URLEncodedFormEncoder.BoolEncoding
|
|
|
819 |
private let dataEncoding: URLEncodedFormEncoder.DataEncoding
|
|
|
820 |
private let dateEncoding: URLEncodedFormEncoder.DateEncoding
|
|
|
821 |
|
|
|
822 |
init(context: URLEncodedFormContext,
|
|
|
823 |
codingPath: [CodingKey],
|
|
|
824 |
boolEncoding: URLEncodedFormEncoder.BoolEncoding,
|
|
|
825 |
dataEncoding: URLEncodedFormEncoder.DataEncoding,
|
|
|
826 |
dateEncoding: URLEncodedFormEncoder.DateEncoding) {
|
|
|
827 |
self.context = context
|
|
|
828 |
self.codingPath = codingPath
|
|
|
829 |
self.boolEncoding = boolEncoding
|
|
|
830 |
self.dataEncoding = dataEncoding
|
|
|
831 |
self.dateEncoding = dateEncoding
|
|
|
832 |
}
|
|
|
833 |
}
|
|
|
834 |
}
|
|
|
835 |
|
|
|
836 |
extension _URLEncodedFormEncoder.UnkeyedContainer: UnkeyedEncodingContainer {
|
|
|
837 |
func encodeNil() throws {
|
|
|
838 |
let context = EncodingError.Context(codingPath: codingPath,
|
|
|
839 |
debugDescription: "URLEncodedFormEncoder cannot encode nil values.")
|
|
|
840 |
throw EncodingError.invalidValue("nil", context)
|
|
|
841 |
}
|
|
|
842 |
|
|
|
843 |
func encode<T>(_ value: T) throws where T: Encodable {
|
|
|
844 |
var container = nestedSingleValueContainer()
|
|
|
845 |
try container.encode(value)
|
|
|
846 |
}
|
|
|
847 |
|
|
|
848 |
func nestedSingleValueContainer() -> SingleValueEncodingContainer {
|
|
|
849 |
defer { count += 1 }
|
|
|
850 |
|
|
|
851 |
return _URLEncodedFormEncoder.SingleValueContainer(context: context,
|
|
|
852 |
codingPath: nestedCodingPath,
|
|
|
853 |
boolEncoding: boolEncoding,
|
|
|
854 |
dataEncoding: dataEncoding,
|
|
|
855 |
dateEncoding: dateEncoding)
|
|
|
856 |
}
|
|
|
857 |
|
|
|
858 |
func nestedContainer<NestedKey>(keyedBy keyType: NestedKey.Type) -> KeyedEncodingContainer<NestedKey> where NestedKey: CodingKey {
|
|
|
859 |
defer { count += 1 }
|
|
|
860 |
let container = _URLEncodedFormEncoder.KeyedContainer<NestedKey>(context: context,
|
|
|
861 |
codingPath: nestedCodingPath,
|
|
|
862 |
boolEncoding: boolEncoding,
|
|
|
863 |
dataEncoding: dataEncoding,
|
|
|
864 |
dateEncoding: dateEncoding)
|
|
|
865 |
|
|
|
866 |
return KeyedEncodingContainer(container)
|
|
|
867 |
}
|
|
|
868 |
|
|
|
869 |
func nestedUnkeyedContainer() -> UnkeyedEncodingContainer {
|
|
|
870 |
defer { count += 1 }
|
|
|
871 |
|
|
|
872 |
return _URLEncodedFormEncoder.UnkeyedContainer(context: context,
|
|
|
873 |
codingPath: nestedCodingPath,
|
|
|
874 |
boolEncoding: boolEncoding,
|
|
|
875 |
dataEncoding: dataEncoding,
|
|
|
876 |
dateEncoding: dateEncoding)
|
|
|
877 |
}
|
|
|
878 |
|
|
|
879 |
func superEncoder() -> Encoder {
|
|
|
880 |
defer { count += 1 }
|
|
|
881 |
|
|
|
882 |
return _URLEncodedFormEncoder(context: context,
|
|
|
883 |
codingPath: codingPath,
|
|
|
884 |
boolEncoding: boolEncoding,
|
|
|
885 |
dataEncoding: dataEncoding,
|
|
|
886 |
dateEncoding: dateEncoding)
|
|
|
887 |
}
|
|
|
888 |
}
|
|
|
889 |
|
|
|
890 |
final class URLEncodedFormSerializer {
|
|
|
891 |
private let alphabetizeKeyValuePairs: Bool
|
|
|
892 |
private let arrayEncoding: URLEncodedFormEncoder.ArrayEncoding
|
|
|
893 |
private let keyEncoding: URLEncodedFormEncoder.KeyEncoding
|
|
|
894 |
private let spaceEncoding: URLEncodedFormEncoder.SpaceEncoding
|
|
|
895 |
private let allowedCharacters: CharacterSet
|
|
|
896 |
|
|
|
897 |
init(alphabetizeKeyValuePairs: Bool,
|
|
|
898 |
arrayEncoding: URLEncodedFormEncoder.ArrayEncoding,
|
|
|
899 |
keyEncoding: URLEncodedFormEncoder.KeyEncoding,
|
|
|
900 |
spaceEncoding: URLEncodedFormEncoder.SpaceEncoding,
|
|
|
901 |
allowedCharacters: CharacterSet) {
|
|
|
902 |
self.alphabetizeKeyValuePairs = alphabetizeKeyValuePairs
|
|
|
903 |
self.arrayEncoding = arrayEncoding
|
|
|
904 |
self.keyEncoding = keyEncoding
|
|
|
905 |
self.spaceEncoding = spaceEncoding
|
|
|
906 |
self.allowedCharacters = allowedCharacters
|
|
|
907 |
}
|
|
|
908 |
|
|
|
909 |
func serialize(_ object: URLEncodedFormComponent.Object) -> String {
|
|
|
910 |
var output: [String] = []
|
|
|
911 |
for (key, component) in object {
|
|
|
912 |
let value = serialize(component, forKey: key)
|
|
|
913 |
output.append(value)
|
|
|
914 |
}
|
|
|
915 |
output = alphabetizeKeyValuePairs ? output.sorted() : output
|
|
|
916 |
|
|
|
917 |
return output.joinedWithAmpersands()
|
|
|
918 |
}
|
|
|
919 |
|
|
|
920 |
func serialize(_ component: URLEncodedFormComponent, forKey key: String) -> String {
|
|
|
921 |
switch component {
|
|
|
922 |
case let .string(string): return "\(escape(keyEncoding.encode(key)))=\(escape(string))"
|
|
|
923 |
case let .array(array): return serialize(array, forKey: key)
|
|
|
924 |
case let .object(object): return serialize(object, forKey: key)
|
|
|
925 |
}
|
|
|
926 |
}
|
|
|
927 |
|
|
|
928 |
func serialize(_ object: URLEncodedFormComponent.Object, forKey key: String) -> String {
|
|
|
929 |
var segments: [String] = object.map { subKey, value in
|
|
|
930 |
let keyPath = "[\(subKey)]"
|
|
|
931 |
return serialize(value, forKey: key + keyPath)
|
|
|
932 |
}
|
|
|
933 |
segments = alphabetizeKeyValuePairs ? segments.sorted() : segments
|
|
|
934 |
|
|
|
935 |
return segments.joinedWithAmpersands()
|
|
|
936 |
}
|
|
|
937 |
|
|
|
938 |
func serialize(_ array: [URLEncodedFormComponent], forKey key: String) -> String {
|
|
|
939 |
var segments: [String] = array.enumerated().map { index, component in
|
|
|
940 |
let keyPath = arrayEncoding.encode(key, atIndex: index)
|
|
|
941 |
return serialize(component, forKey: keyPath)
|
|
|
942 |
}
|
|
|
943 |
segments = alphabetizeKeyValuePairs ? segments.sorted() : segments
|
|
|
944 |
|
|
|
945 |
return segments.joinedWithAmpersands()
|
|
|
946 |
}
|
|
|
947 |
|
|
|
948 |
func escape(_ query: String) -> String {
|
|
|
949 |
var allowedCharactersWithSpace = allowedCharacters
|
|
|
950 |
allowedCharactersWithSpace.insert(charactersIn: " ")
|
|
|
951 |
let escapedQuery = query.addingPercentEncoding(withAllowedCharacters: allowedCharactersWithSpace) ?? query
|
|
|
952 |
let spaceEncodedQuery = spaceEncoding.encode(escapedQuery)
|
|
|
953 |
|
|
|
954 |
return spaceEncodedQuery
|
|
|
955 |
}
|
|
|
956 |
}
|
|
|
957 |
|
|
|
958 |
extension Array where Element == String {
|
|
|
959 |
func joinedWithAmpersands() -> String {
|
|
|
960 |
joined(separator: "&")
|
|
|
961 |
}
|
|
|
962 |
}
|
|
|
963 |
|
|
|
964 |
extension CharacterSet {
|
|
|
965 |
/// Creates a CharacterSet from RFC 3986 allowed characters.
|
|
|
966 |
///
|
|
|
967 |
/// RFC 3986 states that the following characters are "reserved" characters.
|
|
|
968 |
///
|
|
|
969 |
/// - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
|
|
|
970 |
/// - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
|
|
|
971 |
///
|
|
|
972 |
/// In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
|
|
|
973 |
/// query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
|
|
|
974 |
/// should be percent-escaped in the query string.
|
|
|
975 |
public static let afURLQueryAllowed: CharacterSet = {
|
|
|
976 |
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
|
|
|
977 |
let subDelimitersToEncode = "!$&'()*+,;="
|
|
|
978 |
let encodableDelimiters = CharacterSet(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
|
|
|
979 |
|
|
|
980 |
return CharacterSet.urlQueryAllowed.subtracting(encodableDelimiters)
|
|
|
981 |
}()
|
|
|
982 |
}
|