Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  Response.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
/// Default type of `DataResponse` returned by Alamofire, with an `AFError` `Failure` type.
28
public typealias AFDataResponse<Success> = DataResponse<Success, AFError>
29
/// Default type of `DownloadResponse` returned by Alamofire, with an `AFError` `Failure` type.
30
public typealias AFDownloadResponse<Success> = DownloadResponse<Success, AFError>
31
 
32
/// Type used to store all values associated with a serialized response of a `DataRequest` or `UploadRequest`.
33
public struct DataResponse<Success, Failure: Error> {
34
    /// The URL request sent to the server.
35
    public let request: URLRequest?
36
 
37
    /// The server's response to the URL request.
38
    public let response: HTTPURLResponse?
39
 
40
    /// The data returned by the server.
41
    public let data: Data?
42
 
43
    /// The final metrics of the response.
44
    ///
45
    /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.`
46
    ///
47
    public let metrics: URLSessionTaskMetrics?
48
 
49
    /// The time taken to serialize the response.
50
    public let serializationDuration: TimeInterval
51
 
52
    /// The result of response serialization.
53
    public let result: Result<Success, Failure>
54
 
55
    /// Returns the associated value of the result if it is a success, `nil` otherwise.
56
    public var value: Success? { result.success }
57
 
58
    /// Returns the associated error value if the result if it is a failure, `nil` otherwise.
59
    public var error: Failure? { result.failure }
60
 
61
    /// Creates a `DataResponse` instance with the specified parameters derived from the response serialization.
62
    ///
63
    /// - Parameters:
64
    ///   - request:               The `URLRequest` sent to the server.
65
    ///   - response:              The `HTTPURLResponse` from the server.
66
    ///   - data:                  The `Data` returned by the server.
67
    ///   - metrics:               The `URLSessionTaskMetrics` of the `DataRequest` or `UploadRequest`.
68
    ///   - serializationDuration: The duration taken by serialization.
69
    ///   - result:                The `Result` of response serialization.
70
    public init(request: URLRequest?,
71
                response: HTTPURLResponse?,
72
                data: Data?,
73
                metrics: URLSessionTaskMetrics?,
74
                serializationDuration: TimeInterval,
75
                result: Result<Success, Failure>) {
76
        self.request = request
77
        self.response = response
78
        self.data = data
79
        self.metrics = metrics
80
        self.serializationDuration = serializationDuration
81
        self.result = result
82
    }
83
}
84
 
85
// MARK: -
86
 
87
extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible {
88
    /// The textual representation used when written to an output stream, which includes whether the result was a
89
    /// success or failure.
90
    public var description: String {
91
        "\(result)"
92
    }
93
 
94
    /// The debug textual representation used when written to an output stream, which includes (if available) a summary
95
    /// of the `URLRequest`, the request's headers and body (if decodable as a `String` below 100KB); the
96
    /// `HTTPURLResponse`'s status code, headers, and body; the duration of the network and serialization actions; and
97
    /// the `Result` of serialization.
98
    public var debugDescription: String {
99
        guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" }
100
 
101
        let requestDescription = DebugDescription.description(of: urlRequest)
102
 
103
        let responseDescription = response.map { response in
104
            let responseBodyDescription = DebugDescription.description(for: data, headers: response.headers)
105
 
106
            return """
107
            \(DebugDescription.description(of: response))
108
                \(responseBodyDescription.indentingNewlines())
109
            """
110
        } ?? "[Response]: None"
111
 
112
        let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None"
113
 
114
        return """
115
        \(requestDescription)
116
        \(responseDescription)
117
        [Network Duration]: \(networkDuration)
118
        [Serialization Duration]: \(serializationDuration)s
119
        [Result]: \(result)
120
        """
121
    }
122
}
123
 
124
// MARK: -
125
 
126
extension DataResponse {
127
    /// Evaluates the specified closure when the result of this `DataResponse` is a success, passing the unwrapped
128
    /// result value as a parameter.
129
    ///
130
    /// Use the `map` method with a closure that does not throw. For example:
131
    ///
132
    ///     let possibleData: DataResponse<Data> = ...
133
    ///     let possibleInt = possibleData.map { $0.count }
134
    ///
135
    /// - parameter transform: A closure that takes the success value of the instance's result.
136
    ///
137
    /// - returns: A `DataResponse` whose result wraps the value returned by the given closure. If this instance's
138
    ///            result is a failure, returns a response wrapping the same failure.
139
    public func map<NewSuccess>(_ transform: (Success) -> NewSuccess) -> DataResponse<NewSuccess, Failure> {
140
        DataResponse<NewSuccess, Failure>(request: request,
141
                                          response: response,
142
                                          data: data,
143
                                          metrics: metrics,
144
                                          serializationDuration: serializationDuration,
145
                                          result: result.map(transform))
146
    }
147
 
148
    /// Evaluates the given closure when the result of this `DataResponse` is a success, passing the unwrapped result
149
    /// value as a parameter.
150
    ///
151
    /// Use the `tryMap` method with a closure that may throw an error. For example:
152
    ///
153
    ///     let possibleData: DataResponse<Data> = ...
154
    ///     let possibleObject = possibleData.tryMap {
155
    ///         try JSONSerialization.jsonObject(with: $0)
156
    ///     }
157
    ///
158
    /// - parameter transform: A closure that takes the success value of the instance's result.
159
    ///
160
    /// - returns: A success or failure `DataResponse` depending on the result of the given closure. If this instance's
161
    ///            result is a failure, returns the same failure.
162
    public func tryMap<NewSuccess>(_ transform: (Success) throws -> NewSuccess) -> DataResponse<NewSuccess, Error> {
163
        DataResponse<NewSuccess, Error>(request: request,
164
                                        response: response,
165
                                        data: data,
166
                                        metrics: metrics,
167
                                        serializationDuration: serializationDuration,
168
                                        result: result.tryMap(transform))
169
    }
170
 
171
    /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter.
172
    ///
173
    /// Use the `mapError` function with a closure that does not throw. For example:
174
    ///
175
    ///     let possibleData: DataResponse<Data> = ...
176
    ///     let withMyError = possibleData.mapError { MyError.error($0) }
177
    ///
178
    /// - Parameter transform: A closure that takes the error of the instance.
179
    ///
180
    /// - Returns: A `DataResponse` instance containing the result of the transform.
181
    public func mapError<NewFailure: Error>(_ transform: (Failure) -> NewFailure) -> DataResponse<Success, NewFailure> {
182
        DataResponse<Success, NewFailure>(request: request,
183
                                          response: response,
184
                                          data: data,
185
                                          metrics: metrics,
186
                                          serializationDuration: serializationDuration,
187
                                          result: result.mapError(transform))
188
    }
189
 
190
    /// Evaluates the specified closure when the `DataResponse` is a failure, passing the unwrapped error as a parameter.
191
    ///
192
    /// Use the `tryMapError` function with a closure that may throw an error. For example:
193
    ///
194
    ///     let possibleData: DataResponse<Data> = ...
195
    ///     let possibleObject = possibleData.tryMapError {
196
    ///         try someFailableFunction(taking: $0)
197
    ///     }
198
    ///
199
    /// - Parameter transform: A throwing closure that takes the error of the instance.
200
    ///
201
    /// - Returns: A `DataResponse` instance containing the result of the transform.
202
    public func tryMapError<NewFailure: Error>(_ transform: (Failure) throws -> NewFailure) -> DataResponse<Success, Error> {
203
        DataResponse<Success, Error>(request: request,
204
                                     response: response,
205
                                     data: data,
206
                                     metrics: metrics,
207
                                     serializationDuration: serializationDuration,
208
                                     result: result.tryMapError(transform))
209
    }
210
}
211
 
212
// MARK: -
213
 
214
/// Used to store all data associated with a serialized response of a download request.
215
public struct DownloadResponse<Success, Failure: Error> {
216
    /// The URL request sent to the server.
217
    public let request: URLRequest?
218
 
219
    /// The server's response to the URL request.
220
    public let response: HTTPURLResponse?
221
 
222
    /// The final destination URL of the data returned from the server after it is moved.
223
    public let fileURL: URL?
224
 
225
    /// The resume data generated if the request was cancelled.
226
    public let resumeData: Data?
227
 
228
    /// The final metrics of the response.
229
    ///
230
    /// - Note: Due to `FB7624529`, collection of `URLSessionTaskMetrics` on watchOS is currently disabled.`
231
    ///
232
    public let metrics: URLSessionTaskMetrics?
233
 
234
    /// The time taken to serialize the response.
235
    public let serializationDuration: TimeInterval
236
 
237
    /// The result of response serialization.
238
    public let result: Result<Success, Failure>
239
 
240
    /// Returns the associated value of the result if it is a success, `nil` otherwise.
241
    public var value: Success? { result.success }
242
 
243
    /// Returns the associated error value if the result if it is a failure, `nil` otherwise.
244
    public var error: Failure? { result.failure }
245
 
246
    /// Creates a `DownloadResponse` instance with the specified parameters derived from response serialization.
247
    ///
248
    /// - Parameters:
249
    ///   - request:               The `URLRequest` sent to the server.
250
    ///   - response:              The `HTTPURLResponse` from the server.
251
    ///   - fileURL:               The final destination URL of the data returned from the server after it is moved.
252
    ///   - resumeData:            The resume `Data` generated if the request was cancelled.
253
    ///   - metrics:               The `URLSessionTaskMetrics` of the `DownloadRequest`.
254
    ///   - serializationDuration: The duration taken by serialization.
255
    ///   - result:                The `Result` of response serialization.
256
    public init(request: URLRequest?,
257
                response: HTTPURLResponse?,
258
                fileURL: URL?,
259
                resumeData: Data?,
260
                metrics: URLSessionTaskMetrics?,
261
                serializationDuration: TimeInterval,
262
                result: Result<Success, Failure>) {
263
        self.request = request
264
        self.response = response
265
        self.fileURL = fileURL
266
        self.resumeData = resumeData
267
        self.metrics = metrics
268
        self.serializationDuration = serializationDuration
269
        self.result = result
270
    }
271
}
272
 
