Skip to content

Commit 78bd078

Browse files
committed
Extend the customization to both client and server
1 parent 73ea1d9 commit 78bd078

File tree

6 files changed

+96
-12
lines changed

6 files changed

+96
-12
lines changed

Sources/OpenAPIRuntime/Conversion/Configuration.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ public struct Configuration: Sendable {
152152
/// Custom XML coder for encoding and decoding xml bodies.
153153
public var xmlCoder: (any CustomCoder)?
154154

155+
/// An error mapping closure to allow customizing the error thrown by the client.
156+
public var clientErrorMapper: (@Sendable (ClientError) -> any Error)?
157+
158+
/// An error mapping closure to allow customizing the error thrown by the server.
159+
public var serverErrorMapper: (@Sendable (ServerError) -> any Error)?
160+
155161
/// Creates a new configuration with the specified values.
156162
///
157163
/// - Parameters:
@@ -160,15 +166,20 @@ public struct Configuration: Sendable {
160166
/// - jsonEncodingOptions: The options for the underlying JSON encoder.
161167
/// - multipartBoundaryGenerator: The generator to use when creating mutlipart bodies.
162168
/// - xmlCoder: Custom XML coder for encoding and decoding xml bodies. Only required when using XML body payloads.
169+
/// - errorMapper: An error mapping closure to allow customizing the final error thrown.
163170
public init(
164171
dateTranscoder: any DateTranscoder = .iso8601,
165172
jsonEncodingOptions: JSONEncodingOptions = [.sortedKeys, .prettyPrinted],
166173
multipartBoundaryGenerator: any MultipartBoundaryGenerator = .random,
167-
xmlCoder: (any CustomCoder)? = nil
174+
xmlCoder: (any CustomCoder)? = nil,
175+
clientErrorMapper: (@Sendable (ClientError) -> any Error)? = nil,
176+
serverErrorMapper: (@Sendable (ServerError) -> any Error)? = nil
168177
) {
169178
self.dateTranscoder = dateTranscoder
170179
self.jsonEncodingOptions = jsonEncodingOptions
171180
self.multipartBoundaryGenerator = multipartBoundaryGenerator
172181
self.xmlCoder = xmlCoder
182+
self.clientErrorMapper = clientErrorMapper
183+
self.serverErrorMapper = serverErrorMapper
173184
}
174185
}

Sources/OpenAPIRuntime/Deprecated/Deprecated.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,31 @@ extension Configuration {
5858
xmlCoder: xmlCoder
5959
)
6060
}
61+
62+
/// Creates a new configuration with the specified values.
63+
///
64+
/// - Parameters:
65+
/// - dateTranscoder: The transcoder to use when converting between date
66+
/// and string values.
67+
/// - jsonEncodingOptions: The options for the underlying JSON encoder.
68+
/// - multipartBoundaryGenerator: The generator to use when creating mutlipart bodies.
69+
/// - xmlCoder: Custom XML coder for encoding and decoding xml bodies. Only required when using XML body payloads.
70+
@available(*, deprecated, renamed: "init(dateTranscoder:jsonEncodingOptions:multipartBoundaryGenerator:xmlCoder:clientErrorMapper:serverErrorMapper:)")
71+
@_disfavoredOverload public init(
72+
dateTranscoder: any DateTranscoder = .iso8601,
73+
jsonEncodingOptions: JSONEncodingOptions = [.sortedKeys, .prettyPrinted],
74+
multipartBoundaryGenerator: any MultipartBoundaryGenerator = .random,
75+
xmlCoder: (any CustomCoder)? = nil
76+
) {
77+
self.init(
78+
dateTranscoder: dateTranscoder,
79+
jsonEncodingOptions: [.sortedKeys, .prettyPrinted],
80+
multipartBoundaryGenerator: multipartBoundaryGenerator,
81+
xmlCoder: xmlCoder,
82+
clientErrorMapper: nil,
83+
serverErrorMapper: nil
84+
)
85+
}
6186
}
6287

6388
extension AsyncSequence where Element == ArraySlice<UInt8>, Self: Sendable {

Sources/OpenAPIRuntime/Interface/UniversalClient.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,14 @@ import struct Foundation.URL
6161
serverURL: URL = .defaultOpenAPIServerURL,
6262
configuration: Configuration = .init(),
6363
transport: any ClientTransport,
64-
middlewares: [any ClientMiddleware] = [],
65-
errorMapper: (@Sendable (ClientError) -> any Error)? = nil
64+
middlewares: [any ClientMiddleware] = []
6665
) {
6766
self.init(
6867
serverURL: serverURL,
6968
converter: Converter(configuration: configuration),
7069
transport: transport,
7170
middlewares: middlewares,
72-
errorMapper: errorMapper
71+
errorMapper: configuration.clientErrorMapper
7372
)
7473
}
7574

