apple / swift-openapi-generator

Generate Swift client and server code from an OpenAPI document.
https://swiftpackageindex.com/apple/swift-openapi-generator/documentation
Apache License 2.0
1.36k stars 103 forks source link

Protocol where each `Output` conforms to #574

Open laurensvm opened 3 months ago

laurensvm commented 3 months ago

Question

I would like to write one wrapError function that takes a generic Output and handles all the errors. For this I think we'd need to have a protocol to which all Output enums conform to. For instance:

enum OpenAPIResult<V: Sendable>: Sendable, Hashable {
    case ok(value: V)
    case badRequest(error: Components.Responses.ErrorResponse)
    case unauthorized(error: Components.Responses.ErrorResponse)
    case internalServerError(error: Components.Responses.ErrorResponse)
    case undocumented(statusCode: Int)
    // ... additional cases
}

 func get(id: Int) -> AnyPublisher<[Comment], ErrorResponse> {
        let response: Future<[Components.Schemas.Comment], ErrorResponse> = self.wrapError {
            try await self.client?.GetComments(path: .init(id: id)) as? OpenAPIResult<[Components.Schemas.Comment]>
        }

        return response
            .compactMap { arr -> [Comment] in
                arr.compactMap { $0.comment }
            }
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
}

func wrapError<V>(_ call: @escaping () async throws -> OpenAPIResult<V>) -> Future<V, ErrorResponse> {
    // Call completion and log errors for all the routes
}

I am not an advanced enough Swift programmer to know if this would actually work. I'm trying to avoid copying error handling logic for each of my routes. Perhaps you have any other solutions? Many thanks

czechboy0 commented 3 months ago

Hi @laurensvm,

this sounds similar to #522 - would that cover your use case as well?

laurensvm commented 3 months ago

Hi @czechboy0 ,

Thanks for your quick response.

Yes, this middleware should cover the logging/tracing part of the use case. However, I would also like to present the errors in a standardized way to the UI. In the example, ErrorResponse is a generic response that is consumed by the calling SwiftUI component.

I think having a generic Output such as OpenAPIResult in the example has added value in order to handle generic errors and to avoid code duplication. Right now I have to handle the possible Components.Responses.ErrorResponse's in each of the routes separately. The alternative is to ignore the error using try? await self.client?.GetComments(path: .init(id: id)).ok.body.json, but I feel like this solution disregards a lot of useful information.

Is it expected to do this for all the routes?

Thanks again for this package, I'm still very much a fan.

czechboy0 commented 3 months ago

The challenge here is that every operation might have completely different set of responses, and those responses might have different payloads. So you as the user familiar with your OpenAPI document can write a type like OpenAPIResult, but the generator can't know - because it treats every operation separately.

So my current recommendation would be to write an extensions/macro for your own project, with the requirement that all operations have identical non-success responses, and get some value out of it that way. And if you have ideas of how the generator could help here, please do share them. But I don't see a straightforward way to provide what you're asking right now in a way that generalizes to all OpenAPI docs.