Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alamofire 5: Async RequestAdapter #2628

Merged
merged 11 commits into from Nov 22, 2018
20 changes: 9 additions & 11 deletions Alamofire.xcodeproj/project.pbxproj
Expand Up @@ -49,9 +49,9 @@
3111CE9220A7EC30008315E2 /* ResponseSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */; };
3111CE9320A7EC31008315E2 /* ResponseSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */; };
3111CE9420A7EC32008315E2 /* ResponseSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */; };
3111CE9520A7EC39008315E2 /* ServerTrustPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */; };
3111CE9620A7EC3A008315E2 /* ServerTrustPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */; };
3111CE9720A7EC3A008315E2 /* ServerTrustPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */; };
3111CE9520A7EC39008315E2 /* ServerTrustEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C33A1421B52089C00873DFF /* ServerTrustEvaluatorTests.swift */; };
3111CE9620A7EC3A008315E2 /* ServerTrustEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C33A1421B52089C00873DFF /* ServerTrustEvaluatorTests.swift */; };
3111CE9720A7EC3A008315E2 /* ServerTrustEvaluatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C33A1421B52089C00873DFF /* ServerTrustEvaluatorTests.swift */; };
3111CE9B20A7EC57008315E2 /* URLProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */; };
3111CE9C20A7EC58008315E2 /* URLProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */; };
3111CE9D20A7EC58008315E2 /* URLProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */; };
Expand Down Expand Up @@ -349,7 +349,7 @@
4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MultipartFormDataTests.swift; sourceTree = "<group>"; };
4C33A1231B5207DB00873DFF /* rainbow.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = rainbow.jpg; sourceTree = "<group>"; };
4C33A1241B5207DB00873DFF /* unicorn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = unicorn.png; sourceTree = "<group>"; };
4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustPolicyTests.swift; sourceTree = "<group>"; };
4C33A1421B52089C00873DFF /* ServerTrustEvaluatorTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerTrustEvaluatorTests.swift; sourceTree = "<group>"; };
4C341BB91B1A865A00C1B34D /* CacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheTests.swift; sourceTree = "<group>"; };
4C3D00531C66A63000D1F709 /* NetworkReachabilityManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManager.swift; sourceTree = "<group>"; };
4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkReachabilityManagerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -506,7 +506,7 @@
4C3238E61B3604DB00FE04AE /* MultipartFormDataTests.swift */,
4C3D00571C66A8B900D1F709 /* NetworkReachabilityManagerTests.swift */,
4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */,
4C33A1421B52089C00873DFF /* ServerTrustPolicyTests.swift */,
4C33A1421B52089C00873DFF /* ServerTrustEvaluatorTests.swift */,
F86AEFE51AE6A282007D9C76 /* TLSEvaluationTests.swift */,
4CCFA7991B2BE71600B6F460 /* URLProtocolTests.swift */,
F8AE910119D28DCC0078C7B2 /* ValidationTests.swift */,
Expand Down Expand Up @@ -656,8 +656,8 @@
319917A4209CDAC400103A19 /* RequestTaskMap.swift */,
31991791209CDA7F00103A19 /* Response.swift */,
4C0E5BF71B673D3400816CCC /* Result.swift */,
31991793209CDA7F00103A19 /* SessionStateProvider.swift */,
31991792209CDA7F00103A19 /* Session.swift */,
31991793209CDA7F00103A19 /* SessionStateProvider.swift */,
31D83FCD20D5C29300D93E47 /* URLConvertible+URLRequestConvertible.swift */,
);
name = Core;
Expand Down Expand Up @@ -1272,7 +1272,7 @@
buildActionMask = 2147483647;
files = (
4CF627181BA7CC240011A099 /* RequestTests.swift in Sources */,
3111CE9720A7EC3A008315E2 /* ServerTrustPolicyTests.swift in Sources */,
3111CE9720A7EC3A008315E2 /* ServerTrustEvaluatorTests.swift in Sources */,
3111CE9420A7EC32008315E2 /* ResponseSerializationTests.swift in Sources */,
3111CE8E20A7EBE7008315E2 /* MultipartFormDataTests.swift in Sources */,
311B199620B0ED990036823B /* UploadTests.swift in Sources */,
Expand Down Expand Up @@ -1396,7 +1396,7 @@
buildActionMask = 2147483647;
files = (
31ED52E81D73891B00199085 /* AFError+AlamofireTests.swift in Sources */,
3111CE9520A7EC39008315E2 /* ServerTrustPolicyTests.swift in Sources */,
3111CE9520A7EC39008315E2 /* ServerTrustEvaluatorTests.swift in Sources */,
3111CE9220A7EC30008315E2 /* ResponseSerializationTests.swift in Sources */,
3111CE8C20A7EBE6008315E2 /* MultipartFormDataTests.swift in Sources */,
311B199420B0ED980036823B /* UploadTests.swift in Sources */,
Expand Down Expand Up @@ -1424,7 +1424,7 @@
buildActionMask = 2147483647;
files = (
31ED52E91D73891C00199085 /* AFError+AlamofireTests.swift in Sources */,
3111CE9620A7EC3A008315E2 /* ServerTrustPolicyTests.swift in Sources */,
3111CE9620A7EC3A008315E2 /* ServerTrustEvaluatorTests.swift in Sources */,
3111CE9320A7EC31008315E2 /* ResponseSerializationTests.swift in Sources */,
3111CE8D20A7EBE7008315E2 /* MultipartFormDataTests.swift in Sources */,
311B199520B0ED980036823B /* UploadTests.swift in Sources */,
Expand Down Expand Up @@ -1598,7 +1598,6 @@
SDKROOT = watchos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Debug;
};
Expand All @@ -1618,7 +1617,6 @@
SDKROOT = watchos;
SKIP_INSTALL = YES;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 3.0;
};
name = Release;
};
Expand Down
106 changes: 87 additions & 19 deletions Source/AFError.swift
Expand Up @@ -40,12 +40,9 @@ public enum AFError: Error {
/// - missingURL: The URL request did not have a URL to encode.
/// - jsonEncodingFailed: JSON serialization failed with an underlying system error during the
/// encoding process.
/// - propertyListEncodingFailed: Property list serialization failed with an underlying system error during
/// encoding process.
public enum ParameterEncodingFailureReason {
case missingURL
case jsonEncodingFailed(error: Error)
case propertyListEncodingFailed(error: Error)
}

/// The underlying reason the multipart encoding error occurred.
Expand Down Expand Up @@ -116,7 +113,6 @@ public enum AFError: Error {
/// - inputFileReadFailed: The file containing the server response could not be read.
/// - stringSerializationFailed: String serialization failed using the provided `String.Encoding`.
/// - jsonSerializationFailed: JSON serialization failed with an underlying system error.
/// - propertyListSerializationFailed: Property list serialization failed with an underlying system error.
/// - invalidEmptyResponse: Generic serialization failed for an empty response that wasn't type `Empty`.
public enum ResponseSerializationFailureReason {
case inputDataNil
Expand All @@ -125,17 +121,50 @@ public enum AFError: Error {
case inputFileReadFailed(at: URL)
case stringSerializationFailed(encoding: String.Encoding)
case jsonSerializationFailed(error: Error)
case propertyListSerializationFailed(error: Error)
case invalidEmptyResponse(type: String)
}

public enum ServerTrustFailureReason {
public struct Output {
public let host: String
public let trust: SecTrust
public let status: OSStatus
public let result: SecTrustResultType

init(_ host: String, _ trust: SecTrust, _ status: OSStatus, _ result: SecTrustResultType) {
self.host = host
self.trust = trust
self.status = status
self.result = result
}
}
case unknown(host: String)
case noRequiredEvaluator(host: String)
case noCertificatesFound
case noPublicKeysFound
case policyApplicationFailed(trust: SecTrust, policy: SecPolicy, status: OSStatus)
case settingAnchorCertificatesFailed(status: OSStatus, certificates: [SecCertificate])
case revocationPolicyCreationFailed
case defaultEvaluationFailed(output: Output)
case hostValidationFailed(output: Output)
case revocationCheckFailed(output: Output, options: RevocationTrustEvaluator.Options)
case certificatePinningFailed(host: String, trust: SecTrust, pinnedCertificates: [SecCertificate], serverCertificates: [SecCertificate])
case publicKeyPinningFailed(host: String, trust: SecTrust, pinnedKeys: [SecKey], serverKeys: [SecKey])
}

case explicitlyCancelled
case invalidURL(url: URLConvertible)
case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
case responseValidationFailed(reason: ResponseValidationFailureReason)
case responseSerializationFailed(reason: ResponseSerializationFailureReason)
case certificatePinningFailed
case serverTrustEvaluationFailed(reason: ServerTrustFailureReason)
}

public extension Error {
public var asAFError: AFError? {
return self as? AFError
}
}

// MARK: - Error Booleans
Expand Down Expand Up @@ -181,9 +210,9 @@ extension AFError {
return false
}

/// Returns whether the `AFError` is a certificate pinning error.
public var isCertificatePinningError: Bool {
if case .certificatePinningFailed = self { return true }
/// Returns whether the `AFError` is a server trust evaluation error.
public var isServerTrustEvaluationError: Bool {
if case .serverTrustEvaluationFailed = self { return true }
return false
}
}
Expand Down Expand Up @@ -270,7 +299,7 @@ extension AFError {
extension AFError.ParameterEncodingFailureReason {
var underlyingError: Error? {
switch self {
case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
case .jsonEncodingFailed(let error):
return error
default:
return nil
Expand Down Expand Up @@ -344,19 +373,33 @@ extension AFError.ResponseSerializationFailureReason {

var underlyingError: Error? {
switch self {
case .jsonSerializationFailed(let error), .propertyListSerializationFailed(let error):
case .jsonSerializationFailed(let error):
return error
default:
return nil
}
}
}

extension AFError.ServerTrustFailureReason {
var output: AFError.ServerTrustFailureReason.Output? {
switch self {
case let .defaultEvaluationFailed(output),
let .hostValidationFailed(output),
let .revocationCheckFailed(output, _):
return output
default: return nil
}
}
}

// MARK: - Error Descriptions

extension AFError: LocalizedError {
public var errorDescription: String? {
switch self {
case .explicitlyCancelled:
return "Request explicitly cancelled."
case .invalidURL(let url):
return "URL is not valid: \(url)"
case .parameterEncodingFailed(let reason):
Expand All @@ -367,10 +410,8 @@ extension AFError: LocalizedError {
return reason.localizedDescription
case .responseSerializationFailed(let reason):
return reason.localizedDescription
case .explicitlyCancelled:
return "Request explicitly cancelled."
case .certificatePinningFailed:
return "Certificate pinning failed."
case .serverTrustEvaluationFailed:
return "Server trust evaluation failed."
}
}
}
Expand All @@ -382,8 +423,6 @@ extension AFError.ParameterEncodingFailureReason {
return "URL request to encode was missing a URL"
case .jsonEncodingFailed(let error):
return "JSON could not be encoded because of error:\n\(error.localizedDescription)"
case .propertyListEncodingFailed(let error):
return "PropertyList could not be encoded because of error:\n\(error.localizedDescription)"
}
}
}
Expand Down Expand Up @@ -442,8 +481,6 @@ extension AFError.ResponseSerializationFailureReason {
return "String could not be serialized with encoding: \(encoding)."
case .jsonSerializationFailed(let error):
return "JSON could not be serialized because of error:\n\(error.localizedDescription)"
case .propertyListSerializationFailed(let error):
return "PropertyList could not be serialized because of error:\n\(error.localizedDescription)"
case .invalidEmptyResponse(let type):
return "Empty response could not be serialized to type: \(type). Use Empty as the expected type for such responses."
}
Expand Down Expand Up @@ -472,3 +509,34 @@ extension AFError.ResponseValidationFailureReason {
}
}
}

extension AFError.ServerTrustFailureReason {
var localizedDescription: String {
switch self {
case .unknown:
return "Server trust evaluation failed but no specific error was thrown."
case let .noRequiredEvaluator(host):
return "A ServerTrustEvaluating value is required for host \(host) but none was found."
case .noCertificatesFound:
return "No certificates were found or provided for evaluation."
case .noPublicKeysFound:
return "No public keys were found or provided for evaluation."
case .policyApplicationFailed:
return "Attempting to set a SecPolicy failed."
case .settingAnchorCertificatesFailed:
return "Attempting to set the provided certificates as anchor certificates failed."
case .revocationPolicyCreationFailed:
return "Attempting to create a revocation policy failed."
case let .defaultEvaluationFailed(output):
return "Default evaluation failed for host \(output.host)."
case let .hostValidationFailed(output):
return "Host validation failed for host \(output.host)."
case let .revocationCheckFailed(output, _):
return "Revocation check failed for host \(output.host)."
case let .certificatePinningFailed(host, _, _, _):
return "Certificate pinning failed for host \(host)."
case let .publicKeyPinningFailed(host, _, _, _):
return "Public key pinning failed for host \(host)."
}
}
}
75 changes: 0 additions & 75 deletions Source/ParameterEncoding.swift
Expand Up @@ -371,81 +371,6 @@ public struct JSONEncoding: ParameterEncoding {

// MARK: -

/// Uses `PropertyListSerialization` to create a plist representation of the parameters object, according to the
/// associated format and write options values, which is set as the body of the request. The `Content-Type` HTTP header
/// field of an encoded request is set to `application/x-plist`.
public struct PropertyListEncoding: ParameterEncoding {

// MARK: Properties

/// Returns a default `PropertyListEncoding` instance.
public static var `default`: PropertyListEncoding { return PropertyListEncoding() }

/// Returns a `PropertyListEncoding` instance with xml formatting and default writing options.
public static var xml: PropertyListEncoding { return PropertyListEncoding(format: .xml) }

/// Returns a `PropertyListEncoding` instance with binary formatting and default writing options.
public static var binary: PropertyListEncoding { return PropertyListEncoding(format: .binary) }

/// The property list serialization format.
public let format: PropertyListSerialization.PropertyListFormat

/// The options for writing the parameters as plist data.
public let options: PropertyListSerialization.WriteOptions

// MARK: Initialization

/// Creates a `PropertyListEncoding` instance using the specified format and options.
///
/// - parameter format: The property list serialization format.
/// - parameter options: The options for writing the parameters as plist data.
///
/// - returns: The new `PropertyListEncoding` instance.
public init(
format: PropertyListSerialization.PropertyListFormat = .xml,
options: PropertyListSerialization.WriteOptions = 0)
{
self.format = format
self.options = options
}

// MARK: Encoding

/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
/// - parameter urlRequest: The request to have parameters applied.
/// - parameter parameters: The parameters to apply.
///
/// - throws: An `Error` if the encoding process encounters an error.
///
/// - returns: The encoded request.
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()

guard let parameters = parameters else { return urlRequest }

do {
let data = try PropertyListSerialization.data(
fromPropertyList: parameters,
format: format,
options: options
)

if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type")
}

urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .propertyListEncodingFailed(error: error))
}

return urlRequest
}
}

// MARK: -

extension NSNumber {
fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }
}
12 changes: 5 additions & 7 deletions Source/RequestAdapter.swift
Expand Up @@ -26,12 +26,10 @@ import Foundation

/// A type that can inspect and optionally adapt a `URLRequest` in some manner if necessary.
public protocol RequestAdapter {
/// Inspects and adapts the specified `URLRequest` in some manner if necessary and returns the result.
/// Inspects and adapts the specified `URLRequest` in some manner and calls the completion handler with the Result.
///
/// - parameter urlRequest: The URL request to adapt.
///
/// - throws: An `Error` if the adaptation encounters an error.
///
/// - returns: The adapted `URLRequest`.
func adapt(_ urlRequest: URLRequest) throws -> URLRequest
/// - Parameters:
/// - urlRequest: The `URLRequest` to adapt.
/// - completion: The completion handler that must be called when adaptation is complete.
func adapt(_ urlRequest: URLRequest, completion: @escaping (_ result: Result<URLRequest>) -> Void)
}