SwagGen icon indicating copy to clipboard operation
SwagGen copied to clipboard

Request: Combine support

Open VyrCossont opened this issue 5 years ago • 5 comments

I've started working on SwagGen support for Apple's new Combine reactive programming library over in the swift-combine branch of my fork as an alternate template set, since I want it for a SwiftUI project. Using the closure parameters to APIClient and RequestBehaviour as an adapter to Combine, as suggested in the readme, probably won't help here, since Combine has uniform backpressure and cancellation support, and since I'd also like to get rid of Alamofire as a dependency.

Still working on file upload support since Apple doesn't seem to have Combine-ized data upload tasks yet, but I'll open a PR once this is a little more complete.

VyrCossont avatar Aug 13 '19 02:08 VyrCossont

Hey @VyrCossont I'm wondering what your status is on this

moritzsternemann avatar Dec 09 '19 20:12 moritzsternemann

I've got a feature branch here, implemented as a new folder of Stencil templates called SwiftCombine: https://github.com/VyrCossont/SwagGen/tree/swift-combine

Basics are functional; I've got enough working to support a simple Mastodon client, but development has been on hold for the last few months due to other work and life stuff. Can take a look this weekend at what's missing.

On Mon, Dec 9, 2019 at 12:04 Moritz Sternemann [email protected] wrote:

Hey @VyrCossont https://github.com/VyrCossont I'm wondering what your status is on this

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/yonaskolb/SwagGen/issues/190?email_source=notifications&email_token=AGH3MYGWKBXIMVLZTSC5HWLQX2QDLA5CNFSM4ILGPTBKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEGKPVRA#issuecomment-563411652, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGH3MYD3GKID5EDGIW3RCOLQX2QDLANCNFSM4ILGPTBA .

VyrCossont avatar Dec 11 '19 09:12 VyrCossont

If you need Combine support (or any other asynchronous framework), you should just be able to add an extension on APIClient that wraps the existing completion block

yonaskolb avatar Dec 11 '19 09:12 yonaskolb

I know that I am a bit late, but I implemented something similar to what @yonaskolb mentioned in the above comment.

(The code bellow has some other components not here specified, but simple to assume)

//

import Combine
import Foundation
import SwaggerAPI

extension APIClient {

    func request<T: APIResponseValue>(_ request: APIRequest<T>,
                                      behaviours: [RequestBehaviour] = [],
                                      completionQueue: DispatchQueue = DispatchQueue.main) -> Publishers.DataPublisher<T> {
        return Publishers.DataPublisher<T>(apiClient: self,
                                           request: request,
                                           behaviours: behaviours,
                                           completionQueue: completionQueue)
    }
}

extension Publishers {

    struct DataPublisher<T: APIResponseValue>: Publisher {
        typealias Output = T.SuccessType
        typealias Failure = NSError

        private let apiClient: APIClient
        private let request: APIRequest<T>
        private let behaviours: [RequestBehaviour]
        private let completionQueue: DispatchQueue

        init(apiClient: APIClient,
             request: APIRequest<T>,
             behaviours: [RequestBehaviour],
             completionQueue: DispatchQueue) {
            self.apiClient = apiClient
            self.request = request
            self.behaviours = behaviours
            self.completionQueue = completionQueue

        }

        func receive<S: Subscriber>(subscriber: S) where
            DataPublisher.Failure == S.Failure, DataPublisher.Output == S.Input {

            let subscription = DataSubscription(apiClient: self.apiClient,
                                                request: self.request,
                                                behaviours: self.behaviours,
                                                completionQueue: self.completionQueue,
                                                subscriber: subscriber)

            subscriber.receive(subscription: subscription)
        }
    }
}

private extension Publishers {

    class DataSubscription<S: Subscriber, T: APIResponseValue>: Subscription where S.Input == T.SuccessType, S.Failure == NSError {
        let apiClient: APIClient
        let request: APIRequest<T>
        let behaviours: [RequestBehaviour]
        let completionQueue: DispatchQueue
        private var subscriber: S?

        init(apiClient: APIClient,
             request: APIRequest<T>,
             behaviours: [RequestBehaviour] = [],
             completionQueue: DispatchQueue = DispatchQueue.main,
             subscriber: S) {
            self.apiClient = apiClient
            self.request = request
            self.behaviours = behaviours
            self.completionQueue = completionQueue
            self.subscriber = subscriber
        }

        func request(_ demand: Subscribers.Demand) {
            self.sendRequest()
        }

        func cancel() {
            subscriber = nil
        }

        private func sendRequest() {
            self.apiClient.makeRequest(request, completionQueue: completionQueue) { [weak self] response in
                guard let subscriber = self?.subscriber else { return }

                let result = response.result

                switch result {
                case .failure(let error):
                    print(error)
                    let applicationError = error.toNSError(withPrefix: "")

                    subscriber.receive(completion: Subscribers.Completion.failure(applicationError))
                case .success(let response):
                    if let successResult = response.success {
                        _  = subscriber.receive(successResult)
                        return
                    }

                    let result = (response.response as? SGErrorsObject) ?? []
                    let error = NSError.makeError(withStatusCode: response.statusCode, failures: result)
                    subscriber.receive(completion: Subscribers.Completion.failure(error))
                }
            }
        }
    }
}

jrcmramos avatar Apr 12 '20 14:04 jrcmramos

I've got a feature branch here, implemented as a new folder of Stencil templates called SwiftCombine: https://github.com/VyrCossont/SwagGen/tree/swift-combine Basics are functional; I've got enough working to support a simple Mastodon client, but development has been on hold for the last few months due to other work and life stuff. Can take a look this weekend at what's missing.

Hey @VyrCossont, I was able to get your feature branch up and running pretty easily, thanks for sharing! Besides the request.service.isUpload, what do you think is missing?

csjones avatar Sep 06 '20 00:09 csjones