273
// MARK: -
274
 
275
extension DownloadResponse: CustomStringConvertible, CustomDebugStringConvertible {
276
    /// The textual representation used when written to an output stream, which includes whether the result was a
277
    /// success or failure.
278
    public var description: String {
279
        "\(result)"
280
    }
281
 
282
    /// The debug textual representation used when written to an output stream, which includes the URL request, the URL
283
    /// response, the temporary and destination URLs, the resume data, the durations of the network and serialization
284
    /// actions, and the response serialization result.
285
    public var debugDescription: String {
286
        guard let urlRequest = request else { return "[Request]: None\n[Result]: \(result)" }
287
 
288
        let requestDescription = DebugDescription.description(of: urlRequest)
289
        let responseDescription = response.map(DebugDescription.description(of:)) ?? "[Response]: None"
290
        let networkDuration = metrics.map { "\($0.taskInterval.duration)s" } ?? "None"
291
        let resumeDataDescription = resumeData.map { "\($0)" } ?? "None"
292
 
293
        return """
294
        \(requestDescription)
295
        \(responseDescription)
296
        [File URL]: \(fileURL?.path ?? "None")
297
        [Resume Data]: \(resumeDataDescription)
298
        [Network Duration]: \(networkDuration)
299
        [Serialization Duration]: \(serializationDuration)s
300
        [Result]: \(result)
301
        """
302
    }
303
}
304
 
