Skip to content

Commit 7e80669

Browse files
gayathrisairamGayathri Sairamkrishnan
and
Gayathri Sairamkrishnan
authored
Conform RuntimeError to HTTPResponseConvertible (#135)
### Motivation apple/swift-openapi-generator#609 (comment) ### Modifications Confirm `RuntimeError` to `HTTPResponseConvertible` and provide granular status codes. ### Result Response codes for bad user input will be 4xx (instead of 500) ### Test Plan Unit tests. --------- Co-authored-by: Gayathri Sairamkrishnan <[email protected]>
1 parent 56e9fea commit 7e80669

File tree

2 files changed

+100
-0
lines changed

2 files changed

+100
-0
lines changed

Sources/OpenAPIRuntime/Errors/RuntimeError.swift

+23
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414
import protocol Foundation.LocalizedError
1515
import struct Foundation.Data
16+
import HTTPTypes
1617

1718
/// Error thrown by generated code.
1819
internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, PrettyStringConvertible {
@@ -141,3 +142,25 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret
141142
@_spi(Generated) public func throwUnexpectedResponseBody(expectedContent: String, body: any Sendable) throws -> Never {
142143
throw RuntimeError.unexpectedResponseBody(expectedContent: expectedContent, body: body)
143144
}
145+
146+
/// HTTP Response status definition for ``RuntimeError``.
147+
extension RuntimeError: HTTPResponseConvertible {
148+
/// HTTP Status code corresponding to each error case
149+
public var httpStatus: HTTPTypes.HTTPResponse.Status {
150+
switch self {
151+
case .invalidServerURL, .invalidServerVariableValue, .pathUnset: .notFound
152+
case .invalidExpectedContentType, .unexpectedContentTypeHeader: .unsupportedMediaType
153+
case .missingCoderForCustomContentType: .unprocessableContent
154+
case .unexpectedAcceptHeader: .notAcceptable
155+
case .failedToDecodeStringConvertibleValue, .invalidAcceptSubstring, .invalidBase64String,
156+
.invalidHeaderFieldName, .malformedAcceptHeader, .missingMultipartBoundaryContentTypeParameter,
157+
.missingOrMalformedContentDispositionName, .missingRequiredHeaderField,
158+
.missingRequiredMultipartFormDataContentType, .missingRequiredQueryParameter, .missingRequiredPathParameter,
159+
.missingRequiredRequestBody, .unsupportedParameterStyle:
160+
.badRequest
161+
case .handlerFailed, .middlewareFailed, .missingRequiredResponseBody, .transportFailed,
162+
.unexpectedResponseStatus, .unexpectedResponseBody:
163+
.internalServerError
164+
}
165+
}
166+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftOpenAPIGenerator open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftOpenAPIGenerator project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import HTTPTypes
16+
@_spi(Generated) @testable import OpenAPIRuntime
17+
import XCTest
18+
19+
struct MockRuntimeErrorHandler: Sendable {
20+
var failWithError: (any Error)? = nil
21+
func greet(_ input: String) async throws -> String {
22+
if let failWithError { throw failWithError }
23+
guard input == "hello" else { throw TestError() }
24+
return "bye"
25+
}
26+
27+
static let requestBody: HTTPBody = HTTPBody("hello")
28+
static let responseBody: HTTPBody = HTTPBody("bye")
29+
}
30+
31+
final class Test_RuntimeError: XCTestCase {
32+
func testRuntimeError_withUnderlyingErrorNotConforming_returns500() async throws {
33+
let server = UniversalServer(
34+
handler: MockRuntimeErrorHandler(failWithError: RuntimeError.transportFailed(TestError())),
35+
middlewares: [ErrorHandlingMiddleware()]
36+
)
37+
let response = try await server.handle(
38+
request: .init(soar_path: "/", method: .post),
39+
requestBody: MockHandler.requestBody,
40+
metadata: .init(),
41+
forOperation: "op",
42+
using: { MockRuntimeErrorHandler.greet($0) },
43+
deserializer: { request, body, metadata in
44+
let body = try XCTUnwrap(body)
45+
return try await String(collecting: body, upTo: 10)
46+
},
47+
serializer: { output, _ in fatalError() }
48+
)
49+
XCTAssertEqual(response.0.status, .internalServerError)
50+
}
51+
52+
func testRuntimeError_withUnderlyingErrorConforming_returnsCorrectStatusCode() async throws {
53+
let server = UniversalServer(
54+
handler: MockRuntimeErrorHandler(failWithError: TestErrorConvertible.testError("Test Error")),
55+
middlewares: [ErrorHandlingMiddleware()]
56+
)
57+
let response = try await server.handle(
58+
request: .init(soar_path: "/", method: .post),
59+
requestBody: MockHandler.requestBody,
60+
metadata: .init(),
61+
forOperation: "op",
62+
using: { MockRuntimeErrorHandler.greet($0) },
63+
deserializer: { request, body, metadata in
64+
let body = try XCTUnwrap(body)
65+
return try await String(collecting: body, upTo: 10)
66+
},
67+
serializer: { output, _ in fatalError() }
68+
)
69+
XCTAssertEqual(response.0.status, .badGateway)
70+
}
71+
}
72+
73+
enum TestErrorConvertible: Error, HTTPResponseConvertible {
74+
case testError(String)
75+
/// HTTP status code for error cases
76+
public var httpStatus: HTTPTypes.HTTPResponse.Status { .badGateway }
77+
}

0 commit comments

Comments
 (0)