-
Notifications
You must be signed in to change notification settings - Fork 157
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added DownloadStatus convenience (#454)
* Added DownloadStatus convenience * Minor docs edits * DownloadState.failed instead of DownloadState::Failed * Add additional tests * Address swiftlint violations * Finish failure message * Begin to address feedback * Address feedback, refactor tests * Remove whitespace * Update Tests/MapboxMapsTests/Foundation/Extensions/Core/DownloadStatusTests.swift Co-authored-by: Andrew Hershberger <andrew.hershberger@mapbox.com> Co-authored-by: Jordan Kiley <jordan.kiley@mapbox.com> Co-authored-by: Jordan Kiley <jmkiley@users.noreply.github.com> Co-authored-by: Andrew Hershberger <andrew.hershberger@mapbox.com>
- Loading branch information
1 parent
2c73db4
commit 3cbf3d8
Showing
2 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
124 changes: 124 additions & 0 deletions
124
Sources/MapboxMaps/Foundation/Extensions/Core/DownloadStatus.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
@_exported import MapboxCommon | ||
@_implementationOnly import MapboxCommon_Private | ||
|
||
extension DownloadError: LocalizedError { | ||
/// Standardized error message | ||
public var errorDescription: String? { | ||
return "\(code): \(message)" | ||
} | ||
} | ||
|
||
extension DownloadStatus { | ||
|
||
/// Initialize a `DownloadStatus` | ||
/// | ||
/// - Parameters: | ||
/// - downloadId: Download id which was created by download request. | ||
/// - state: State of download request. | ||
/// - error: Contains error information in case of failure when state is | ||
/// set to `DownloadState.failed`. | ||
/// - totalBytes: Total amount of bytes to receive. In some cases this | ||
/// value is unknown until we get final part of the file. | ||
/// - receivedBytes: Amount of bytes already received and saved on the disk. | ||
/// Includes previous download attempts for a resumed download. | ||
/// - transferredBytes: Amount of bytes received during the current resume | ||
/// attempt. For downloads that weren't resumed, this value will be | ||
/// the same as receivedBytes. | ||
/// - downloadOptions: Download options used to send the download request. | ||
/// - httpResult: An optional HTTP result. This field is only set for `DownloadState.failed` | ||
/// and `DownloadState.finished`. | ||
/// For `.failed` expect `HttpRequestError` to be provided for cases when | ||
/// `DownloadErrorCode` is `NetworkError`. For `.finished` `HttpResponseData` | ||
/// is set, but with empty data field (since all the data was written | ||
/// to the disk). | ||
public convenience init(downloadId: UInt64, | ||
state: DownloadState, | ||
error: DownloadError?, | ||
totalBytes: UInt64?, | ||
receivedBytes: UInt64, | ||
transferredBytes: UInt64, | ||
downloadOptions: DownloadOptions, | ||
httpResult: Result<HttpResponseData, HttpRequestError>?) { | ||
|
||
let expected: Expected<AnyObject, AnyObject>? | ||
switch httpResult { | ||
case let .success(response): | ||
expected = Expected(value: response) | ||
case let .failure(error): | ||
expected = Expected(error: error) | ||
case .none: | ||
expected = nil | ||
} | ||
|
||
self.init(downloadId: downloadId, | ||
state: state, | ||
error: error, | ||
totalBytes: totalBytes?.NSNumber, | ||
receivedBytes: receivedBytes, | ||
transferredBytes: transferredBytes, | ||
downloadOptions: downloadOptions, | ||
httpResult: expected) | ||
} | ||
|
||
/// Convenience to initialize a `DownloadStatus` when the download state is `.pending` | ||
/// | ||
/// - Parameters: | ||
/// - error: Contains error information in case of failure when state is | ||
/// set to `DownloadState.failed`. | ||
/// - totalBytes: Total amount of bytes to receive. In some cases this | ||
/// value is unknown until we get final part of the file. | ||
/// - downloadOptions: Download options used to send the download request. | ||
/// - httpResult: An optional HTTP result. This field is only set for `DownloadState.failed` | ||
/// and `DownloadState.finished`. | ||
/// For `.failed` expect `HttpRequestError` to be provided for cases when | ||
/// `DownloadErrorCode` is `NetworkError`. For `.finished` `HttpResponseData` | ||
/// is set, but with empty data field (since all the data was written | ||
/// to the disk). | ||
public convenience init(error: DownloadError?, | ||
totalBytes: UInt64?, | ||
downloadOptions: DownloadOptions, | ||
httpResult: Result<HttpResponseData, HttpRequestError>?) { | ||
|
||
let expected: Expected<AnyObject, AnyObject>? | ||
switch httpResult { | ||
case let .success(response): | ||
expected = Expected(value: response) | ||
case let .failure(error): | ||
expected = Expected(error: error) | ||
case .none: | ||
expected = nil | ||
} | ||
|
||
self.init(error: error, | ||
totalBytes: totalBytes?.NSNumber, | ||
downloadOptions: downloadOptions, | ||
httpResult: expected) | ||
} | ||
|
||
/// HTTP result. This field is only set for `DownloadState.failed` and | ||
/// `DownloadState.finished`. | ||
/// | ||
/// For `.failed` expect `HttpRequestError` to be provided for cases when | ||
/// `DownloadErrorCode` is `NetworkError`. | ||
/// And for `.finished` `HttpResponseData` is set, but with empty data field | ||
/// (since all the data was written to the disk). | ||
public var httpResult: Result<HttpResponseData, HttpRequestError>? { | ||
guard let httpExpected = __httpResult else { | ||
return nil | ||
} | ||
|
||
if httpExpected.isValue(), let value = httpExpected.value as? HttpResponseData { | ||
return .success(value) | ||
} else if httpExpected.isError(), let error = httpExpected.error as? HttpRequestError { | ||
return .failure(error) | ||
} else { | ||
fatalError("Found unexpected types.") | ||
} | ||
} | ||
|
||
/// Total amount of bytes to receive. In some cases this value is unknown | ||
/// until we get final part of the file being downloaded. | ||
public var totalBytes: UInt64? { | ||
return __totalBytes?.uint64Value | ||
} | ||
} |
184 changes: 184 additions & 0 deletions
184
Tests/MapboxMapsTests/Foundation/Extensions/Core/DownloadStatusTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
import XCTest | ||
@testable import MapboxMaps | ||
@_implementationOnly import MapboxCommon_Private | ||
|
||
final class DownloadStatusTests: XCTestCase { | ||
var httpRequest: HttpRequest! | ||
var downloadOptions: DownloadOptions! | ||
var downloadError: DownloadError! | ||
var httpRequestError: HttpRequestError! | ||
var httpResponseData: HttpResponseData! | ||
|
||
override func setUp() { | ||
super.setUp() | ||
httpRequest = HttpRequest(url: name, headers: [:], uaComponents: UAComponents(), body: nil) | ||
downloadOptions = DownloadOptions(request: httpRequest, localPath: "some/test/path") | ||
httpRequestError = HttpRequestError(type: .otherError, message: "Some failure") | ||
httpResponseData = HttpResponseData(headers: [:], code: 200, data: Data()) | ||
downloadError = DownloadError(code: .networkError, message: "Some network error") | ||
} | ||
|
||
func testSuccessfulLongInitializer() { | ||
let status = DownloadStatus(downloadId: 1, | ||
state: .finished, | ||
error: .none, | ||
totalBytes: 2, | ||
receivedBytes: 3, | ||
transferredBytes: 4, | ||
downloadOptions: downloadOptions, | ||
httpResult: .success(httpResponseData)) | ||
XCTAssertEqual(status.downloadId, 1, "The value for downloadId should be 1, got \(status.downloadId)") | ||
XCTAssertEqual(status.state, DownloadState.finished, "The download state should be finished, got \(status.state)") | ||
XCTAssertNil(status.error, "The error should be nil, got \(status.error.debugDescription)") | ||
XCTAssertEqual(status.__totalBytes, 2, "The value for __totalBytes should be 2, got \(status.__totalBytes.debugDescription)") | ||
XCTAssertEqual(status.receivedBytes, 3, "The value for receivedBytes should be 3, got \(status.receivedBytes)") | ||
XCTAssertEqual(status.transferredBytes, 4, "The value for transferredBytes should be 4, got \(status.transferredBytes)") | ||
XCTAssertEqual(status.downloadOptions, downloadOptions, "The value for downloadOptions should be \(downloadOptions.debugDescription), got \(status.downloadOptions.debugDescription)") | ||
|
||
if let result = status.__httpResult, result.isValue(), let value = result.value as? HttpResponseData { | ||
XCTAssertEqual(value, httpResponseData) | ||
} else { | ||
XCTFail("This should be a value. Instead got \(status.__httpResult.debugDescription).") | ||
} | ||
} | ||
|
||
func testFailedLongInitializer() { | ||
let status = DownloadStatus(downloadId: 1, | ||
state: .failed, | ||
error: downloadError, | ||
totalBytes: 2, | ||
receivedBytes: 3, | ||
transferredBytes: 4, | ||
downloadOptions: downloadOptions, | ||
httpResult: .failure(httpRequestError)) | ||
XCTAssertEqual(status.downloadId, 1, "The value for downloadId should be 1, got \(status.downloadId)") | ||
XCTAssertEqual(status.state, DownloadState.failed, "The download state should be failed, got \(status.state)") | ||
XCTAssertEqual(status.error, downloadError, "The error should be \(downloadError.localizedDescription), got \(status.error.debugDescription)") | ||
XCTAssertEqual(status.__totalBytes, 2, "The value for __totalBytes should be 2, got \(status.__totalBytes.debugDescription)") | ||
XCTAssertEqual(status.receivedBytes, 3, "The value for receivedBytes should be 3, got \(status.receivedBytes)") | ||
XCTAssertEqual(status.transferredBytes, 4, "The value for transferredBytes should be 4, got \(status.transferredBytes)") | ||
XCTAssertEqual(status.downloadOptions, downloadOptions, "The value for downloadOptions should be \(downloadOptions.debugDescription), got \(status.downloadOptions)") | ||
|
||
if let result = status.__httpResult, result.isError(), let error = result.error as? HttpRequestError { | ||
XCTAssertEqual(error, httpRequestError, "__httpResult should be \(httpRequestError.debugDescription), got \(error)") | ||
} else { | ||
XCTFail("__httpResult should be an error, got \(String(describing: status.__httpResult))") | ||
} | ||
} | ||
|
||
func testNilLongInitializer() { | ||
let status = DownloadStatus(downloadId: 1, | ||
state: .finished, | ||
error: .none, | ||
totalBytes: UInt64(2), // This tells the compiler which initializer to use | ||
receivedBytes: 3, | ||
transferredBytes: 4, | ||
downloadOptions: downloadOptions, | ||
httpResult: nil) | ||
XCTAssertEqual(status.downloadId, 1, "The value for downloadId should be 1, got \(status.downloadId)") | ||
XCTAssertEqual(status.state, DownloadState.finished, "The download state should be finished, got \(status.state)") | ||
XCTAssertNil(status.error, "The error should be nil, got \(status.error.debugDescription)") | ||
XCTAssertEqual(status.__totalBytes, 2, "The value for __totalBytes should be 2, got \(status.__totalBytes.debugDescription)") | ||
XCTAssertEqual(status.receivedBytes, 3, "The value for receivedBytes should be 3, got \(status.receivedBytes)") | ||
XCTAssertEqual(status.transferredBytes, 4, "The value for transferredBytes should be 4, got \(status.transferredBytes)") | ||
XCTAssertEqual(status.downloadOptions, downloadOptions, "The value for downloadOptions should be \(downloadOptions.debugDescription), got \(status.downloadOptions.debugDescription)") | ||
XCTAssertNil(status.__httpResult, "_httpResult should be nil, got \(status.__httpResult.debugDescription)") | ||
} | ||
|
||
func testSuccessfulShortInitializer() { | ||
let status = DownloadStatus(error: nil, | ||
totalBytes: 1234, | ||
downloadOptions: downloadOptions, | ||
httpResult: .success(httpResponseData)) | ||
|
||
XCTAssertNil(status.error, "The error should be nil, got \(status.error.debugDescription)") | ||
XCTAssertEqual(status.__totalBytes, 1234, "The value for __totalBytes should be 1234, got \(status.__totalBytes.debugDescription)") | ||
XCTAssertEqual(status.downloadOptions, downloadOptions, "The value for downloadOptions should be \(downloadOptions.debugDescription), got \(status.downloadOptions.debugDescription)") | ||
if let result = status.__httpResult, result.isValue(), let value = result.value as? HttpResponseData { | ||
XCTAssertEqual(value, httpResponseData) | ||
} else { | ||
XCTFail("This should be a value. Instead got \(status.__httpResult.debugDescription).") | ||
} | ||
} | ||
|
||
func testFailedShortInitializer() { | ||
let status = DownloadStatus(error: downloadError, | ||
totalBytes: 1234, | ||
downloadOptions: downloadOptions, | ||
httpResult: .failure(httpRequestError)) | ||
|
||
XCTAssertEqual(status.error, downloadError, "The error should be \(downloadError.localizedDescription), got \(status.error.debugDescription)") | ||
XCTAssertEqual(status.__totalBytes, 1234, "The value for __totalBytes should be 1234, got \(status.__totalBytes.debugDescription)") | ||
XCTAssertEqual(status.downloadOptions, downloadOptions, "The value for downloadOptions should be \(downloadOptions.debugDescription), got \(status.downloadOptions.debugDescription)") | ||
if let result = status.__httpResult, result.isError(), let error = result.error as? HttpRequestError { | ||
XCTAssertEqual(error, httpRequestError, "__httpResult should be \(httpRequestError.debugDescription), got \(error)") | ||
} else { | ||
XCTFail("__httpResult should be an error, got \(String(describing: status.__httpResult))") | ||
} | ||
} | ||
|
||
func testNilShortInitializer() { | ||
let status = DownloadStatus(error: nil, | ||
totalBytes: UInt64(1234), | ||
downloadOptions: downloadOptions, | ||
httpResult: nil) | ||
|
||
XCTAssertNil(status.error, "The error should be nil, got \(status.error.debugDescription)") | ||
XCTAssertEqual(status.__totalBytes, 1234, "The value for __totalBytes should be 1234, got \(status.__totalBytes.debugDescription)") | ||
XCTAssertEqual(status.downloadOptions, downloadOptions, "The value for downloadOptions should be \(downloadOptions.debugDescription), got \(status.downloadOptions.debugDescription)") | ||
XCTAssertNil(status.__httpResult, "_httpResult should be nil, got \(status.__httpResult.debugDescription)") | ||
} | ||
|
||
func testNilHttpResult() { | ||
let status = DownloadStatus(downloadId: 1, | ||
state: .finished, | ||
error: .none, | ||
totalBytes: NSNumber(value: 2), // forces using the unrefined initializer | ||
receivedBytes: 3, | ||
transferredBytes: 4, | ||
downloadOptions: downloadOptions, | ||
httpResult: nil) | ||
XCTAssertNil(status.httpResult, "httpResult should be nil.") | ||
} | ||
|
||
func testValueHttpResult() throws { | ||
let status = DownloadStatus(error: nil, | ||
totalBytes: nil, | ||
downloadOptions: downloadOptions, | ||
httpResult: Expected(value: httpResponseData)) | ||
|
||
let result = try status.httpResult?.get() | ||
XCTAssertEqual(result, httpResponseData, "The two HttpResponseData va;ues should be equal.") | ||
} | ||
|
||
func testErrorHttpResult() throws { | ||
let status = DownloadStatus(error: nil, | ||
totalBytes: nil, | ||
downloadOptions: downloadOptions, | ||
httpResult: Expected(error: httpRequestError)) | ||
let result = status.httpResult | ||
guard case .failure(let error) = result else { | ||
XCTFail("Expected to find an error, instead found \(result.debugDescription).") | ||
return | ||
} | ||
|
||
XCTAssertEqual(error, httpRequestError, "The two errors should be equal.") | ||
} | ||
|
||
func testNilTotalBytes() { | ||
let status = DownloadStatus(error: .none, | ||
totalBytes: nil, | ||
downloadOptions: downloadOptions, | ||
httpResult: Expected(value: httpResponseData)) | ||
|
||
XCTAssertNil(status.totalBytes, "The value for totalBytes should be nil.") | ||
} | ||
|
||
func testNotNilTotalBytes() { | ||
let status = DownloadStatus(error: .none, | ||
totalBytes: 1234, | ||
downloadOptions: downloadOptions, | ||
httpResult: Expected(value: httpResponseData)) | ||
XCTAssertEqual(status.totalBytes, 1234, "The value for totalBytes should be 1234, found \(status.totalBytes.debugDescription).") | ||
} | ||
} |