-
Notifications
You must be signed in to change notification settings - Fork 126
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
URLError: localized error message and precise error code are lost #302
Comments
Looking at this, it appears this is coming from the transport-agnostic I'm wondering if we removed one layer of wrapping here1 then the underlying error would be the URLError, rather than a OpenAPIRuntime.RuntimeError. @groue is the principal complaint that we taint the localised description or that you cannot get your hands on the underlying error, or both? Footnotes |
So, if we remove that layer, like so... --- a/Sources/OpenAPIRuntime/Interface/UniversalClient.swift
+++ b/Sources/OpenAPIRuntime/Interface/UniversalClient.swift
@@ -130,7 +130,7 @@ public struct UniversalClient: Sendable {
operationID: operationID
)
} mapError: { error in
- RuntimeError.transportFailed(error)
+ error
}
}
for middleware in middlewares.reversed() {
...then we are able to do this... struct MockClientTransport: ClientTransport {
func send(_ request: Request, baseURL: URL, operationID: String) async throws -> Response {
throw URLError(.notConnectedToInternet)
}
}
let client = Client(serverURL: URL(string: "not used")!, transport: MockClientTransport())
do {
_ = try await client.getGreeting(.init())
} catch let error as ClientError {
if let urlError = error.underlyingError as? URLError {
print(urlError.code)
print(urlError.localizedDescription)
}
} We'd need to think through the implications of this change, but presumably this is the concrete ask @groue? |
I wonder if ClientError should have both |
Hello @simonjbeaumont, @czechboy0 The main issue is the impossibility to access the raw Please excuse me for talking about the "message corruption" in the initial issue. This only happens in internal apis, and I could only observe it indirectly, by printing the error. I should not have presented this as a problem. It's difficult to me to suggest a refactoring, because I'm not familiar with OpenAPI error types and cases, and their respective goals. In particular, I'm unsure which cases are intended for debugging feedback (in which case the api contract should be weak), and which errors are expected to be used for control flow in the client app (in which case the api contract must be very strong). I do intend to use the What I can note, though, is that the So... I can finally share an idea with you 😇 Please update the documentation of That would become the strong contract on which client apps could rely on, in order to write control flow based on // One can't be more straightforward
let clientError: ClientError
// Thanks to the new documentation of ClientError.underlyingError,
// I'm 100% guaranteed that this is the way to get the URLError
// thrown by URLSession, and perform reliable control flow.
if let urlError = clientError.underlyingError as? URLError {
// Perform control flow with the raw URLError
} else {
// Other errors thrown by URLSessionTransport,
// or by OpenAPI.
} Disclaimer: I'm not familiar with the error system of the library, so it's likely I forget other important use cases when I'm focusing on my own 😅 It's a frequent bias of new users 😬 Note: It is in the pitch for typed throws in the Swift forums that I learned about errors used for "debugging" (weak api contract) vs. errors used for "control flow" (strong api contract, with respect for source stability in the client applications, and totally in the realm of semantic versioning). I found these concepts useful, that's how I could suggest a documentation update, above. And of course the main course about the philosophy of errors is the enlightening Precise error typing in Swift. EDIT: If my suggestion for updating the documentation of A possible alternative would be that let clientError: ClientError
// Fine as well, as long as there exists a documented contract
// enforced by tests in the OpenAPI libraries.
if let transportError = clientError.underlyingError as? URLSessionTransportError,
let urlError = transportError.underlyingError as? URLError
{
// Perform control flow with the raw URLError
} else {
// Other errors thrown by URLSessionTransport,
// or by OpenAPI.
} |
Thanks for taking the time to crystallise this @groue. I think it's a reasonable ask that the I think it's also reasonable for the documentation for Of course, this project cannot make any guarantee that the transport implementation follows this recommendation so adopters would then be in the hands of those packages. The only API contract that the OpenAPIRuntime package could make is to faithfully propagate the underlying error from the transport, which may or may not be useful. In the case of the URLSessionTransport, we can make that happen, of course. I haven't formed a strong opinion on whether What do you think to all this @czechboy0? |
Thanks for your feedback, @simonjbeaumont. Let's wait for @czechboy0. Meanwhile:
I agree. Yet we have a way to strongly encourage transport implementers to be virtuous, with yet another documentation change, in /// Sends the underlying HTTP request and returns the received HTTP response.
+///
+/// Errors thrown by this method are rethrown, unmodified, by the
+/// `Client` types generated by the Swift OpenAPI Generator. It is
+/// strongly recommended that your implementation throws public
+/// and well-documented errors. This will help developers who use
+/// generated clients perform reliable error handling. The Implement a custom client transport section could contain a similar note as well. We can never force people to implement protocol semantics. But we can document them, explain the intent behind them, and reveal what can turn wrong if they are not honored. For example, we can not force people to implement matching implementations for
|
I agree we should keep the original thrown error in underlyingError, but also add a causeDescription, because the wrapping today does add information that we don't want to lose, which explains what was happening when the underlying error was thrown. Implementation-wise, I suspect we could move RuntimeError's description to be returned by ClientError's hypothetical new causeDescription, and keep underlyingError reserved for errors thrown by subsystems. That means it might be nil in many cases, for example when there's a missing header, the ClientError would only have causeDescription with that information, and a nil underlyingError. Since this is practically a breaking change, we should align it with a minor bump pre-1.0, possibly even with 0.3.0 going out next week, if the change can be done by early next week. |
How is it a breaking change? The new |
Yes, but for many other cases, underlyingError would become nil, where it was not nil before (since people should move to causeDescription to understand the context of what threw the underlying error). |
#63) Improved enriched error propagation from the transport and middlewares ### Motivation Fixes apple/swift-openapi-generator#302 and apple/swift-openapi-generator#17. The issue was that we hid away errors thrown in transports and middlewares, and the adopter would get `ClientError` where the `underlyingError` wasn't the error thrown by the underlying transport/middleware, but instead a private wrapper of ours. ### Modifications Make sure `{Client,Server}Error.underlyingError` contains the error thrown from the underlying transport/middleware when that was the cause of the error, otherwise keep `RuntimeError` there as was the behavior until now. Also added a `causeDescription` property on both public error types to allow communicating the context for the underlying error. Also made sure middleware errors are now wrapped in Client/ServerError, they weren't before so didn't contain the context necessary to debug issues well. ### Result Adopters can now extract the errors thrown e.g. by URLSession from our public error types using the `underlyingError` property and understand the context of where it was thrown by checking the user-facing `causeDescription`. Also, adopters now get enriched errors thrown from middlewares. ### Test Plan Wrote unit tests for both UniversalClient and UniversalServer, inevitably found some minor bugs there as well, fixed them all, plus the unit tests now verify the behavior new in this PR. Reviewed by: glbrntt Builds: ✔︎ pull request validation (5.10) - Build finished. ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. ✖︎ pull request validation (api breakage) - Build finished. #63
OpenAPIRuntime makes it impossible to access precise error localized message and codes.
To reproduce:
Client
with aURLSessionTransport
Expected: It is possible to grab the original cause of the error, and the original localized message of the underlying URLError. Very precisely, I expect to be able to detect the
notConnectedToInternet
code ofURLError
, and confidently display the excellent French message "La connexion Internet semble interrompue." that is embedded in theNSLocalizedDescription
key of the URLError. This is very important for a quality user interface. Experience shows thatURLError
messages are very suitable for display (at least for some specific codes).What happens instead: I get a
ClientError
whoseunderlyingError
is aRuntimeError
(internal to OpenAPIRuntime) that hides the original error in itstransportFailed
case, and corrupts the localized message in itsprettyDescription
by prepending English text.In summary, please don't hide the raw errors. I opted in for
URLSession
by providingURLSessionTransport
: I expect to be able to get some genuineURLError
at some point, along with their high quality information and localized messages, without any information loss.The text was updated successfully, but these errors were encountered: