Skip to content

Commit

Permalink
Extend the customization to both client and server
Browse files Browse the repository at this point in the history
  • Loading branch information
czechboy0 committed Feb 8, 2025
1 parent 73ea1d9 commit 78bd078
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 12 deletions.
13 changes: 12 additions & 1 deletion Sources/OpenAPIRuntime/Conversion/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,12 @@ public struct Configuration: Sendable {
/// Custom XML coder for encoding and decoding xml bodies.
public var xmlCoder: (any CustomCoder)?

/// An error mapping closure to allow customizing the error thrown by the client.
public var clientErrorMapper: (@Sendable (ClientError) -> any Error)?

/// An error mapping closure to allow customizing the error thrown by the server.
public var serverErrorMapper: (@Sendable (ServerError) -> any Error)?

/// Creates a new configuration with the specified values.
///
/// - Parameters:
Expand All @@ -160,15 +166,20 @@ public struct Configuration: Sendable {
/// - jsonEncodingOptions: The options for the underlying JSON encoder.
/// - multipartBoundaryGenerator: The generator to use when creating mutlipart bodies.
/// - xmlCoder: Custom XML coder for encoding and decoding xml bodies. Only required when using XML body payloads.
/// - errorMapper: An error mapping closure to allow customizing the final error thrown.
public init(
dateTranscoder: any DateTranscoder = .iso8601,
jsonEncodingOptions: JSONEncodingOptions = [.sortedKeys, .prettyPrinted],
multipartBoundaryGenerator: any MultipartBoundaryGenerator = .random,
xmlCoder: (any CustomCoder)? = nil
xmlCoder: (any CustomCoder)? = nil,
clientErrorMapper: (@Sendable (ClientError) -> any Error)? = nil,
serverErrorMapper: (@Sendable (ServerError) -> any Error)? = nil
) {
self.dateTranscoder = dateTranscoder
self.jsonEncodingOptions = jsonEncodingOptions
self.multipartBoundaryGenerator = multipartBoundaryGenerator
self.xmlCoder = xmlCoder
self.clientErrorMapper = clientErrorMapper
self.serverErrorMapper = serverErrorMapper
}
}
25 changes: 25 additions & 0 deletions Sources/OpenAPIRuntime/Deprecated/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ extension Configuration {
xmlCoder: xmlCoder
)
}

/// Creates a new configuration with the specified values.
///
/// - Parameters:
/// - dateTranscoder: The transcoder to use when converting between date
/// and string values.
/// - jsonEncodingOptions: The options for the underlying JSON encoder.
/// - multipartBoundaryGenerator: The generator to use when creating mutlipart bodies.
/// - xmlCoder: Custom XML coder for encoding and decoding xml bodies. Only required when using XML body payloads.
@available(*, deprecated, renamed: "init(dateTranscoder:jsonEncodingOptions:multipartBoundaryGenerator:xmlCoder:clientErrorMapper:serverErrorMapper:)")
@_disfavoredOverload public init(
dateTranscoder: any DateTranscoder = .iso8601,
jsonEncodingOptions: JSONEncodingOptions = [.sortedKeys, .prettyPrinted],
multipartBoundaryGenerator: any MultipartBoundaryGenerator = .random,
xmlCoder: (any CustomCoder)? = nil
) {
self.init(
dateTranscoder: dateTranscoder,
jsonEncodingOptions: [.sortedKeys, .prettyPrinted],
multipartBoundaryGenerator: multipartBoundaryGenerator,
xmlCoder: xmlCoder,
clientErrorMapper: nil,
serverErrorMapper: nil
)
}
}

