Skip to content

Commit

Permalink
feat: add helper methods to ParseFileTransferable protocol (#411)
Browse files Browse the repository at this point in the history
* feat: add helper methods to ParseFileTransferable protocol

* add more tests
  • Loading branch information
cbaker6 committed Sep 13, 2022
1 parent 9ab5271 commit e2827c5
Show file tree
Hide file tree
Showing 6 changed files with 426 additions and 30 deletions.
14 changes: 11 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# Parse-Swift Changelog

### main
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.12.0...main)
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.13.0...main)
* _Contributing to this repo? Add info about your change here to be included in the next release_

### 4.13.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.12.0...4.13.0)

__New features__
- Add helper methods to ParseFileTransferable protocol to assist with creating propper responses to file uploads ([#411](https://github.com/parse-community/Parse-Swift/pull/411)), thanks to [Corey Baker](https://github.com/cbaker6).

__Fixes__
- Remove cached error responses when decoding errors occur ([#411](https://github.com/parse-community/Parse-Swift/pull/411)), thanks to [Corey Baker](https://github.com/cbaker6).

### 4.12.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/4.11.0...4.12.0)

__New features__
- Add the ParseFileTransferable protocol for overriding the default transfer behavior for ParseFile's. Allows for direct uploads to other file storage
providers ([#408](https://github.com/parse-community/Parse-Swift/pull/408)), thanks to [Corey Baker](https://github.com/cbaker6).
- Add the ParseFileTransferable protocol for overriding the default transfer behavior for ParseFile's. Allows for direct uploads to other file storage providers ([#410](https://github.com/parse-community/Parse-Swift/pull/410)), thanks to [Corey Baker](https://github.com/cbaker6).
- Add the become method to ParseInstallation which allows any ParseInstallation to be copied to the current installation. This method can be used to migrate any ParseInstallation to the current installation in the Swift SDK ([#407](https://github.com/parse-community/Parse-Swift/pull/407)), thanks to [Corey Baker](https://github.com/cbaker6).

__Fixes__
Expand Down
8 changes: 8 additions & 0 deletions ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@
704E781D28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */; };
704E781E28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */; };
704E781F28CFFAF80075F952 /* ParseFileDefaultTransfer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */; };
704E782128D0D73E0075F952 /* ParseFileTransferableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E782028D0D73E0075F952 /* ParseFileTransferableTests.swift */; };
704E782228D0D73E0075F952 /* ParseFileTransferableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E782028D0D73E0075F952 /* ParseFileTransferableTests.swift */; };
704E782328D0D73E0075F952 /* ParseFileTransferableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 704E782028D0D73E0075F952 /* ParseFileTransferableTests.swift */; };
705025992842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; };
7050259A2842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; };
7050259B2842FD3B008D6624 /* ParseCLPTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705025982842FD3B008D6624 /* ParseCLPTests.swift */; };
Expand Down Expand Up @@ -1244,6 +1247,7 @@
704C886B28BE69A8008E6B01 /* ParseConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseConfiguration.swift; sourceTree = "<group>"; };
704E781628CFD8A00075F952 /* ParseFileTransferable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileTransferable.swift; sourceTree = "<group>"; };
704E781B28CFFAF80075F952 /* ParseFileDefaultTransfer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileDefaultTransfer.swift; sourceTree = "<group>"; };
704E782028D0D73E0075F952 /* ParseFileTransferableTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileTransferableTests.swift; sourceTree = "<group>"; };
705025982842FD3B008D6624 /* ParseCLPTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseCLPTests.swift; sourceTree = "<group>"; };
7050259C2843F0CF008D6624 /* ParseSchemaAsyncTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchemaAsyncTests.swift; sourceTree = "<group>"; };
705025A02843F0E7008D6624 /* ParseSchemaCombineTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseSchemaCombineTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1620,6 +1624,7 @@
7044C1F825C5CFAB0011F6E7 /* ParseFileCombineTests.swift */,
705A99F8259807F900B3547F /* ParseFileManagerTests.swift */,
705727882593FF8000F0ADD5 /* ParseFileTests.swift */,
704E782028D0D73E0075F952 /* ParseFileTransferableTests.swift */,
70BC0B32251903D1001556DB /* ParseGeoPointTests.swift */,
70F03A592780EAB000E5AFB4 /* ParseGitHubCombineTests.swift */,
70F03A5D2780EAC700E5AFB4 /* ParseGitHubTests.swift */,
Expand Down Expand Up @@ -2926,6 +2931,7 @@
7044C20625C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */,
70C5508525B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */,
7023800F2747FCCD00EFC443 /* ExtensionsTests.swift in Sources */,
704E782128D0D73E0075F952 /* ParseFileTransferableTests.swift in Sources */,
917BA43E2703E84000F8D747 /* ParseOperationAsyncTests.swift in Sources */,
7037DAB226384DE1005D7E62 /* TestParseEncoder.swift in Sources */,
7004C24D25B69207005E0AD9 /* ParseRoleTests.swift in Sources */,
Expand Down Expand Up @@ -3246,6 +3252,7 @@
7044C20825C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */,
70C5508725B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */,
702380112747FCCD00EFC443 /* ExtensionsTests.swift in Sources */,
704E782328D0D73E0075F952 /* ParseFileTransferableTests.swift in Sources */,
917BA4402703E84000F8D747 /* ParseOperationAsyncTests.swift in Sources */,
7037DAB426384DE1005D7E62 /* TestParseEncoder.swift in Sources */,
7004C26125B6920B005E0AD9 /* ParseRoleTests.swift in Sources */,
Expand Down Expand Up @@ -3369,6 +3376,7 @@
7044C20725C5D6780011F6E7 /* ParseQueryCombineTests.swift in Sources */,
70C5508625B4A68700B5DBC2 /* ParseOperationTests.swift in Sources */,
702380102747FCCD00EFC443 /* ExtensionsTests.swift in Sources */,
704E782228D0D73E0075F952 /* ParseFileTransferableTests.swift in Sources */,
917BA43F2703E84000F8D747 /* ParseOperationAsyncTests.swift in Sources */,
7037DAB326384DE1005D7E62 /* TestParseEncoder.swift in Sources */,
7004C25725B6920A005E0AD9 /* ParseRoleTests.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Sources/ParseSwift/API/Responses.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ internal struct LoginSignupResponse: Codable {
}

// MARK: ParseFile
internal struct FileUploadResponse: Decodable {
internal struct FileUploadResponse: Codable {
let name: String
let url: URL

Expand Down
54 changes: 36 additions & 18 deletions Sources/ParseSwift/Extensions/URLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,13 +95,15 @@ internal extension URLSession {
do {
responseData = try ParseCoding.jsonEncoder().encode(pushStatus)
} catch {
URLSession.parse.configuration.urlCache?.removeCachedResponse(for: request)
return .failure(ParseError(code: .unknownError, message: error.localizedDescription))
}
}
}
do {
return try .success(mapper(responseData))
} catch {
URLSession.parse.configuration.urlCache?.removeCachedResponse(for: request)
guard let parseError = error as? ParseError else {
guard JSONSerialization.isValidJSONObject(responseData),
let json = try? JSONSerialization
Expand Down Expand Up @@ -226,26 +228,42 @@ internal extension URLSession {
) {
var task: URLSessionTask?
if let data = data {
task = ParseSwift
.configuration
.parseFileTransfer
.upload(with: request, from: data) { (responseData, urlResponse, responseError) in
completion(self.makeResult(request: request,
responseData: responseData,
urlResponse: urlResponse,
responseError: responseError,
mapper: mapper))
do {
task = try ParseSwift
.configuration
.parseFileTransfer
.upload(with: request,
from: data) { (responseData, urlResponse, updatedRequest, responseError) in
completion(self.makeResult(request: updatedRequest ?? request,
responseData: responseData,
urlResponse: urlResponse,
responseError: responseError,
mapper: mapper))
}
} catch {
let defaultError = ParseError(code: .unknownError,
message: "Error uploading file: \(String(describing: error))")
let parseError = error as? ParseError ?? defaultError
completion(.failure(parseError))
}
} else if let file = file {
task = ParseSwift
.configuration
.parseFileTransfer
.upload(with: request, fromFile: file) { (responseData, urlResponse, responseError) in
completion(self.makeResult(request: request,
responseData: responseData,
urlResponse: urlResponse,
responseError: responseError,
mapper: mapper))
do {
task = try ParseSwift
.configuration
.parseFileTransfer
.upload(with: request,
fromFile: file) { (responseData, urlResponse, updatedRequest, responseError) in
completion(self.makeResult(request: updatedRequest ?? request,
responseData: responseData,
urlResponse: urlResponse,
responseError: responseError,
mapper: mapper))
}
} catch {
let defaultError = ParseError(code: .unknownError,
message: "Error uploading file: \(String(describing: error))")
let parseError = error as? ParseError ?? defaultError
completion(.failure(parseError))
}
} else {
completion(.failure(ParseError(code: .unknownError, message: "data and file both cannot be nil")))
Expand Down
73 changes: 65 additions & 8 deletions Sources/ParseSwift/Protocols/ParseFileTransferable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ public protocol ParseFileTransferable: AnyObject {
request type, and so on.
- parameter fileURL: The URL of the file to upload.
- parameter completion: The completion handler to call when the load request
is complete. This handler is executed on the delegate queue.
is complete. Should be in the form `(Data?, URLResponse?, URLRequest?, Error?)`.
`Data` and `URLResponse` should be created using `makeSuccessfulUploadResponse()`.
`URLRequest` is the request used to upload the file if available. `Error` is any error that occured
that prevented the file upload.
*/
func upload(with request: URLRequest,
fromFile fileURL: URL,
completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask
completion: @escaping (Data?, URLResponse?, URLRequest?, Error?) -> Void) throws -> URLSessionUploadTask

