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

Add async request helpers #380

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,14 @@ The response handlers for each request type are similar to one another. The argu

Note: Response handlers are required for all endpoints. Progress handlers, on the other hand, are optional for all endpoints.

#### Swift Concurrency

As of the 10.0.0 release, all of the request types also support Swift Concurrency (`async`/`await`) via the async `response()` function.

```swift
let response = try await client.files.createFolder(path: "/test/path/in/Dropbox/account").response()
```

---

### Request types
Expand Down
51 changes: 51 additions & 0 deletions Source/SwiftyDropbox/Shared/Handwritten/Request+Async.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// Copyright (c) 2023 Dropbox Inc. All rights reserved.
//

import Foundation

public protocol HasRequestResponse {
associatedtype ValueType
associatedtype ESerial: JSONSerializer

@discardableResult
func response(
queue: DispatchQueue?,
completionHandler: @escaping (ValueType?, CallError<ESerial.ValueType>?) -> Void
) -> Self
}

@available(iOS 13.0, macOS 10.15, *)
extension HasRequestResponse {
/// Async wrapper for retrieving a request's response
///
/// This could have a better name, but this avoids a collision with the other `response` methods
public func responseResult(
) async -> Result<ValueType, CallError<ESerial.ValueType>> {
await withCheckedContinuation { continuation in
self.response(queue: nil) { result, error in
if let result {
continuation.resume(returning: .success(result))
} else if let error {
continuation.resume(returning: .failure(error))
} else {
// this should never happen
continuation.resume(returning: .failure(.clientError(.unexpectedState)))
}
}
}
}

/// Async wrapper for retrieving a request's response
///
/// Same thing as `responseResult` but using async throws instead of returing a Result
public func response(
) async throws -> ValueType {
try await responseResult().get()
}
}

extension RpcRequest: HasRequestResponse {}
extension UploadRequest: HasRequestResponse {}
extension DownloadRequestFile: HasRequestResponse {}
extension DownloadRequestMemory: HasRequestResponse {}
35 changes: 35 additions & 0 deletions Source/SwiftyDropboxUnitTests/Request+Async.test.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
///
/// Copyright (c) 2023 Dropbox, Inc. All rights reserved.
///

@testable import SwiftyDropbox
import XCTest

@available(iOS 13.0, macOS 10.15, *)
final class RequestAsyncTests: XCTestCase {
func testRpcResponseFails() async throws {
let mockTransferClient = MockDropboxTransportClient()
mockTransferClient.mockRequestHandler = MockDropboxTransportClient.alwaysFailMockRequestHandler
let apiClient = DropboxClient(transportClient: mockTransferClient)

let exp = expectation(description: "should fail")
do {
_ = try await apiClient.check.user().response()
XCTFail("This should fail")
} catch {
exp.fulfill()
}
await fulfillment(of: [exp])
}

func testRpcResponseSucceeds() async throws {
let mockTransferClient = MockDropboxTransportClient()
mockTransferClient.mockRequestHandler = { request in
try? request.handleMockInput(.success(json: [:]))
}
let apiClient = DropboxClient(transportClient: mockTransferClient)

let userCheck = try await apiClient.check.user().response()
XCTAssertNotNil(userCheck)
}
}
Loading