Sources/OpenAPIRuntime/Interface/UniversalServer.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,22 @@ import struct Foundation.URLComponents
4040
/// The middlewares to be invoked before the handler receives the request.
4141
public var middlewares: [any ServerMiddleware]
4242

43+
/// An error mapping closure to allow customizing the error thrown by the server handler.
44+
public var errorMapper: (@Sendable (ServerError) -> any Error)?
45+
4346
/// Internal initializer that takes an initialized converter.
44-
internal init(serverURL: URL, converter: Converter, handler: APIHandler, middlewares: [any ServerMiddleware]) {
47+
internal init(
48+
serverURL: URL,
49+
converter: Converter,
50+
handler: APIHandler,
51+
middlewares: [any ServerMiddleware],
52+
errorMapper: (@Sendable (ServerError) -> any Error)?
53+
) {
4554
self.serverURL = serverURL
4655
self.converter = converter
4756
self.handler = handler
4857
self.middlewares = middlewares
58+
self.errorMapper = errorMapper
4959
}
5060

5161
/// Creates a new server with the specified parameters.
@@ -59,7 +69,8 @@ import struct Foundation.URLComponents
5969
serverURL: serverURL,
6070
converter: Converter(configuration: configuration),
6171
handler: handler,
62-
middlewares: middlewares
72+
middlewares: middlewares,
73+
errorMapper: configuration.serverErrorMapper
6374
)
6475
}
6576

@@ -169,7 +180,15 @@ import struct Foundation.URLComponents
169180
}
170181
}
171182
}
172-
return try await next(request, requestBody, metadata)
183+
do {
184+
return try await next(request, requestBody, metadata)
185+
} catch {
186+
if let errorMapper, let serverError = error as? ServerError {
187+
throw errorMapper(serverError)
188+
} else {
189+
throw error
190+
}
191+
}
173192
}
174193

175194
/// Returns the path with the server URL's path prefix prepended.

Tests/OpenAPIRuntimeTests/Interface/Test_UniversalClient.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,13 @@ final class Test_UniversalClient: Test_Runtime {
122122
func testErrorPropagation_customErrorMapper() async throws {
123123
do {
124124
let client = UniversalClient(
125-
transport: MockClientTransport.failing,
126-
errorMapper: { clientError in
127-
// Don't care about the extra context, just wants the underlyingError
128-
clientError.underlyingError
129-
}
125+
configuration: .init(
126+
clientErrorMapper: { clientError in
127+
// Don't care about the extra context, just wants the underlyingError
128+
clientError.underlyingError
129+
}
130+
),
131+
transport: MockClientTransport.failing
130132
)
131133
try await client.send(
132134
input: "input",

Tests/OpenAPIRuntimeTests/Interface/Test_UniversalServer.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,34 @@ final class Test_UniversalServer: Test_Runtime {
129129
}
130130
}
131131

132+
func testErrorPropagation_errorMapper() async throws {
133+
do {
134+
let server = UniversalServer(
135+
handler: MockHandler(shouldFail: true),
136+
configuration: .init(
137+
serverErrorMapper: { serverError in
138+
// Don't care about the extra context, just wants the underlyingError
139+
serverError.underlyingError
140+
}
141+
)
142+
)
143+
_ = try await server.handle(
144+
request: .init(soar_path: "/", method: .post),
145+
requestBody: MockHandler.requestBody,
146+
metadata: .init(),
147+
forOperation: "op",
148+
using: { MockHandler.greet($0) },
149+
deserializer: { request, body, metadata in
150+
let body = try XCTUnwrap(body)
151+
return try await String(collecting: body, upTo: 10)
152+
},
153+
serializer: { output, _ in fatalError() }
154+
)
155+
} catch {
156+
XCTAssertTrue(error is TestError, "Threw an unexpected error: \(type(of: error))")
157+
}
158+
}
159+
132160
func testErrorPropagation_serializer() async throws {
133161
do {
134162
let server = UniversalServer(handler: MockHandler())

0 commit comments

Comments
 (0)