Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
//
2
//  Validation.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
extension Request {
28
    // MARK: Helper Types
29
 
30
    fileprivate typealias ErrorReason = AFError.ResponseValidationFailureReason
31
 
32
    /// Used to represent whether a validation succeeded or failed.
33
    public typealias ValidationResult = Result<Void, Error>
34
 
35
    fileprivate struct MIMEType {
36
        let type: String
37
        let subtype: String
38
 
39
        var isWildcard: Bool { type == "*" && subtype == "*" }
40
 
41
        init?(_ string: String) {
42
            let components: [String] = {
43
                let stripped = string.trimmingCharacters(in: .whitespacesAndNewlines)
44
                let split = stripped[..<(stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)]
45
 
46
                return split.components(separatedBy: "/")
47
            }()
48
 
49
            if let type = components.first, let subtype = components.last {
50
                self.type = type
51
                self.subtype = subtype
52
            } else {
53
                return nil
54
            }
55
        }
56
 
57
        func matches(_ mime: MIMEType) -> Bool {
58
            switch (type, subtype) {
59
            case (mime.type, mime.subtype), (mime.type, "*"), ("*", mime.subtype), ("*", "*"):
60
                return true
61
            default:
62
                return false
63
            }
64
        }
65
    }
66
 
67
    // MARK: Properties
68
 
69
    fileprivate var acceptableStatusCodes: Range<Int> { 200..<300 }
70
 
71
    fileprivate var acceptableContentTypes: [String] {
72
        if let accept = request?.value(forHTTPHeaderField: "Accept") {
73
            return accept.components(separatedBy: ",")
74
        }
75
 
76
        return ["*/*"]
77
    }
78
 
79
    // MARK: Status Code
80
 
81
    fileprivate func validate<S: Sequence>(statusCode acceptableStatusCodes: S,
82
                                           response: HTTPURLResponse)
83
        -> ValidationResult
84
        where S.Iterator.Element == Int {
85
        if acceptableStatusCodes.contains(response.statusCode) {
86
            return .success(())
87
        } else {
88
            let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
89
            return .failure(AFError.responseValidationFailed(reason: reason))
90
        }
91
    }
92
 
93
    // MARK: Content Type
94
 
95
    fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
96
                                           response: HTTPURLResponse,
97
                                           data: Data?)
98
        -> ValidationResult
99
        where S.Iterator.Element == String {
100
        guard let data = data, !data.isEmpty else { return .success(()) }
101
 
102
        return validate(contentType: acceptableContentTypes, response: response)
103
    }
104
 
105
    fileprivate func validate<S: Sequence>(contentType acceptableContentTypes: S,
106
                                           response: HTTPURLResponse)
107
        -> ValidationResult
108
        where S.Iterator.Element == String {
109
        guard
110
            let responseContentType = response.mimeType,
111
            let responseMIMEType = MIMEType(responseContentType)
112
        else {
113
            for contentType in acceptableContentTypes {
114
                if let mimeType = MIMEType(contentType), mimeType.isWildcard {
115
                    return .success(())
116
                }
117
            }
118
 
119
            let error: AFError = {
120
                let reason: ErrorReason = .missingContentType(acceptableContentTypes: acceptableContentTypes.sorted())
121
                return AFError.responseValidationFailed(reason: reason)
122
            }()
123
 
124
            return .failure(error)
125
        }
126
 
127
        for contentType in acceptableContentTypes {
128
            if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
129
                return .success(())
130
            }
131
        }
132
 
133
        let error: AFError = {
134
            let reason: ErrorReason = .unacceptableContentType(acceptableContentTypes: acceptableContentTypes.sorted(),
135
                                                               responseContentType: responseContentType)
136
 
137
            return AFError.responseValidationFailed(reason: reason)
138
        }()
139
 
140
        return .failure(error)
141
    }
142
}
143
 
144
// MARK: -
145
 
146
extension DataRequest {
147
    /// A closure used to validate a request that takes a URL request, a URL response and data, and returns whether the
148
    /// request was valid.
149
    public typealias Validation = (URLRequest?, HTTPURLResponse, Data?) -> ValidationResult
150
 
151
    /// Validates that the response has a status code in the specified sequence.
152
    ///
153
    /// If validation fails, subsequent calls to response handlers will have an associated error.
154
    ///
155
    /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes.
156
    ///
157
    /// - Returns:                         The instance.
158
    @discardableResult
159
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
160
        validate { [unowned self] _, response, _ in
161
            self.validate(statusCode: acceptableStatusCodes, response: response)
162
        }
163
    }
164
 
165
    /// Validates that the response has a content type in the specified sequence.
166
    ///
167
    /// If validation fails, subsequent calls to response handlers will have an associated error.
168
    ///
