Skip to content

Commit

Permalink
[PR #1419] Refactored Error to new AFError system across all APIs.
Browse files Browse the repository at this point in the history
* Preliminary refactoring.

* Refactor to use AFError.

* Update inline docs in SessionDelegate.

* Update AFError implementation.

* Start trying to fix tests.

* Update and fix tests.

* Address comments.

* Remove unnecessary @testable declarations.

* Cleanup whitespace.
  • Loading branch information
jshier authored and cnoon committed Sep 8, 2016
1 parent 9e3ce80 commit 2205fb8
Show file tree
Hide file tree
Showing 28 changed files with 975 additions and 543 deletions.
8 changes: 8 additions & 0 deletions Alamofire.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
/* End PBXAggregateTarget section */

/* Begin PBXBuildFile section */
31ED52E81D73891B00199085 /* AFError+AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED52E61D73889D00199085 /* AFError+AlamofireTests.swift */; };
31ED52E91D73891C00199085 /* AFError+AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED52E61D73889D00199085 /* AFError+AlamofireTests.swift */; };
31ED52EA1D73891C00199085 /* AFError+AlamofireTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31ED52E61D73889D00199085 /* AFError+AlamofireTests.swift */; };
4C0B58391B747A4400C0B99C /* ResponseSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */; };
4C0B583A1B747A4400C0B99C /* ResponseSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */; };
4C0B62511BB1001C009302D3 /* Response.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C0B62501BB1001C009302D3 /* Response.swift */; };
Expand Down Expand Up @@ -251,6 +254,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
31ED52E61D73889D00199085 /* AFError+AlamofireTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AFError+AlamofireTests.swift"; sourceTree = "<group>"; };
4C0B58381B747A4400C0B99C /* ResponseSerializationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResponseSerializationTests.swift; sourceTree = "<group>"; };
4C0B62501BB1001C009302D3 /* Response.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Response.swift; sourceTree = "<group>"; };
4C0E02681BF99A18004E7F18 /* Info-tvOS.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-tvOS.plist"; path = "Source/Info-tvOS.plist"; sourceTree = SOURCE_ROOT; };
Expand Down Expand Up @@ -494,6 +498,7 @@
4C7C8D201B9D0D7300948136 /* Extensions */ = {
isa = PBXGroup;
children = (
31ED52E61D73889D00199085 /* AFError+AlamofireTests.swift */,
4C4CBE7A1BAF700C0024D659 /* String+AlamofireTests.swift */,
);
name = Extensions;
Expand Down Expand Up @@ -1033,6 +1038,7 @@
4C9DCE7A1CB1BCE2003E6463 /* SessionDelegateTests.swift in Sources */,
4C80F9F91BB730F6001B46D2 /* String+AlamofireTests.swift in Sources */,
4CF6271E1BA7CC240011A099 /* MultipartFormDataTests.swift in Sources */,
31ED52EA1D73891C00199085 /* AFError+AlamofireTests.swift in Sources */,
4CF627201BA7CC240011A099 /* ServerTrustPolicyTests.swift in Sources */,
4CF627241BA7CC240011A099 /* ValidationTests.swift in Sources */,
4CF627141BA7CC240011A099 /* BaseTestCase.swift in Sources */,
Expand Down Expand Up @@ -1128,6 +1134,7 @@
4C9DCE781CB1BCE2003E6463 /* SessionDelegateTests.swift in Sources */,
4C4CBE7B1BAF700C0024D659 /* String+AlamofireTests.swift in Sources */,
4CA028C51B7466C500C84163 /* ResultTests.swift in Sources */,
31ED52E81D73891B00199085 /* AFError+AlamofireTests.swift in Sources */,
4CCFA79A1B2BE71600B6F460 /* URLProtocolTests.swift in Sources */,
F86AEFE71AE6A312007D9C76 /* TLSEvaluationTests.swift in Sources */,
4C0B58391B747A4400C0B99C /* ResponseSerializationTests.swift in Sources */,
Expand All @@ -1154,6 +1161,7 @@
4C9DCE791CB1BCE2003E6463 /* SessionDelegateTests.swift in Sources */,
4C4CBE7C1BAF700C0024D659 /* String+AlamofireTests.swift in Sources */,
4CA028C61B7466C500C84163 /* ResultTests.swift in Sources */,
31ED52E91D73891C00199085 /* AFError+AlamofireTests.swift in Sources */,
4CCFA79B1B2BE71600B6F460 /* URLProtocolTests.swift in Sources */,
F829C6BE1A7A950600A2CD59 /* ParameterEncodingTests.swift in Sources */,
4C0B583A1B747A4400C0B99C /* ResponseSerializationTests.swift in Sources */,
Expand Down
344 changes: 317 additions & 27 deletions Source/Error.swift

Large diffs are not rendered by default.

131 changes: 49 additions & 82 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: AFError?
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: .bodyPartFilenameInvalid(in: 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: .bodyPartURLInvalid(url: 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()
guard isReachable else {
setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL))
return
}
} catch {
setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error))
return
}