/**
Creates a task that performs an HTTP request for the specified URL request
Expand All @@ -36,23 +39,77 @@ public protocol ParseFileTransferable: AnyObject {
request type, and so on.
- parameter bodyData: The body data for the request.
- parameter completion: The completion handler to call when the load request
is complete. This handler is executed on the delegate queue.
is complete. Should be in the form `(Data?, URLResponse?, URLRequest?, Error?)`.
`Data` and `URLResponse` should be created using `makeSuccessfulUploadResponse()`.
`URLRequest` is the request used to upload the file if available. `Error` is any error that occured
that prevented the file upload.
*/
func upload(with request: URLRequest,
from bodyData: Data?,
completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask
completion: @escaping (Data?, URLResponse?, URLRequest?, Error?) -> Void) throws -> URLSessionUploadTask

/**
Compose a valid file upload response with a name and url.
Use this method after uploading a file to any file storage to
respond to the Swift SDK upload request.
- parameter name: The name of the file.
- parameter url: The url of the file that was stored.
- returns: A tuple of `(Data, HTTPURLResponse?)` where `Data` is the
JSON encoded file upload response and `HTTPURLResponse` is the metadata
associated with the response to the load request.
*/
func makeSuccessfulUploadResponse(_ name: String, url: URL) throws -> (Data, HTTPURLResponse?)

