yonaskolb / SwagGen

OpenAPI/Swagger 3.0 Parser and Swift code generator

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Request: Combine support

VyrCossont opened this issue · 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.

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

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

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))
                }
            }
        }
    }
}
commented

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?