Expand All @@ -257,29 +259,26 @@ 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: .bodyPartFileIsDirectory(at: fileURL))
return
}

//============================================================
// Check 4 - can the file size be extracted?
//============================================================

var bodyContentLength: UInt64?
let bodyContentLength: UInt64

do {
if let fileSize = try FileManager.default.attributesOfItem(atPath: path)[.size] as? NSNumber {
bodyContentLength = fileSize.uint64Value
guard let fileSize = try FileManager.default.attributesOfItem(atPath: path)[.size] as? NSNumber else {
setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL))
return
}

bodyContentLength = fileSize.uint64Value
}
catch {
// No Op
}

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

Expand All @@ -288,12 +287,11 @@ 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: .bodyPartInputStreamCreationFailed(for: fileURL))
return
}

append(stream, withLength: length, headers: headers)
append(stream, withLength: bodyContentLength, headers: headers)
}

/// Creates a body part from the stream and appends it to the multipart form data object.
Expand Down Expand Up @@ -339,13 +337,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,39 +371,31 @@ open class MultipartFormData {
///
/// - parameter fileURL: The file URL to write the multipart form data into.
///
/// - throws: An `NSError` if encoding encounters an error.
public func writeEncodedDataToDisk(_ fileURL: URL) throws {
/// - throws: An `AFError` if encoding encounters an error.
public func writeEncodedData(to 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.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(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.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL))
}

let outputStream: OutputStream

if let possibleOutputStream = OutputStream(url: fileURL, append: false) {
outputStream = possibleOutputStream
} else {
let failureReason = "Failed to create an output stream with the given URL: \(fileURL)"
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, failureReason: failureReason)
guard let outputStream = OutputStream(url: fileURL, append: false) else {
throw AFError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: 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 All @@ -426,7 +416,7 @@ open class MultipartFormData {
encoded.append(finalBoundaryData())
}

return encoded as Data
return encoded
}

private func encodeHeaders(for bodyPart: BodyPart) -> Data {
Expand All @@ -443,37 +433,26 @@ open class MultipartFormData {
private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data {
let inputStream = bodyPart.bodyStream
inputStream.open()
defer { inputStream.close() }

var error: Error?
var encoded = Data()

while inputStream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)

if inputStream.streamError != nil {
error = inputStream.streamError
break
if let error = inputStream.streamError {
throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error))
}

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)
break
} else {
break
}
}

inputStream.close()

if let error = error {
throw error
}

return encoded as Data
return encoded
}

// MARK: - Private - Writing Body Part to Output Stream
Expand All @@ -497,14 +476,16 @@ open class MultipartFormData {

private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
let inputStream = bodyPart.bodyStream

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

while inputStream.hasBytesAvailable {
var buffer = [UInt8](repeating: 0, count: streamBufferSize)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)

if let streamError = inputStream.streamError {
throw streamError
throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError))
}

if bytesRead > 0 {
Expand All @@ -513,15 +494,10 @@ 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)
} else {
break
}
}

inputStream.close()
}

private func writeFinalBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
Expand All @@ -534,34 +510,25 @@ open class MultipartFormData {

private func write(_ data: Data, to outputStream: OutputStream) throws {
var buffer = [UInt8](repeating: 0, count: data.count)
(data as NSData).getBytes(&buffer, length: data.count)
data.copyBytes(to: &buffer, count: data.count)

return try write(&buffer, to: outputStream)
}

private func write(_ buffer: inout [UInt8], to outputStream: OutputStream) throws {
var bytesToWrite = buffer.count

while bytesToWrite > 0 {
if outputStream.hasSpaceAvailable {
let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)

if let streamError = outputStream.streamError {
throw streamError
}
while bytesToWrite > 0, outputStream.hasSpaceAvailable {
let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)

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

bytesToWrite -= bytesWritten
bytesToWrite -= bytesWritten

if bytesToWrite > 0 {
buffer = Array(buffer[bytesWritten..<buffer.count])
}
} else if let streamError = outputStream.streamError {
throw streamError
if bytesToWrite > 0 {
buffer = Array(buffer[bytesWritten..<buffer.count])
}
}
}
Expand Down Expand Up @@ -607,8 +574,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)
}
}

0 comments on commit 2205fb8

Please sign in to comment.