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

New Error Handling #1419

Merged
merged 11 commits into from
Aug 29, 2016
441 changes: 414 additions & 27 deletions Source/Error.swift

Large diffs are not rendered by default.

59 changes: 27 additions & 32 deletions Source/MultipartFormData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ open class MultipartFormData {
public let boundary: String

private var bodyParts: [BodyPart]
private var bodyPartError: NSError?
private var bodyPartError: Error?
private let streamBufferSize: Int

// MARK: - Lifecycle
Expand Down Expand Up @@ -206,8 +206,7 @@ open class MultipartFormData {
let mime = mimeType(forPathExtension: pathExtension)
append(fileURL, withName: name, fileName: fileName, mimeType: mime)
} else {
let failureReason = "Failed to extract the fileName of the provided URL: \(fileURL)"
setBodyPartError(withCode: NSURLErrorBadURL, failureReason: failureReason)
setBodyPartError(withReason: .failedToExtractFilename(for: fileURL))
}
}

Expand All @@ -232,19 +231,22 @@ open class MultipartFormData {
//============================================================

guard fileURL.isFileURL else {
let failureReason = "The file URL does not point to a file URL: \(fileURL)"
setBodyPartError(withCode: NSURLErrorBadURL, failureReason: failureReason)
setBodyPartError(withReason: .notAFile(at: fileURL))
return
}

//============================================================
// Check 2 - is file URL reachable?
//============================================================

let isReachable = (fileURL as NSURL).checkPromisedItemIsReachableAndReturnError(nil)

guard isReachable else {
setBodyPartError(withCode: NSURLErrorBadURL, failureReason: "The file URL is not reachable: \(fileURL)")

do {
let isReachable = try fileURL.checkPromisedItemIsReachable()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newline?

guard isReachable else {
setBodyPartError(withReason: .fileNotReachable(at: fileURL))
return
}
} catch {
bodyPartError = error
return
}

Expand All @@ -257,8 +259,7 @@ open class MultipartFormData {

guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else
{
let failureReason = "The file URL is a directory, not a file: \(fileURL)"
setBodyPartError(withCode: NSURLErrorBadURL, failureReason: failureReason)
setBodyPartError(withReason: .isDirectory(at: fileURL))
return
}

Expand All @@ -278,8 +279,7 @@ open class MultipartFormData {
}

guard let length = bodyContentLength else {
let failureReason = "Could not fetch attributes from the file URL: \(fileURL)"
setBodyPartError(withCode: NSURLErrorBadURL, failureReason: failureReason)
setBodyPartError(withReason: .failedToFetchAttributes(from: fileURL))
return
}

Expand All @@ -288,8 +288,7 @@ open class MultipartFormData {
//============================================================

guard let stream = InputStream(url: fileURL) else {
let failureReason = "Failed to create an input stream from the file URL: \(fileURL)"
setBodyPartError(withCode: NSURLErrorCannotOpenFile, failureReason: failureReason)
setBodyPartError(withReason: .failedToCreateInputStream(from: fileURL))
return
}

Expand Down Expand Up @@ -339,13 +338,13 @@ open class MultipartFormData {

// MARK: - Data Encoding

/// Encodes all the appended body parts into a single `NSData` object.
/// Encodes all the appended body parts into a single `Data` value.
///
/// It is important to note that this method will load all the appended body parts into memory all at the same
/// time. This method should only be used when the encoded data will have a small memory footprint. For large data
/// cases, please use the `writeEncodedDataToDisk(fileURL:completionHandler:)` method.
///
/// - throws: An `NSError` if encoding encounters an error.
/// - throws: An `AFError` if encoding encounters an error.
///
/// - returns: The encoded `Data` if encoding is successful.
public func encode() throws -> Data {
Expand Down Expand Up @@ -373,18 +372,18 @@ open class MultipartFormData {
///
/// - parameter fileURL: The file URL to write the multipart form data into.
///
/// - throws: An `NSError` if encoding encounters an error.
/// - throws: An `AFError` if encoding encounters an error.
public func writeEncodedDataToDisk(_ fileURL: URL) throws {
if let bodyPartError = bodyPartError {
throw bodyPartError
}

if FileManager.default.fileExists(atPath: fileURL.path) {
let failureReason = "A file already exists at the given file URL: \(fileURL)"
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL, failureReason: failureReason)
throw AFError.outputStreamWriteFailed(reason: .fileAlreadyExists(at: fileURL))
} else if !fileURL.isFileURL {
let failureReason = "The URL does not point to a valid file: \(fileURL)"
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL, failureReason: failureReason)
throw AFError.outputStreamWriteFailed(reason: .invalidFile(at: fileURL))
}

let outputStream: OutputStream
Expand All @@ -393,19 +392,18 @@ open class MultipartFormData {
outputStream = possibleOutputStream
} else {
let failureReason = "Failed to create an output stream with the given URL: \(fileURL)"
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, failureReason: failureReason)
throw AFError.outputStreamWriteFailed(reason: .failedToCreateStream(at: fileURL))
}

outputStream.open()
defer { outputStream.close() }

self.bodyParts.first?.hasInitialBoundary = true
self.bodyParts.last?.hasFinalBoundary = true

for bodyPart in self.bodyParts {
try write(bodyPart, to: outputStream)
}

outputStream.close()
}

// MARK: - Private - Body Part Encoding
Expand Down Expand Up @@ -459,8 +457,7 @@ open class MultipartFormData {
if bytesRead > 0 {
encoded.append(buffer, count: bytesRead)
} else if bytesRead < 0 {
let failureReason = "Failed to read from input stream: \(inputStream)"
error = NSError(domain: NSURLErrorDomain, code: .inputStreamReadFailed, failureReason: failureReason)
error = AFError.inputStreamReadFailed(inputStream: inputStream)
break
} else {
break
Expand Down Expand Up @@ -514,8 +511,7 @@ open class MultipartFormData {

try write(&buffer, to: outputStream)
} else if bytesRead < 0 {
let failureReason = "Failed to read from input stream: \(inputStream)"
throw NSError(domain: NSURLErrorDomain, code: .inputStreamReadFailed, failureReason: failureReason)
throw AFError.inputStreamReadFailed(inputStream: inputStream)
} else {
break
}
Expand Down Expand Up @@ -551,8 +547,7 @@ open class MultipartFormData {
}

if bytesWritten < 0 {
let failureReason = "Failed to write to output stream: \(outputStream)"
throw NSError(domain: NSURLErrorDomain, code: .outputStreamWriteFailed, failureReason: failureReason)
throw AFError.outputStreamWriteFailed(reason: .writeFailed(to: outputStream))
}

bytesToWrite -= bytesWritten
Expand Down Expand Up @@ -607,8 +602,8 @@ open class MultipartFormData {

// MARK: - Private - Errors

private func setBodyPartError(withCode code: Int, failureReason: String) {
private func setBodyPartError(withReason reason: AFError.MultipartEncodingFailureReason) {
guard bodyPartError == nil else { return }
bodyPartError = NSError(domain: NSURLErrorDomain, code: code, failureReason: failureReason)
bodyPartError = AFError.multipartEncodingFailed(reason: reason)
}
}
13 changes: 6 additions & 7 deletions Source/ParameterEncoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public enum ParameterEncoding {
case urlEncodedInURL
case json
case propertyList(PropertyListSerialization.PropertyListFormat, PropertyListSerialization.WriteOptions)
case custom((URLRequestConvertible, [String: Any]?) -> (URLRequest, NSError?))
case custom((URLRequestConvertible, [String: Any]?) -> (URLRequest, Error?))

/// Creates a URL request by encoding parameters and applying them onto an existing request.
///
Expand All @@ -82,13 +82,13 @@ public enum ParameterEncoding {
public func encode(
_ urlRequest: URLRequestConvertible,
parameters: [String: Any]?)
-> (URLRequest, NSError?)
-> (URLRequest, Error?)
{
var urlRequest = urlRequest.urlRequest

guard let parameters = parameters else { return (urlRequest, nil) }

var encodingError: NSError? = nil
var encodingError: Error? = nil

switch self {
case .url, .urlEncodedInURL:
Expand Down Expand Up @@ -143,16 +143,15 @@ public enum ParameterEncoding {
}
case .json:
do {
let options = JSONSerialization.WritingOptions()
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
let data = try JSONSerialization.data(withJSONObject: parameters, options: [])

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

urlRequest.httpBody = data
} catch {
encodingError = error as NSError
encodingError = error
}
case .propertyList(let format, let options):
do {
Expand All @@ -168,7 +167,7 @@ public enum ParameterEncoding {

urlRequest.httpBody = data
} catch {
encodingError = error as NSError
encodingError = error
}
case .custom(let closure):
(urlRequest, encodingError) = closure(urlRequest, parameters)
Expand Down
6 changes: 3 additions & 3 deletions Source/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import Foundation

/// Used to store all response data returned from a completed `Request`.
public struct Response<ValueType, ErrorType: Error> {
public struct Response<Value> {
/// The URL request sent to the server.
public let request: URLRequest?

Expand All @@ -36,7 +36,7 @@ public struct Response<ValueType, ErrorType: Error> {
public let data: Data?

/// The result of response serialization.
public let result: Result<ValueType, ErrorType>
public let result: Result<Value>

/// The timeline of the complete lifecycle of the `Request`.
public let timeline: Timeline
Expand All @@ -55,7 +55,7 @@ public struct Response<ValueType, ErrorType: Error> {
request: URLRequest?,
response: HTTPURLResponse?,
data: Data?,
result: Result<ValueType, ErrorType>,
result: Result<Value>,
timeline: Timeline = Timeline())
{
self.request = request
Expand Down