305
// MARK: -
306
 
307
extension DownloadResponse {
308
    /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped
309
    /// result value as a parameter.
310
    ///
311
    /// Use the `map` method with a closure that does not throw. For example:
312
    ///
313
    ///     let possibleData: DownloadResponse<Data> = ...
314
    ///     let possibleInt = possibleData.map { $0.count }
315
    ///
316
    /// - parameter transform: A closure that takes the success value of the instance's result.
317
    ///
318
    /// - returns: A `DownloadResponse` whose result wraps the value returned by the given closure. If this instance's
319
    ///            result is a failure, returns a response wrapping the same failure.
320
    public func map<NewSuccess>(_ transform: (Success) -> NewSuccess) -> DownloadResponse<NewSuccess, Failure> {
321
        DownloadResponse<NewSuccess, Failure>(request: request,
322
                                              response: response,
323
                                              fileURL: fileURL,
324
                                              resumeData: resumeData,
325
                                              metrics: metrics,
326
                                              serializationDuration: serializationDuration,
327
                                              result: result.map(transform))
328
    }
329
 
330
    /// Evaluates the given closure when the result of this `DownloadResponse` is a success, passing the unwrapped
331
    /// result value as a parameter.
332
    ///
333
    /// Use the `tryMap` method with a closure that may throw an error. For example:
334
    ///
335
    ///     let possibleData: DownloadResponse<Data> = ...
336
    ///     let possibleObject = possibleData.tryMap {
337
    ///         try JSONSerialization.jsonObject(with: $0)
338
    ///     }
339
    ///
340
    /// - parameter transform: A closure that takes the success value of the instance's result.
341
    ///
342
    /// - returns: A success or failure `DownloadResponse` depending on the result of the given closure. If this
343
    /// instance's result is a failure, returns the same failure.
344
    public func tryMap<NewSuccess>(_ transform: (Success) throws -> NewSuccess) -> DownloadResponse<NewSuccess, Error> {
345
        DownloadResponse<NewSuccess, Error>(request: request,
346
                                            response: response,
347
                                            fileURL: fileURL,
348
                                            resumeData: resumeData,
349
                                            metrics: metrics,
350
                                            serializationDuration: serializationDuration,
351
                                            result: result.tryMap(transform))
352
    }
353
 
354
    /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter.
355
    ///
356
    /// Use the `mapError` function with a closure that does not throw. For example:
357
    ///
358
    ///     let possibleData: DownloadResponse<Data> = ...
359
    ///     let withMyError = possibleData.mapError { MyError.error($0) }
360
    ///
361
    /// - Parameter transform: A closure that takes the error of the instance.
362
    ///
363
    /// - Returns: A `DownloadResponse` instance containing the result of the transform.
364
    public func mapError<NewFailure: Error>(_ transform: (Failure) -> NewFailure) -> DownloadResponse<Success, NewFailure> {
365
        DownloadResponse<Success, NewFailure>(request: request,
366
                                              response: response,
367
                                              fileURL: fileURL,
368
                                              resumeData: resumeData,
369
                                              metrics: metrics,
370
                                              serializationDuration: serializationDuration,
371
                                              result: result.mapError(transform))
372
    }
373
 
374
    /// Evaluates the specified closure when the `DownloadResponse` is a failure, passing the unwrapped error as a parameter.
375
    ///
376
    /// Use the `tryMapError` function with a closure that may throw an error. For example:
377
    ///
378
    ///     let possibleData: DownloadResponse<Data> = ...
379
    ///     let possibleObject = possibleData.tryMapError {
380
    ///         try someFailableFunction(taking: $0)
381
    ///     }
382
    ///
383
    /// - Parameter transform: A throwing closure that takes the error of the instance.
384
    ///
385
    /// - Returns: A `DownloadResponse` instance containing the result of the transform.
386
    public func tryMapError<NewFailure: Error>(_ transform: (Failure) throws -> NewFailure) -> DownloadResponse<Success, Error> {
387
        DownloadResponse<Success, Error>(request: request,
388
                                         response: response,
389
                                         fileURL: fileURL,
390
                                         resumeData: resumeData,
391
                                         metrics: metrics,
392
                                         serializationDuration: serializationDuration,
393
                                         result: result.tryMapError(transform))
394
    }
395
}
396
 