/**
Compose a dummy upload task.
Use this method if you do not need the Parse Swift SDK to start
your upload task for you.
- returns: A dummy upload task that starts an upload of zero bytes
to localhost.
- throws: An error of type `ParseError`.
*/
func makeDummyUploadTask() throws -> URLSessionUploadTask
}

// MARK: Default Implementation - Internal
extension ParseFileTransferable {
func upload(with request: URLRequest,
fromFile fileURL: URL,
completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask {
URLSession.parse.uploadTask(with: request, fromFile: fileURL, completionHandler: completion)
// swiftlint:disable:next line_length
completion: @escaping (Data?, URLResponse?, URLRequest?, Error?) -> Void) throws -> URLSessionUploadTask {
URLSession.parse.uploadTask(with: request, fromFile: fileURL) { (data, response, error) in
completion(data, response, request, error)
}
}

func upload(with request: URLRequest,
from bodyData: Data?,
completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask {
URLSession.parse.uploadTask(with: request, from: bodyData, completionHandler: completion)
// swiftlint:disable:next line_length
completion: @escaping (Data?, URLResponse?, URLRequest?, Error?) -> Void) throws -> URLSessionUploadTask {
URLSession.parse.uploadTask(with: request, from: bodyData) { (data, response, error) in
completion(data, response, request, error)
}
}
}

// MARK: Default Implementation - Public
public extension ParseFileTransferable {
func makeSuccessfulUploadResponse(_ name: String, url: URL) throws -> (Data, HTTPURLResponse?) {
let responseData = FileUploadResponse(name: name, url: url)
let response = HTTPURLResponse(url: url,
statusCode: 200,
httpVersion: nil,
headerFields: nil)
let encodedResponseData = try ParseCoding.jsonEncoder().encode(responseData)
return (encodedResponseData, response)
}

func makeDummyUploadTask() throws -> URLSessionUploadTask {
guard let url = URL(string: "http://localhost") else {
throw ParseError(code: .unknownError, message: "Could not create URL")
}
return URLSession.shared.uploadTask(with: .init(url: url), from: Data())
}
}
Loading

0 comments on commit e2827c5

Please sign in to comment.