- 
                Notifications
    You must be signed in to change notification settings 
- Fork 148
[Proposal] SOAR-0011: Improved error handling #626
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
          
     Merged
      
      
    
  
     Merged
                    Changes from 12 commits
      Commits
    
    
            Show all changes
          
          
            14 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      7fe0b73
              
                Proposal for error handling
              
              
                 b1481bd
              
                Update Sources/swift-openapi-generator/Documentation.docc/Proposals/S…
              
              
                gayathrisairam e29736a
              
                Update Sources/swift-openapi-generator/Documentation.docc/Proposals/S…
              
              
                gayathrisairam 205d78c
              
                Review comments
              
              
                 09bd716
              
                Update Sources/swift-openapi-generator/Documentation.docc/Proposals/S…
              
              
                gayathrisairam e6fcb19
              
                Add link to SOAR-0011 in Proposals.md
              
              
                 e4725c4
              
                Update Sources/swift-openapi-generator/Documentation.docc/Proposals/S…
              
              
                gayathrisairam fb20a73
              
                Merge branch 'main' into error_handling
              
              
                gayathrisairam 76d31c6
              
                Update proposal based on discussion from forums
              
              
                 c671f57
              
                Update error handling middleware name
              
              
                 71eb331
              
                Update Sources/swift-openapi-generator/Documentation.docc/Proposals/S…
              
              
                gayathrisairam 66f97a3
              
                Merge branch 'main' into error_handling
              
              
                gayathrisairam 55bd604
              
                Update Sources/swift-openapi-generator/Documentation.docc/Proposals/S…
              
              
                czechboy0 42d6259
              
                Merge branch 'main' into error_handling
              
              
                czechboy0 File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
        
          
          
            159 changes: 159 additions & 0 deletions
          
          159 
        
  Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0011.md
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| # SOAR-0011: Improved Error Handling | ||
|  | ||
| Improve error handling by adding the ability for mapping application errors to HTTP responses. | ||
|  | ||
| ## Overview | ||
|  | ||
| - Proposal: SOAR-0011 | ||
| - Author(s): [Gayathri Sairamkrishnan](https://github.com/gayathrisairam) | ||
| - Status: **Ready for Implementation** | ||
| - Issue: [apple/swift-openapi-generator#609](https://github.com/apple/swift-openapi-generator/issues/609) | ||
| - Affected components: | ||
| - runtime | ||
| - Versions: | ||
| - v1.0 (2024-09-19): Initial version | ||
| - v1.1(2024-10-07): | ||
| - Replace the proposed solution to have a single error handling protocol, with the status being required and | ||
| headers/body being optional. | ||
|  | ||
| ### Introduction | ||
|  | ||
| The goal of this proposal to improve the current error handling mechanism in Swift OpenAPI runtime. The proposal | ||
| introduces a way for users to map errors thrown by their handlers to specific HTTP responses. | ||
|  | ||
| ### Motivation | ||
|  | ||
| 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, it's up to the server transport to transform it into an HTTP response status code – for example, some transport use `500 Internal Error`. | ||
|  | ||
| Instead, server developers may want to map errors thrown by the application to a more specific HTTP response. | ||
| Currently, this can be achieved by checking for each error type in each handler's catch block, converting it to an | ||
| appropriate HTTP response and returning it. | ||
|  | ||
| For example, | ||
| ```swift | ||
| func getGreeting(_ input: Operations.getGreeting.Input) async throws -> Operations.getGreeting.Output { | ||
| do { | ||
| let response = try callGreetingLib() | ||
| return .ok(.init(body: response)) | ||
| } catch let error { | ||
| switch error { | ||
| case GreetingError.authorizationError: | ||
| return .unauthorized(.init()) | ||
| case GreetingError.timeout: | ||
| return ... | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
| If a user wishes to map many errors, the error handling block scales linearly and introduces a lot of ceremony. | ||
|  | ||
| ### Proposed solution | ||
|  | ||
| The proposed solution is twofold. | ||
|  | ||
| 1. Provide a protocol in `OpenAPIRuntime` to allow users to extend their error types with mappings to HTTP responses. | ||
|  | ||
| 2. Provide an (opt-in) middleware in OpenAPIRuntime that will call the conversion function on conforming error types when | ||
| constructing the HTTP response. | ||
|  | ||
| Vapor has a similar mechanism called [AbortError](https://docs.vapor.codes/basics/errors/). | ||
|  | ||
| Hummingbird also has an [error handling mechanism](https://docs.hummingbird.codes/2.0/documentation/hummingbird/errorhandling/) | ||
| by allowing users to define a [HTTPError](https://docs.hummingbird.codes/2.0/documentation/hummingbird/httperror) | ||
|  | ||
| The proposal aims to provide a transport agnostic error handling mechanism for OpenAPI users. | ||
|  | ||
| ### Detailed design | ||
|  | ||
| #### Proposed Error protocols | ||
|  | ||
| Users can choose to conform to the error handling protocol below and optionally provide the optional fields depending on | ||
| the level of specificity they would like to have in the response. | ||
|  | ||
| ```swift | ||
| public protocol HTTPResponseConvertible { | ||
| var httpStatus: HTTPResponse.Status { get } | ||
| var httpHeaderFields: HTTPTypes.HTTPFields { get } | ||
| var httpBody: OpenAPIRuntime.HTTPBody? { get } | ||
| } | ||
|  | ||
| extension HTTPResponseConvertible { | ||
| var httpHeaderFields: HTTPTypes.HTTPFields { [:] } | ||
| var httpBody: OpenAPIRuntime.HTTPBody? { nil } | ||
| } | ||
| ``` | ||
|  | ||
| #### Proposed Error Middleware | ||
|  | ||
| The proposed error middleware in OpenAPIRuntime will convert the application error to the appropriate error response. | ||
| It returns 500 for application error(s) that do not conform to HTTPResponseConvertible protocol. | ||
|  | ||
| ```swift | ||
| public struct ErrorHandlingMiddleware: ServerMiddleware { | ||
| func intercept(_ request: HTTPTypes.HTTPRequest, | ||
| body: OpenAPIRuntime.HTTPBody?, | ||
| metadata: OpenAPIRuntime.ServerRequestMetadata, | ||
| operationID: String, | ||
| next: @Sendable (HTTPTypes.HTTPRequest, OpenAPIRuntime.HTTPBody?, OpenAPIRuntime.ServerRequestMetadata) async throws -> (HTTPTypes.HTTPResponse, OpenAPIRuntime.HTTPBody?)) async throws -> (HTTPTypes.HTTPResponse, OpenAPIRuntime.HTTPBody?) { | ||
| do { | ||
| return try await next(request, body, metadata) | ||
| } catch let error as ServerError { | ||
| if let appError = error.underlyingError as? HTTPResponseConvertible else { | ||
| return (HTTPResponse(status: appError.httpStatus, headerFields: appError.httpHeaderFields), | ||
| appError.httpBody) | ||
| } else { | ||
| throw error | ||
| } | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|  | ||
| Please note that the proposal places the responsibility to conform to the documented API in the hands of the user. | ||
| There's no mechanism to prevent the users from inadvertently transforming a thrown error into an undocumented response. | ||
|  | ||
| #### Example usage | ||
|  | ||
| 1. Create an error type that conforms to the error protocol | ||
| ```swift | ||
| extension MyAppError: HTTPResponseConvertible { | ||
| var httpStatus: HTTPResponse.Status { | ||
| switch self { | ||
| case .invalidInputFormat: | ||
| .badRequest | ||
| case .authorizationError: | ||
| .forbidden | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|  | ||
| 2. Opt in to the error middleware while registering the handler | ||
|  | ||
| ```swift | ||
| let handler = try await RequestHandler() | ||
| try handler.registerHandlers(on: transport, middlewares: [ErrorHandlingMiddleware()]) | ||
|         
                  czechboy0 marked this conversation as resolved.
              Show resolved
            Hide resolved | ||
|  | ||
| ``` | ||
|  | ||
| ### API stability | ||
|  | ||
| This feature is purely additive: | ||
| - Additional APIs in the runtime library | ||
|  | ||
|  | ||
| ### Future directions | ||
|  | ||
| 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) | ||
| argument in handler initialisation. | ||
|  | ||
| ### Alternatives considered | ||
|  | ||
| An alternative here is to invoke the error conversion function directly from OpenAPIRuntime's handler. The feature would | ||
| still be opt-in as users have to explicitly conform to the new error protocols. | ||
|  | ||
| However, there is a rare case where an application might depend on a library (for eg: an auth library) which in turn | ||
| depends on OpenAPIRuntime. If the authentication library conforms to the new error protocols, this would result in a | ||
| breaking change for the application, whereas an error middleware provides flexibility to the user on whether they | ||
| want to subscribe to the new behaviour or not. | ||
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.