diff --git a/Sources/OpenAPIRuntime/Errors/RuntimeError.swift b/Sources/OpenAPIRuntime/Errors/RuntimeError.swift index 549e3a13..e2ed3f21 100644 --- a/Sources/OpenAPIRuntime/Errors/RuntimeError.swift +++ b/Sources/OpenAPIRuntime/Errors/RuntimeError.swift @@ -13,7 +13,7 @@ //===----------------------------------------------------------------------===// import protocol Foundation.LocalizedError import struct Foundation.Data - +import HTTPTypes /// Error thrown by generated code. internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, PrettyStringConvertible { @@ -141,3 +141,43 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret @_spi(Generated) public func throwUnexpectedResponseBody(expectedContent: String, body: any Sendable) throws -> Never { throw RuntimeError.unexpectedResponseBody(expectedContent: expectedContent, body: body) } + +/// HTTP Response status definition for ``RuntimeError``. +extension RuntimeError: HTTPResponseConvertible { + public var httpStatus: HTTPTypes.HTTPResponse.Status { + switch self { + case .invalidServerURL, + .invalidServerVariableValue: + .notFound + case .invalidExpectedContentType, + .missingCoderForCustomContentType, + .unexpectedContentTypeHeader: + .unsupportedMediaType + case .unexpectedAcceptHeader(_): + .notAcceptable + case .missingOrMalformedContentDispositionName: + .unprocessableContent + case .failedToDecodeStringConvertibleValue, + .invalidAcceptSubstring, + .invalidBase64String, + .invalidHeaderFieldName, + .malformedAcceptHeader, + .missingMultipartBoundaryContentTypeParameter, + .missingRequiredHeaderField, + .missingRequiredMultipartFormDataContentType, + .missingRequiredQueryParameter, + .missingRequiredPathParameter, + .missingRequiredRequestBody, + .pathUnset, + .unsupportedParameterStyle: + .badRequest + case .handlerFailed, + .middlewareFailed, + .missingRequiredResponseBody, + .transportFailed, + .unexpectedResponseStatus, + .unexpectedResponseBody: + .internalServerError + } + } +} diff --git a/Tests/OpenAPIRuntimeTests/Errors/Test_RuntimeError.swift b/Tests/OpenAPIRuntimeTests/Errors/Test_RuntimeError.swift new file mode 100644 index 00000000..a3234369 --- /dev/null +++ b/Tests/OpenAPIRuntimeTests/Errors/Test_RuntimeError.swift @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import HTTPTypes +@_spi(Generated) @testable import OpenAPIRuntime +import XCTest + +struct MockRuntimeErrorHandler: Sendable { + var failWithError: (any Error)? = nil + func greet(_ input: String) async throws -> String { + if failWithError != nil { throw failWithError! } + guard input == "hello" else { throw TestError() } + return "bye" + } + + static let requestBody: HTTPBody = HTTPBody("hello") + static let responseBody: HTTPBody = HTTPBody("bye") +} + +final class Test_RuntimeError: XCTestCase { + func testRuntimeError_returnsCorrectStatusCode() async throws { + + let server = UniversalServer(handler: MockRuntimeErrorHandler(failWithError: RuntimeError.invalidServerURL("Invalid server URL")), + middlewares: [ErrorHandlingMiddleware()]) + let response = try await server.handle( + request: .init(soar_path: "/", method: .post), + requestBody: MockHandler.requestBody, + metadata: .init(), + forOperation: "op", + using: { MockRuntimeErrorHandler.greet($0) }, + deserializer: { request, body, metadata in + let body = try XCTUnwrap(body) + return try await String(collecting: body, upTo: 10) + }, + serializer: { output, _ in fatalError() } + ) + XCTAssertEqual(response.0.status, .notFound) + } + + func testRuntimeError_withUnderlyingErrorNotConfirming_returns500() async throws { + + let server = UniversalServer(handler: MockRuntimeErrorHandler(failWithError: RuntimeError.transportFailed(TestError())), + middlewares: [ErrorHandlingMiddleware()]) + let response = try await server.handle( + request: .init(soar_path: "/", method: .post), + requestBody: MockHandler.requestBody, + metadata: .init(), + forOperation: "op", + using: { MockRuntimeErrorHandler.greet($0) }, + deserializer: { request, body, metadata in + let body = try XCTUnwrap(body) + return try await String(collecting: body, upTo: 10) + }, + serializer: { output, _ in fatalError() } + ) + XCTAssertEqual(response.0.status, .internalServerError) + } + + func testRuntimeError_withUnderlyingErrorConfirming_returns500() async throws { + + let server = UniversalServer(handler: MockRuntimeErrorHandler(failWithError: TestErrorConvertible.testError("Test Error")), + middlewares: [ErrorHandlingMiddleware()]) + let response = try await server.handle( + request: .init(soar_path: "/", method: .post), + requestBody: MockHandler.requestBody, + metadata: .init(), + forOperation: "op", + using: { MockRuntimeErrorHandler.greet($0) }, + deserializer: { request, body, metadata in + let body = try XCTUnwrap(body) + return try await String(collecting: body, upTo: 10) + }, + serializer: { output, _ in fatalError() } + ) + XCTAssertEqual(response.0.status, .badGateway) + } +} + +enum TestErrorConvertible: Error, HTTPResponseConvertible { + case testError(String) + + public var httpStatus: HTTPTypes.HTTPResponse.Status { + .badGateway + } +} + +