| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211 |
- //
- // 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 = (Foundation.URLRequest?, HTTPURLResponse) -> 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.addOperation {
- if let response = self.response, 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: Sequence where S.Iterator.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)"
- let error = NSError(
- domain: Error.Domain,
- code: Error.Code.statusCodeValidationFailed.rawValue,
- userInfo: [
- NSLocalizedFailureReasonErrorKey: failureReason,
- Error.UserInfoKeys.StatusCode: response.statusCode
- ]
- )
- return .failure(error)
- }
- }
- }
- // MARK: - Content-Type
- private struct MIMEType {
- let type: String
- let subtype: String
- init?(_ string: String) {
- let components: [String] = {
- let stripped = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
- let split = stripped.substring(to: stripped.range(of: ";")?.lowerBound ?? stripped.endIndex)
- return split.components(separatedBy: "/")
- }()
- if let type = components.first,
- let 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 : Sequence where S.Iterator.Element == String>(contentType acceptableContentTypes: S) -> Self {
- return validate { _, response in
- guard let validData = self.delegate.data, validData.count > 0 else { return .success }
- if let responseContentType = response.mimeType,
- let responseMIMEType = MIMEType(responseContentType)
- {
- for contentType in acceptableContentTypes {
- if let acceptableMIMEType = MIMEType(contentType), acceptableMIMEType.matches(responseMIMEType) {
- return .success
- }
- }
- } else {
- for contentType in acceptableContentTypes {
- if let mimeType = MIMEType(contentType), mimeType.type == "*" && mimeType.subtype == "*" {
- return .success
- }
- }
- }
- let contentType: String
- let failureReason: String
- if let responseContentType = response.mimeType {
- contentType = responseContentType
- failureReason = (
- "Response content type \"\(responseContentType)\" does not match any acceptable " +
- "content types: \(acceptableContentTypes)"
- )
- } else {
- contentType = ""
- failureReason = "Response content type was missing and acceptable content type does not match \"*/*\""
- }
- let error = NSError(
- domain: Error.Domain,
- code: Error.Code.contentTypeValidationFailed.rawValue,
- userInfo: [
- NSLocalizedFailureReasonErrorKey: failureReason,
- Error.UserInfoKeys.ContentType: contentType
- ]
- )
- return .failure(error)
- }
- }
- // 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: CountableRange<Int> = 200..<300
- let acceptableContentTypes: [String] = {
- if let accept = request?.value(forHTTPHeaderField: "Accept") {
- return accept.components(separatedBy: ",")
- }
- return ["*/*"]
- }()
- return validate(statusCode: acceptableStatusCodes).validate(contentType: acceptableContentTypes)
- }
- }
|