| // Validation.swift |
| // |
| // Copyright (c) 2014–2016 Alamofire Software Foundation (http://alamofire.org/) |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| // THE SOFTWARE. |
| |
| import Foundation |
| |
| extension Request { |
| |
| /** |
| Used to represent whether validation was successful or encountered an error resulting in a failure. |
| |
| - Success: The validation was successful. |
| - Failure: The validation failed encountering the provided error. |
| */ |
| public enum ValidationResult { |
| case Success |
| case Failure(NSError) |
| } |
| |
| /** |
| A closure used to validate a request that takes a URL request and URL response, and returns whether the |
| request was valid. |
| */ |
| public typealias Validation = (NSURLRequest?, NSHTTPURLResponse) -> ValidationResult |
| |
| /** |
| Validates the request, using the specified closure. |
| |
| If validation fails, subsequent calls to response handlers will have an associated error. |
| |
| - parameter validation: A closure to validate the request. |
| |
| - returns: The request. |
| */ |
| public func validate(validation: Validation) -> Self { |
| delegate.queue.addOperationWithBlock { |
| if let |
| response = self.response where self.delegate.error == nil, |
| case let .Failure(error) = validation(self.request, response) |
| { |
| self.delegate.error = error |
| } |
| } |
| |
| return self |
| } |
| |
| // MARK: - Status Code |
| |
| /** |
| Validates that the response has a status code in the specified range. |
| |
| If validation fails, subsequent calls to response handlers will have an associated error. |
| |
| - parameter range: The range of acceptable status codes. |
| |
| - returns: The request. |
| */ |
| public func validate<S: SequenceType where S.Generator.Element == Int>(statusCode acceptableStatusCode: S) -> Self { |
| return validate { _, response in |
| if acceptableStatusCode.contains(response.statusCode) { |
| return .Success |
| } else { |
| let failureReason = "Response status code was unacceptable: \(response.statusCode)" |
| return .Failure(Error.errorWithCode(.StatusCodeValidationFailed, failureReason: failureReason)) |
| } |
| } |
| } |
| |
| // MARK: - Content-Type |
| |
| private struct MIMEType { |
| let type: String |
| let subtype: String |
| |
| init?(_ string: String) { |
| let components: [String] = { |
| let stripped = string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) |
| let split = stripped.substringToIndex(stripped.rangeOfString(";")?.startIndex ?? stripped.endIndex) |
| return split.componentsSeparatedByString("/") |
| }() |
| |
| if let |
| type = components.first, |
| subtype = components.last |
| { |
| self.type = type |
| self.subtype = subtype |
| } else { |
| return nil |
| } |
| } |
| |
| func matches(MIME: MIMEType) -> Bool { |
| switch (type, subtype) { |
| case (MIME.type, MIME.subtype), (MIME.type, "*"), ("*", MIME.subtype), ("*", "*"): |
| return true |
| default: |
| return false |
| } |
| } |
| } |
| |
| /** |
| Validates that the response has a content type in the specified array. |
| |
| If validation fails, subsequent calls to response handlers will have an associated error. |
| |
| - parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes. |
| |
| - returns: The request. |
| */ |
| public func validate<S : SequenceType where S.Generator.Element == String>(contentType acceptableContentTypes: S) -> Self { |
| return validate { _, response in |
| guard let validData = self.delegate.data where validData.length > 0 else { return .Success } |
| |
| if let |
| responseContentType = response.MIMEType, |
| responseMIMEType = MIMEType(responseContentType) |
| { |
| for contentType in acceptableContentTypes { |
| if let acceptableMIMEType = MIMEType(contentType) where acceptableMIMEType.matches(responseMIMEType) { |
| return .Success |
| } |
| } |
| } else { |
| for contentType in acceptableContentTypes { |
| if let MIMEType = MIMEType(contentType) where MIMEType.type == "*" && MIMEType.subtype == "*" { |
| return .Success |
| } |
| } |
| } |
| |
| let failureReason: String |
| |
| if let responseContentType = response.MIMEType { |
| failureReason = ( |
| "Response content type \"\(responseContentType)\" does not match any acceptable " + |
| "content types: \(acceptableContentTypes)" |
| ) |
| } else { |
| failureReason = "Response content type was missing and acceptable content type does not match \"*/*\"" |
| } |
| |
| return .Failure(Error.errorWithCode(.ContentTypeValidationFailed, failureReason: failureReason)) |
| } |
| } |
| |
| // MARK: - Automatic |
| |
| /** |
| Validates that the response has a status code in the default acceptable range of 200...299, and that the content |
| type matches any specified in the Accept HTTP header field. |
| |
| If validation fails, subsequent calls to response handlers will have an associated error. |
| |
| - returns: The request. |
| */ |
| public func validate() -> Self { |
| let acceptableStatusCodes: Range<Int> = 200..<300 |
| let acceptableContentTypes: [String] = { |
| if let accept = request?.valueForHTTPHeaderField("Accept") { |
| return accept.componentsSeparatedByString(",") |
| } |
| |
| return ["*/*"] |
| }() |
| |
| return validate(statusCode: acceptableStatusCodes).validate(contentType: acceptableContentTypes) |
| } |
| } |