extension AsyncSequence where Element == ArraySlice<UInt8>, Self: Sendable {
Expand Down
5 changes: 2 additions & 3 deletions Sources/OpenAPIRuntime/Interface/UniversalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@ import struct Foundation.URL
serverURL: URL = .defaultOpenAPIServerURL,
configuration: Configuration = .init(),
transport: any ClientTransport,
middlewares: [any ClientMiddleware] = [],
errorMapper: (@Sendable (ClientError) -> any Error)? = nil
middlewares: [any ClientMiddleware] = []
) {
self.init(
serverURL: serverURL,
converter: Converter(configuration: configuration),
transport: transport,
middlewares: middlewares,
errorMapper: errorMapper
errorMapper: configuration.clientErrorMapper
)
}

Expand Down
25 changes: 22 additions & 3 deletions Sources/OpenAPIRuntime/Interface/UniversalServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,22 @@ import struct Foundation.URLComponents
/// The middlewares to be invoked before the handler receives the request.
public var middlewares: [any ServerMiddleware]

/// An error mapping closure to allow customizing the error thrown by the server handler.
public var errorMapper: (@Sendable (ServerError) -> any Error)?

/// Internal initializer that takes an initialized converter.
internal init(serverURL: URL, converter: Converter, handler: APIHandler, middlewares: [any ServerMiddleware]) {
internal init(
serverURL: URL,
converter: Converter,
handler: APIHandler,
middlewares: [any ServerMiddleware],
errorMapper: (@Sendable (ServerError) -> any Error)?
) {
self.serverURL = serverURL
self.converter = converter
self.handler = handler
self.middlewares = middlewares
self.errorMapper = errorMapper
}

/// Creates a new server with the specified parameters.
Expand All @@ -59,7 +69,8 @@ import struct Foundation.URLComponents
serverURL: serverURL,
converter: Converter(configuration: configuration),
handler: handler,
middlewares: middlewares
middlewares: middlewares,
errorMapper: configuration.serverErrorMapper
)
}

Expand Down Expand Up @@ -169,7 +180,15 @@ import struct Foundation.URLComponents
}
}
}
return try await next(request, requestBody, metadata)
do {
return try await next(request, requestBody, metadata)
} catch {
if let errorMapper, let serverError = error as? ServerError {
throw errorMapper(serverError)
} else {
throw error
}
}
}

/// Returns the path with the server URL's path prefix prepended.
Expand Down
12 changes: 7 additions & 5 deletions Tests/OpenAPIRuntimeTests/Interface/Test_UniversalClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,13 @@ final class Test_UniversalClient: Test_Runtime {
func testErrorPropagation_customErrorMapper() async throws {
do {
let client = UniversalClient(
transport: MockClientTransport.failing,
errorMapper: { clientError in
// Don't care about the extra context, just wants the underlyingError
clientError.underlyingError
}
configuration: .init(
clientErrorMapper: { clientError in
// Don't care about the extra context, just wants the underlyingError
clientError.underlyingError
}
),
transport: MockClientTransport.failing
)
try await client.send(
input: "input",
Expand Down
28 changes: 28 additions & 0 deletions Tests/OpenAPIRuntimeTests/Interface/Test_UniversalServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,34 @@ final class Test_UniversalServer: Test_Runtime {
}
}

func testErrorPropagation_errorMapper() async throws {
do {
let server = UniversalServer(
handler: MockHandler(shouldFail: true),
configuration: .init(
serverErrorMapper: { serverError in
// Don't care about the extra context, just wants the underlyingError
serverError.underlyingError
}
)
)
_ = try await server.handle(
request: .init(soar_path: "/", method: .post),
requestBody: MockHandler.requestBody,
metadata: .init(),
forOperation: "op",
using: { MockHandler.greet($0) },
deserializer: { request, body, metadata in
let body = try XCTUnwrap(body)
return try await String(collecting: body, upTo: 10)
},
serializer: { output, _ in fatalError() }
)
} catch {
XCTAssertTrue(error is TestError, "Threw an unexpected error: \(type(of: error))")
}
}

func testErrorPropagation_serializer() async throws {
do {
let server = UniversalServer(handler: MockHandler())
Expand Down

0 comments on commit 78bd078

Please sign in to comment.