Skip to content

Commit fbf0f01

Browse files
authored
Merge pull request #3 from Pattio/status-validator-error-mapping
Add support for mapping error when HTTP status validation fails
2 parents 591ddf1 + bb50739 commit fbf0f01

File tree

2 files changed

+61
-4
lines changed

2 files changed

+61
-4
lines changed

Sources/Modules/Sauce/Handlers/HTTPStatusValidatingHandler.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,30 @@ import SimpleHTTPCore
99

1010
/// Validates that the HTTP response status code falls within a given range.
1111
public struct HTTPStatusValidatingHandler: HTTPHandler {
12+
public typealias ErrorMapper = @Sendable (Response) -> Error?
13+
1214
/// The next handler in the chain.
1315
public var next: AnyHandler?
1416

1517
/// The default acceptable status code range: 200–299.
1618
public static let defaultValidRange = 200..<300
1719

1820
private let validStatusRange: Range<Int>
21+
private let mapError: ErrorMapper?
1922

2023
/// Creates a status-validator handler.
2124
///
2225
/// - Parameters:
2326
/// - validStatusRange: The acceptable HTTP status codes.
27+
/// - mapError: Optional mapper that produces an underlying error when the status code is invalid.
2428
/// - nextHandler: The next handler to invoke.
2529
public init(
2630
validStatusRange: Range<Int> = Self.defaultValidRange,
31+
mapError: ErrorMapper? = nil,
2732
nextHandler: AnyHandler? = nil
2833
) {
2934
self.validStatusRange = validStatusRange
35+
self.mapError = mapError
3036
self.next = nextHandler
3137
}
3238

@@ -38,7 +44,8 @@ public struct HTTPStatusValidatingHandler: HTTPHandler {
3844
throw .init(
3945
code: .invalidStatus(result.status),
4046
request: result.request,
41-
response: result
47+
response: result,
48+
underlyingError: mapError?(result)
4249
)
4350
}
4451

@@ -54,8 +61,16 @@ extension HTTPHandler where Self == HTTPStatusValidatingHandler {
5461

5562
/// Factory for a status-validating handler.
5663
///
57-
/// - Parameter range: The acceptable status codes.
58-
public static func httpStatusValidator(in range: Range<Int> = Self.defaultValidRange) -> Self {
59-
HTTPStatusValidatingHandler(validStatusRange: range)
64+
/// - Parameters:
65+
/// - range: The acceptable status codes.
66+
/// - mapError: Optional mapper that produces an underlying error when the status code is invalid.
67+
public static func httpStatusValidator(
68+
in range: Range<Int> = Self.defaultValidRange,
69+
mapError: Self.ErrorMapper? = nil
70+
) -> Self {
71+
HTTPStatusValidatingHandler(
72+
validStatusRange: range,
73+
mapError: mapError
74+
)
6075
}
6176
}

Tests/SimpleHTTPTests/Sauce/Handlers/HTTPStatusValidatingHandlerTests.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,46 @@ struct StatusValidatorTests {
4242

4343
#expect(status == HTTPStatus(rawValue: failingStatusCode))
4444
}
45+
46+
@Test("Maps underlying error for invalid status codes")
47+
func testMapsUnderlyingErrorOnInvalidStatus() async {
48+
let failingStatusCode = 500
49+
let failingHandler = MockHTTPHandler(
50+
response: .mock(status: failingStatusCode)
51+
)
52+
53+
let validator = HTTPStatusValidatingHandler(
54+
mapError: { _ in MappedError() },
55+
nextHandler: failingHandler
56+
)
57+
58+
let error = await #expect(throws: HTTPError.self) {
59+
_ = try await validator.handle(request: .mock)
60+
}
61+
62+
guard let underlyingError = error?.underlyingError else {
63+
Issue.record("Expected underlyingError to be set.")
64+
return
65+
}
66+
67+
#expect((underlyingError as? MappedError) == MappedError())
68+
}
69+
70+
@Test("Does not map underlying error for acceptable status codes")
71+
func testDoesNotMapUnderlyingErrorOnValidStatus() async throws {
72+
let okHandler = MockHTTPHandler(response: .mock(status: 204))
73+
74+
let validator = HTTPStatusValidatingHandler(
75+
validStatusRange: 200..<300,
76+
mapError: { _ in
77+
Issue.record("mapError must not be invoked for acceptable status codes.")
78+
return MappedError()
79+
},
80+
nextHandler: okHandler
81+
)
82+
83+
_ = try await validator.handle(request: .mock)
84+
}
4585
}
86+
87+
fileprivate struct MappedError: Error, Equatable {}

0 commit comments

Comments
 (0)