Skip to content

Commit 7fe0b73

Browse files
author
Gayathri Sairamkrishnan
committed
Proposal for error handling
1 parent 7c36ba9 commit 7fe0b73

File tree

1 file changed

+151
-0
lines changed
  • Sources/swift-openapi-generator/Documentation.docc/Proposals

1 file changed

+151
-0
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# SOAR-0011: Improved Error Handling
2+
3+
Improve error handling by adding the ability for mapping application errors to HTTP responses.
4+
5+
## Overview
6+
7+
- Proposal: SOAR-0011
8+
- Author(s): [Gayathri Sairamkrishnan](https://github.com/gayathrisairam)
9+
- Status: **Awaiting Review**
10+
- Issue: [apple/swift-openapi-generator#609](https://github.com/apple/swift-openapi-generator/issues/609)
11+
- Affected components:
12+
- runtime
13+
14+
### Introduction
15+
16+
The goal of this proposal to improve the current error handling mechanism in Swift OpenAPI runtime. The proposal
17+
introduces a way for users to map errors thrown by their handlers to specific HTTP responses.
18+
19+
### Motivation
20+
21+
When implementing a server with Swift OpenAPI Generator, users implement a type that conforms to a generated protocol, providing one method for each API operation defined in the OpenAPI document. At runtime, if this function throws, the runtime library transforms this into a 500 HTTP response (Internal Error).
22+
23+
Instead, server developers may want to map errors thrown by the application to a more specific HTTP response.
24+
Currently, this can be achieved by checking for each error type in each handler's catch block, converting it to an
25+
appropriate HTTP response and returning it.
26+
27+
For example,
28+
```swift
29+
func getGreeting(_ input: Operations.getGreeting.Input) async throws -> Operations.getGreeting.Output {
30+
do {
31+
let response = try callGreetingLib()
32+
return .ok(.init(body: response))
33+
} catch let error {
34+
switch error {
35+
case GreetingError.authorizationError:
36+
return (HTTPResponse(status: 404), nil)
37+
case GreetingError.timeout:
38+
return ...
39+
}
40+
}
41+
}
42+
```
43+
If a user wishes to map many errors, the error handling block scales linearly and introduces a lot of ceremony.
44+
45+
### Proposed solution
46+
47+
The proposed solution is twofold.
48+
49+
1. Provide protocol(s) in `OpenAPIRuntime` to allow users to extend their error types with mappings to HTTP responses.
50+
51+
2. Provide an (opt-in) middleware in OpenAPIRuntime that will call the conversion function on conforming error types when
52+
constructing the HTTP response.
53+
54+
Vapor has a similar mechanism called [AbortError](https://docs.vapor.codes/basics/errors/).
55+
56+
HummingBird also has an [error handling mechanism](https://docs.hummingbird.codes/2.0/documentation/hummingbird/errorhandling/)
57+
by allowing users to define a [HTTPError](https://docs.hummingbird.codes/2.0/documentation/hummingbird/httperror)
58+
59+
The proposal aims to provide a transport agnostic error handling mechanism for OpenAPI users.
60+
61+
### Detailed design
62+
63+
#### Proposed Error protocols
64+
65+
Users can choose to conform to one of the protocols described below depending on the level of specificity they would
66+
like to have in the response.
67+
68+
```swift
69+
public protocol HTTPStatusConvertible {
70+
var httpStatus: HTTPResponse.Status { get }
71+
}
72+
73+
public protocol HTTPHeadConvertible: HTTPStatusConvertible {
74+
var httpHeaderFields: HTTPTypes.HTTPFields { get }
75+
}
76+
77+
public protocol HTTPResponseConvertible: HTTPHeadConvertible {
78+
var httpBody: OpenAPIRuntime.HTTPBody { get }
79+
}
80+
```
81+
82+
#### Proposed Error Middleware
83+
84+
The proposed error middleware in OpenAPIRuntime will convert the application error to the appropriate error response.
85+
```swift
86+
public struct ErrorMiddleware: ServerMiddleware {
87+
func intercept(_ request: HTTPTypes.HTTPRequest,
88+
body: OpenAPIRuntime.HTTPBody?,
89+
metadata: OpenAPIRuntime.ServerRequestMetadata,
90+
operationID: String,
91+
next: @Sendable (HTTPTypes.HTTPRequest, OpenAPIRuntime.HTTPBody?, OpenAPIRuntime.ServerRequestMetadata) async throws -> (HTTPTypes.HTTPResponse, OpenAPIRuntime.HTTPBody?)) async throws -> (HTTPTypes.HTTPResponse, OpenAPIRuntime.HTTPBody?) {
92+
do {
93+
return try await next(request, body, metadata)
94+
} catch let error as ServerError {
95+
if let appError = error.underlyingError as? HTTPStatusConvertible else {
96+
return (HTTPResponse(status: appError.httpStatus), nil)
97+
} else if ... {
98+
99+
}
100+
}
101+
}
102+
}
103+
```
104+
105+
Please note that the proposal places the responsibility to conform to the documented API in the hands of the user.
106+
There's no mechanism to prevent the users from inadvertently transforming a thrown error into an undocumented response.
107+
108+
#### Example usage
109+
110+
1. Create an error type that conforms to one of the error protocol(s)
111+
```swift
112+
extension MyAppError: HTTPStatusConvertible {
113+
var httpStatus: HTTPResponse.Status {
114+
switch self {
115+
case .invalidInputFormat:
116+
HTTPResponse.Status.badRequest
117+
case .authorizationError:
118+
HTTPResponse.Status.forbidden
119+
}
120+
}
121+
}
122+
```
123+
124+
2. Opt in to the error middleware while registering the handler
125+
126+
```swift
127+
let handler = try await RequestHandler()
128+
try handler.registerHandlers(on: transport, middlewares: [ErrorHandlingMiddleware()])
129+
130+
```
131+
132+
### API stability
133+
134+
This feature is purely additive:
135+
- Additional APIs in the runtime library
136+
137+
138+
### Future directions
139+
140+
A possible future direction is to add the error middleware by default by changing the [default value for the middlewares](https://github.com/apple/swift-openapi-runtime/blob/main/Sources/OpenAPIRuntime/Interface/UniversalServer.swift#L56)
141+
argument in handler initialisation.
142+
143+
### Alternatives considered
144+
145+
An alternative here is to invoke the error conversion function directly from OpenAPIRuntime's handler. The feature would
146+
still be opt-in as users have to explicitly conform to the new error protocols.
147+
148+
However, there is a rare case where an application might depend on a library (for eg: an auth library) which in turn
149+
depends on OpenAPIRuntime. If the authentication library conforms to the new error protocols, this would result in a
150+
breaking change for the application, whereas an error middleware provides flexibility to the user on whether they
151+
want to subscribe to the new behaviour or not.

0 commit comments

Comments
 (0)