-
Notifications
You must be signed in to change notification settings - Fork 57
Pull Request: Add Error Handler Support to UniversalClient #162
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
base: main
Are you sure you want to change the base?
Conversation
- Adds new optional parameter to Configuration initializers
|
Hi @czechboy0, I've noticed error handling has been a recurring topic in discussions. This PR offers a simple, backward-compatible solution for error transformation and logging that many developers need. Key benefits:
The implementation is minimal and non-breaking. Would love to hear your thoughts on this approach. Thanks for your time and for maintaining swift-openapi-runtime! 🙏 |
|
Hi @winnisx7, thanks for the PR. You're right that this is an interesting topic that continues to evolve. Let me briefly react to the motivation (thanks for including that!) now and we can discuss more later.
That's right, at the moment the only place to catch all errors are outside of a call to the client. I think it's desirable to figure out if we can offer such a closure for observability purposes, one that doesn't change the error, just allows logging it in one place. Similar to your proposed design, just with the signature
This is partly a design choice - since the 1.0, all errors thrown by the client are
Did you check ClientError? That has a lot of context and if you just log the error, it'll contain plenty of detail. |
|
Hi @czechboy0 , |
|
Great questions!
Calls to methods on An operation call on The errors you encounter when using the convenience methods on Output can't throw ClientError, because they're not thrown by the Client. They're also not meant to be caught and inspected, they're only meant to be logged, more details in https://swiftpackageindex.com/apple/swift-openapi-generator/1.10.3/documentation/swift-openapi-generator/soar-0007 If you need to handle more than one status type, you should use a switch statement on the Output type, not the shorthand unwrap methods.
I don't think so, the proposal linked above goes into more details behind why it's deliberately designed this way.
Ultimately that needs to be handled by your application, just like errors thrown by other libraries, from Foundation to higher level libraries. One way that's worked for me in the past is: implement a middleware and record whatever information you need, and use Swift Log with metadata to include a request id. That way you'll be able to record all request and response information for all HTTP traffic, and when one of your requests results in a thrown error, you'll be able to find the request details in the logs based on the request id. |
|
Re: Error Handler Support - Evolved Proposal Hi @czechboy0 , Currently:
Why This Matters
Real Example: I believe this addresses a real gap in production observability while maintaining the clean separation of concerns in the current architecture. |
|
I think improving observability is always a great goal. We'll just want to be clear about what exact use case each additional feature serves, and make sure it composes well with existing features. For example, to track timings, distributed tracing can work really well - you'd put one span around your call to the client, and add a middleware at the last position in the stack that adds a span around the transport call. That way, you'll see in a tracing visualization tool how much time was spent outside the HTTP transport layer. For next steps, it might be good for you to file issues for the specific problems you'd like to solve, and optionally, if you have ideas, some proposed solutions for each. We can then discuss each topic in each issue, and once we narrow on a solution, we'll run it through our lightweight proposal process. Thanks again, I agree this area can use improvement 🙏 |
|
Hi @czechboy0, Thank you for the feedback. I want to clarify my immediate need and get your guidance on the best path forward. The Critical ProblemMy most urgent issue is tracking decoding failures in production. When response deserialization fails, I need to immediately capture this in our crash reporting system (like Crashlytics) for rapid response. This isn't something each client caller should handle individually - it needs centralized monitoring. Two Possible ApproachesI'm considering two solutions: Approach 1: Error Handler (Simple, Immediate)// In Configuration
let errorHandler: ((ClientError) -> Void)?
// Wraps UniversalClient.send() to catch all ClientErrorsPros:
Cons:
Approach 2: Operation Observer (Comprehensive, Better)// Full lifecycle visibility
protocol OperationObserver {
func didEncodeParameters(operationId: String, duration: TimeInterval)
func didReceiveResponse(operationId: String, response: HTTPResponse)
func didFailDecoding(operationId: String, error: Error)
// ... other hooks
}Pros:
Cons:
What I'm PlanningI'll likely implement Approach 1 to solve my immediate production need, since I can do it confidently and it's minimal. However, if you have design guidance or ideas on how a proper observability layer should work in swift-openapi-runtime (e.g., how it should compose with middleware, what hooks make sense, etc.), I'd be happy to attempt Approach 2 instead. Your architectural insights would make a huge difference here. What are your thoughts? Should I proceed with the simple error handler, or is there a clearer path to proper observability that I'm missing? |
|
If (1) would address your needs, I agree it's likely the right one to go for. We attempted and ultimately rejected a "mapper" approach earlier this year (https://github.com/apple/swift-openapi-runtime/pull/141/files), but I think you could take inspiration from that PR on how the closures were added on the Configuration struct. The implementation in your case will be simpler, because you just need an informative callback and not a mapper. Once you have the implementation working and unit tests passing, please write up a lightweight proposal so that we can get the rest of the community to chime in as well - doesn't need to be long, short and clear is usually preferred: https://swiftpackageindex.com/apple/swift-openapi-generator/1.10.3/documentation/swift-openapi-generator/proposals |
|
In unit tests, it'll be important to show that the callback sees errors from all these locations:
|
Adds new optional parameter to Configuration initializers
Motivation
Currently, swift-openapi-runtime provides limited options for centralized error handling, which creates several challenges for developers:
This change addresses these pain points by providing a single interception point for all errors with full context.
Modifications
Added errorHandler property to Configuration struct for custom error transformation
Result
When an error occurs, if an errorHandler is configured, it will transform the error before throwing
This allows for centralized error mapping, logging, or custom error type conversion
Flexibility: Enables custom error transformation without modifying core runtime code
Centralized Error Handling: Allows global error mapping in a single configuration point
Backward Compatible: The errorHandler is optional, maintaining compatibility with existing code
Test Plan