Skip to content

Fix error description forwarding #143

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 1 commit into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Sources/OpenAPIRuntime/Conversion/ErrorExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,18 @@ struct MultiError: Swift.Error, LocalizedError, CustomStringConvertible {
var description: String {
let combinedDescription =
errors.map { error in
guard let error = error as? (any PrettyStringConvertible) else { return error.localizedDescription }
guard let error = error as? (any PrettyStringConvertible) else { return "\(error)" }
return error.prettyDescription
}
.enumerated().map { ($0.offset + 1, $0.element) }.map { "Error \($0.0): [\($0.1)]" }.joined(separator: ", ")
return "MultiError (contains \(errors.count) error\(errors.count == 1 ? "" : "s")): \(combinedDescription)"
}

var errorDescription: String? { description }
var errorDescription: String? {
if let first = errors.first {
return "Mutliple errors encountered, first one: \(first.localizedDescription)."
} else {
return "No errors"
}
}
}
8 changes: 4 additions & 4 deletions Sources/OpenAPIRuntime/Errors/ClientError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,7 @@ public struct ClientError: Error {
// MARK: Private

fileprivate var underlyingErrorDescription: String {
guard let prettyError = underlyingError as? (any PrettyStringConvertible) else {
return underlyingError.localizedDescription
}
guard let prettyError = underlyingError as? (any PrettyStringConvertible) else { return "\(underlyingError)" }
return prettyError.prettyDescription
}
}
Expand All @@ -133,5 +131,7 @@ extension ClientError: LocalizedError {
/// This computed property provides a localized human-readable description of the client error, which is suitable for displaying to users.
///
/// - Returns: A localized string describing the client error.
public var errorDescription: String? { description }
public var errorDescription: String? {
"Client encountered an error invoking the operation \"\(operationID)\", caused by \"\(causeDescription)\", underlying error: \(underlyingError.localizedDescription)."
}
}
8 changes: 4 additions & 4 deletions Sources/OpenAPIRuntime/Errors/CodingErrors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ extension DecodingError: PrettyStringConvertible {
case .keyNotFound(let key, let context): output = "keyNotFound \(key) - \(context.prettyDescription)"
case .typeMismatch(let type, let context): output = "typeMismatch \(type) - \(context.prettyDescription)"
case .valueNotFound(let type, let context): output = "valueNotFound \(type) - \(context.prettyDescription)"
@unknown default: output = "unknown: \(localizedDescription)"
@unknown default: output = "unknown: \(self)"
}
return "DecodingError: \(output)"
}
Expand All @@ -30,7 +30,7 @@ extension DecodingError: PrettyStringConvertible {
extension DecodingError.Context: PrettyStringConvertible {
var prettyDescription: String {
let path = codingPath.map(\.description).joined(separator: "/")
return "at \(path): \(debugDescription) (underlying error: \(underlyingError?.localizedDescription ?? "<nil>"))"
return "at \(path): \(debugDescription) (underlying error: \(underlyingError.map { "\($0)" } ?? "<nil>"))"
}
}

Expand All @@ -39,7 +39,7 @@ extension EncodingError: PrettyStringConvertible {
let output: String
switch self {
case .invalidValue(let value, let context): output = "invalidValue \(value) - \(context.prettyDescription)"
@unknown default: output = "unknown: \(localizedDescription)"
@unknown default: output = "unknown: \(self)"
}
return "EncodingError: \(output)"
}
Expand All @@ -48,6 +48,6 @@ extension EncodingError: PrettyStringConvertible {
extension EncodingError.Context: PrettyStringConvertible {
var prettyDescription: String {
let path = codingPath.map(\.description).joined(separator: "/")
return "at \(path): \(debugDescription) (underlying error: \(underlyingError?.localizedDescription ?? "<nil>"))"
return "at \(path): \(debugDescription) (underlying error: \(underlyingError.map { "\($0)" } ?? "<nil>"))"
}
}
4 changes: 4 additions & 0 deletions Sources/OpenAPIRuntime/Errors/RuntimeError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ internal enum RuntimeError: Error, CustomStringConvertible, LocalizedError, Pret
return "Unexpected response body, expected content type: \(expectedContentType), body: \(body)"
}
}

// MARK: - LocalizedError

var errorDescription: String? { description }
}