397
private enum DebugDescription {
398
    static func description(of request: URLRequest) -> String {
399
        let requestSummary = "\(request.httpMethod!) \(request)"
400
        let requestHeadersDescription = DebugDescription.description(for: request.headers)
401
        let requestBodyDescription = DebugDescription.description(for: request.httpBody, headers: request.headers)
402
 
403
        return """
404
        [Request]: \(requestSummary)
405
            \(requestHeadersDescription.indentingNewlines())
406
            \(requestBodyDescription.indentingNewlines())
407
        """
408
    }
409
 
410
    static func description(of response: HTTPURLResponse) -> String {
411
        """
412
        [Response]:
413
            [Status Code]: \(response.statusCode)
414
            \(DebugDescription.description(for: response.headers).indentingNewlines())
415
        """
416
    }
417
 
418
    static func description(for headers: HTTPHeaders) -> String {
419
        guard !headers.isEmpty else { return "[Headers]: None" }
420
 
421
        let headerDescription = "\(headers.sorted())".indentingNewlines()
422
        return """
423
        [Headers]:
424
            \(headerDescription)
425
        """
426
    }
427
 
428
    static func description(for data: Data?,
429
                            headers: HTTPHeaders,
430
                            allowingPrintableTypes printableTypes: [String] = ["json", "xml", "text"],
431
                            maximumLength: Int = 100_000) -> String {
432
        guard let data = data, !data.isEmpty else { return "[Body]: None" }
433
 
434
        guard
435
            data.count <= maximumLength,
436
            printableTypes.compactMap({ headers["Content-Type"]?.contains($0) }).contains(true)
437
        else { return "[Body]: \(data.count) bytes" }
438
 
439
        return """
440
        [Body]:
441
            \(String(decoding: data, as: UTF8.self)
442
            .trimmingCharacters(in: .whitespacesAndNewlines)
443
            .indentingNewlines())
444
        """
445
    }
446
}
447
 
448
extension String {
449
    fileprivate func indentingNewlines(by spaceCount: Int = 4) -> String {
450
        let spaces = String(repeating: " ", count: spaceCount)
451
        return replacingOccurrences(of: "\n", with: "\n\(spaces)")
452
    }
453
}