169
    /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
170
    ///
171
    /// - returns: The request.
172
    @discardableResult
173
    public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
174
        validate { [unowned self] _, response, data in
175
            self.validate(contentType: acceptableContentTypes(), response: response, data: data)
176
        }
177
    }
178
 
179
    /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
180
    /// type matches any specified in the Accept HTTP header field.
181
    ///
182
    /// If validation fails, subsequent calls to response handlers will have an associated error.
183
    ///
184
    /// - returns: The request.
185
    @discardableResult
186
    public func validate() -> Self {
187
        let contentTypes: () -> [String] = { [unowned self] in
188
            self.acceptableContentTypes
189
        }
190
        return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
191
    }
192
}
193
 
194
extension DataStreamRequest {
195
    /// A closure used to validate a request that takes a `URLRequest` and `HTTPURLResponse` and returns whether the
196
    /// request was valid.
197
    public typealias Validation = (_ request: URLRequest?, _ response: HTTPURLResponse) -> ValidationResult
198
 
199
    /// Validates that the response has a status code in the specified sequence.
200
    ///
201
    /// If validation fails, subsequent calls to response handlers will have an associated error.
202
    ///
203
    /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes.
204
    ///
205
    /// - Returns:                         The instance.
206
    @discardableResult
207
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
208
        validate { [unowned self] _, response in
209
            self.validate(statusCode: acceptableStatusCodes, response: response)
210
        }
211
    }
212
 
213
    /// Validates that the response has a content type in the specified sequence.
214
    ///
215
    /// If validation fails, subsequent calls to response handlers will have an associated error.
216
    ///
217
    /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
218
    ///
219
    /// - returns: The request.
220
    @discardableResult
221
    public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
222
        validate { [unowned self] _, response in
223
            self.validate(contentType: acceptableContentTypes(), response: response)
224
        }
225
    }
226
 
227
    /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
228
    /// type matches any specified in the Accept HTTP header field.
229
    ///
230
    /// If validation fails, subsequent calls to response handlers will have an associated error.
231
    ///
232
    /// - Returns: The instance.
233
    @discardableResult
234
    public func validate() -> Self {
235
        let contentTypes: () -> [String] = { [unowned self] in
236
            self.acceptableContentTypes
237
        }
238
        return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
239
    }
240
}
241
 
242
// MARK: -
243
 
244
extension DownloadRequest {
245
    /// A closure used to validate a request that takes a URL request, a URL response, a temporary URL and a
246
    /// destination URL, and returns whether the request was valid.
247
    public typealias Validation = (_ request: URLRequest?,
248
                                   _ response: HTTPURLResponse,
249
                                   _ fileURL: URL?)
250
        -> ValidationResult
251
 
252
    /// Validates that the response has a status code in the specified sequence.
253
    ///
254
    /// If validation fails, subsequent calls to response handlers will have an associated error.
255
    ///
256
    /// - Parameter acceptableStatusCodes: `Sequence` of acceptable response status codes.
257
    ///
258
    /// - Returns:                         The instance.
259
    @discardableResult
260
    public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {
261
        validate { [unowned self] _, response, _ in
262
            self.validate(statusCode: acceptableStatusCodes, response: response)
263
        }
264
    }
265
 
266
    /// Validates that the response has a content type in the specified sequence.
267
    ///
268
    /// If validation fails, subsequent calls to response handlers will have an associated error.
269
    ///
270
    /// - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
271
    ///
272
    /// - returns: The request.
273
    @discardableResult
274
    public func validate<S: Sequence>(contentType acceptableContentTypes: @escaping @autoclosure () -> S) -> Self where S.Iterator.Element == String {
275
        validate { [unowned self] _, response, fileURL in
276
            guard let validFileURL = fileURL else {
277
                return .failure(AFError.responseValidationFailed(reason: .dataFileNil))
278
            }
279
 
280
            do {
281
                let data = try Data(contentsOf: validFileURL)
282
                return self.validate(contentType: acceptableContentTypes(), response: response, data: data)
283
            } catch {
284
                return .failure(AFError.responseValidationFailed(reason: .dataFileReadFailed(at: validFileURL)))
285
            }
286
        }
287
    }
288
 
289
    /// Validates that the response has a status code in the default acceptable range of 200...299, and that the content
290
    /// type matches any specified in the Accept HTTP header field.
291
    ///
292
    /// If validation fails, subsequent calls to response handlers will have an associated error.
293
    ///
294
    /// - returns: The request.
295
    @discardableResult
296
    public func validate() -> Self {
297
        let contentTypes = { [unowned self] in
298
            self.acceptableContentTypes
299
        }
300
        return validate(statusCode: acceptableStatusCodes).validate(contentType: contentTypes())
301
    }
302
}