/// Throws an error to indicate an unexpected HTTP response status.
Expand Down
8 changes: 4 additions & 4 deletions Sources/OpenAPIRuntime/Errors/ServerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,7 @@ public struct ServerError: Error {
// MARK: Private

fileprivate var underlyingErrorDescription: String {
guard let prettyError = underlyingError as? (any PrettyStringConvertible) else {
return underlyingError.localizedDescription
}
guard let prettyError = underlyingError as? (any PrettyStringConvertible) else { return "\(underlyingError)" }
return prettyError.prettyDescription
}
}
Expand All @@ -106,5 +104,7 @@ extension ServerError: LocalizedError {
/// This computed property provides a localized human-readable description of the server error, which is suitable for displaying to users.
///
/// - Returns: A localized string describing the server error.
public var errorDescription: String? { description }
public var errorDescription: String? {
"Server encountered an error handling the operation \"\(operationID)\", caused by \"\(causeDescription)\", underlying error: \(underlyingError.localizedDescription)."
}
}
2 changes: 1 addition & 1 deletion Sources/OpenAPIRuntime/Interface/ServerTransport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public protocol ServerTransport {
/// print("<<<: \(response.status.code)")
/// return (response, responseBody)
/// } catch {
/// print("!!!: \(error.localizedDescription)")
/// print("!!!: \(error)")
/// throw error
/// }
/// }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,5 +285,5 @@ public func XCTAssertEqualStringifiedData(
do {
let actualString = String(decoding: try expression1(), as: UTF8.self)
XCTAssertEqual(actualString, try expression2(), file: file, line: line)
} catch { XCTFail(error.localizedDescription, file: file, line: line) }
} catch { XCTFail("\(error)", file: file, line: line) }
}
39 changes: 39 additions & 0 deletions Tests/OpenAPIRuntimeTests/Errors/Test_ClientError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2025 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

final class Test_ServerError: XCTestCase {
func testPrinting() throws {
let upstreamError = RuntimeError.handlerFailed(PrintableError())
let error: any Error = ServerError(
operationID: "op",
request: .init(soar_path: "/test", method: .get),
requestBody: nil,
requestMetadata: .init(),
causeDescription: upstreamError.prettyDescription,
underlyingError: upstreamError.underlyingError ?? upstreamError
)
XCTAssertEqual(
"\(error)",
"Server error - cause description: 'User handler threw an error.', underlying error: Just description, operationID: op, request: GET /test [], requestBody: <nil>, metadata: Path parameters: [:], operationInput: <nil>, operationOutput: <nil>"
)
XCTAssertEqual(
error.localizedDescription,
"Server encountered an error handling the operation \"op\", caused by \"User handler threw an error.\", underlying error: Just errorDescription."
)
}
}
6 changes: 6 additions & 0 deletions Tests/OpenAPIRuntimeTests/Errors/Test_RuntimeError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ final class Test_RuntimeError: XCTestCase {
)
XCTAssertEqual(response.0.status, .badGateway)
}

func testDescriptions() async throws {
let error: any Error = RuntimeError.transportFailed(PrintableError())
XCTAssertEqual("\(error)", "Transport threw an error.")
XCTAssertEqual(error.localizedDescription, "Transport threw an error.")
}
}

enum TestErrorConvertible: Error, HTTPResponseConvertible {
Expand Down
37 changes: 37 additions & 0 deletions Tests/OpenAPIRuntimeTests/Errors/Test_ServerError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the SwiftOpenAPIGenerator open source project
//
// Copyright (c) 2025 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

final class Test_ClientError: XCTestCase {
func testPrinting() throws {
let upstreamError = RuntimeError.transportFailed(PrintableError())
let error: any Error = ClientError(
operationID: "op",
operationInput: "test",
causeDescription: upstreamError.prettyDescription,
underlyingError: upstreamError.underlyingError ?? upstreamError
)
XCTAssertEqual(
"\(error)",
"Client error - cause description: 'Transport threw an error.', underlying error: Just description, operationID: op, operationInput: test, request: <nil>, requestBody: <nil>, baseURL: <nil>, response: <nil>, responseBody: <nil>"
)
XCTAssertEqual(
error.localizedDescription,
"Client encountered an error invoking the operation \"op\", caused by \"Transport threw an error.\", underlying error: Just errorDescription."
)
}
}
11 changes: 8 additions & 3 deletions Tests/OpenAPIRuntimeTests/Test_Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,11 @@ extension ArraySlice<UInt8> {

struct TestError: Error, Equatable {}

struct PrintableError: Error, CustomStringConvertible, LocalizedError {
var description: String { "Just description" }
var errorDescription: String? { "Just errorDescription" }
}

struct MockMiddleware: ClientMiddleware, ServerMiddleware {
enum FailurePhase {
case never
Expand Down Expand Up @@ -345,7 +350,7 @@ struct PrintingMiddleware: ClientMiddleware {
print("Received: \(response.status)")
return (response, responseBody)
} catch {
print("Failed with error: \(error.localizedDescription)")
print("Failed with error: \(error)")
throw error
}
}
Expand Down Expand Up @@ -373,7 +378,7 @@ public func XCTAssertEqualStringifiedData<S: Sequence>(
}
let actualString = String(decoding: Array(value1), as: UTF8.self)
XCTAssertEqual(actualString, try expression2(), file: file, line: line)
} catch { XCTFail(error.localizedDescription, file: file, line: line) }
} catch { XCTFail("\(error)", file: file, line: line) }
}

/// Asserts that the string representation of binary data in an HTTP body is equal to an expected string.
Expand Down Expand Up @@ -454,7 +459,7 @@ public func XCTAssertEqualData<C1: Collection, C2: Collection>(
file: file,
line: line
)
} catch { XCTFail(error.localizedDescription, file: file, line: line) }
} catch { XCTFail("\(error)", file: file, line: line) }
}

/// Asserts that the data matches the expected value.
Expand Down
Loading