Skip to content

Commit 3dee2dd

Browse files
author
Eric Rosenberg
committed
Make HTTP3ErrorCode a public extensible struct
Expose the HTTP/3 error code carried by RESET_STREAM / STOP_SENDING / CONNECTION_CLOSE so consumers can read it, matching NIOHTTP2's public HTTP2ErrorCode / StreamClosed.errorCode. - HTTP3ErrorCode becomes a public struct (was a package enum) with camelCased static known values, so future/unknown codes don't force a source-breaking new case and are preserved verbatim. - HTTP3Error.h3ErrorCode becomes public; its doc now covers both the code this endpoint sends and a code received from the peer. - The received-reset path preserves the peer's raw code instead of collapsing unknown codes to H3_NO_ERROR (the .remoteStreamError classification already satisfies RFC 9114 § 8's handling requirement).
1 parent 240d833 commit 3dee2dd

28 files changed

Lines changed: 236 additions & 182 deletions

Sources/HTTP3/HTTP3ConnectionQuiescingStateMachine.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ package struct HTTP3ConnectionQuiescingStateMachine: ~Copyable {
162162
code: .invalidGoawayStreamID,
163163
message: "Invalid GOAWAY id",
164164
cause: nil,
165-
errorCode: .H3_ID_ERROR,
165+
errorCode: .idError,
166166
location: location
167167
)
168168
}
@@ -418,7 +418,7 @@ extension HTTP3Error {
418418
code: .invalidGoawayStreamID,
419419
message: "GOAWAY id was increased",
420420
cause: nil,
421-
errorCode: .H3_ID_ERROR,
421+
errorCode: .idError,
422422
location: location
423423
)
424424
}

Sources/HTTP3/HTTP3ConnectionStateMachine.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
5858
code: .invalidStream,
5959
message: "Received a duplicate incoming stream",
6060
cause: nil,
61-
errorCode: .H3_STREAM_CREATION_ERROR,
61+
errorCode: .streamCreationError,
6262
location: .here()
6363
)
6464
)
@@ -185,7 +185,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
185185
code: .rejected,
186186
message: "Stream rejected due to server shutting down",
187187
cause: nil,
188-
errorCode: .H3_REQUEST_REJECTED,
188+
errorCode: .requestRejected,
189189
location: .here()
190190
)
191191
)
@@ -200,7 +200,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
200200
code: .streamCreationError,
201201
message: "Incoming request stream on client",
202202
cause: nil,
203-
errorCode: .H3_STREAM_CREATION_ERROR,
203+
errorCode: .streamCreationError,
204204
location: .here()
205205
)
206206
)
@@ -263,7 +263,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
263263
code: .streamCreationError,
264264
message: "Cannot accept push stream on server",
265265
cause: nil,
266-
errorCode: .H3_STREAM_CREATION_ERROR,
266+
errorCode: .streamCreationError,
267267
location: .here()
268268
)
269269
)
@@ -278,7 +278,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
278278
code: .streamCreationError,
279279
message: "Rejecting inbound push stream with invalid ID",
280280
cause: nil,
281-
errorCode: .H3_ID_ERROR,
281+
errorCode: .idError,
282282
location: .here()
283283
)
284284
)
@@ -367,7 +367,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
367367
code: .streamCreationError,
368368
message: "Rejecting inbound stream of unknown type \(streamType.rawValue)",
369369
cause: nil,
370-
errorCode: .H3_STREAM_CREATION_ERROR,
370+
errorCode: .streamCreationError,
371371
location: .here()
372372
)
373373
)
@@ -600,7 +600,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
600600
code: .unexpectedFrame,
601601
message: "Received MAX_PUSH_ID on client",
602602
cause: nil,
603-
errorCode: .H3_FRAME_UNEXPECTED,
603+
errorCode: .frameUnexpected,
604604
location: .here()
605605
)
606606
)
@@ -959,7 +959,7 @@ package struct HTTP3ConnectionStateMachine: ~Copyable {
959959
code: .criticalStreamClosed,
960960
message: "The \(typeName) \(unidirectionalStreamType) stream was closed",
961961
cause: nil,
962-
errorCode: .H3_CLOSED_CRITICAL_STREAM,
962+
errorCode: .closedCriticalStream,
963963
location: .here()
964964
)
965965
)
@@ -1052,7 +1052,7 @@ extension HTTP3Error {
10521052
code: .streamCreationError,
10531053
message: "Endpoint is shutting down",
10541054
cause: nil,
1055-
errorCode: .H3_STREAM_CREATION_ERROR,
1055+
errorCode: .streamCreationError,
10561056
location: location
10571057
)
10581058
}

Sources/HTTP3/HTTP3Error.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,13 @@ public struct HTTP3Error: Error, Sendable {
3030
/// about the root cause of the failure.
3131
public var cause: (any Error)?
3232

33-
/// The http3 error code to be sent to the peer when the connection or stream is closed.
34-
/// See RFC 9114 § 8.1.
35-
package var h3ErrorCode: HTTP3ErrorCode?
33+
/// The HTTP/3 error code associated with this error (RFC 9114 § 8.1).
34+
///
35+
/// For an error this endpoint raises, this is the code it will send to the
36+
/// peer when it closes the stream or connection. For an error representing a
37+
/// reset received from the peer (see ``HTTP3Error/Code-swift.struct/remoteStreamError``),
38+
/// this is the code the peer sent, preserved verbatim.
39+
public var h3ErrorCode: HTTP3ErrorCode?
3640

3741
/// The location from which this error was thrown.
3842
public var location: SourceLocation

Sources/HTTP3/HTTP3ErrorCode.swift

Lines changed: 69 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,49 +12,96 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
/// RFC 9114 § 8.1: The following error codes are defined for use when abruptly terminating streams, aborting reading of streams, or immediately closing HTTP/3 connections.
16-
package enum HTTP3ErrorCode: UInt64 {
15+
/// An HTTP/3 error code.
16+
///
17+
/// HTTP/3 uses error codes to communicate what went wrong when abruptly
18+
/// terminating streams, aborting reading of streams, or immediately closing
19+
/// connections. See RFC 9114 § 8.1.
20+
///
21+
/// This is modeled as a struct rather than an enum so that codes registered by
22+
/// future revisions or extensions can be added without a source-breaking
23+
/// change, and so that a code received from a peer is preserved verbatim even
24+
/// when this library does not recognize it (mirroring `HTTP2ErrorCode`).
25+
public struct HTTP3ErrorCode: Hashable, Sendable {
26+
/// The underlying error code value (RFC 9114 § 8.1 / RFC 9000 § 20.2).
27+
public var rawValue: UInt64
28+
29+
/// Create an ``HTTP3ErrorCode`` from its raw value.
30+
public init(rawValue: UInt64) {
31+
self.rawValue = rawValue
32+
}
33+
1734
/// No error. This is used when the connection or stream needs to be closed, but there is no error to signal.
18-
case H3_NO_ERROR = 0x0100
35+
public static let noError = HTTP3ErrorCode(rawValue: 0x0100)
1936
/// Peer violated protocol requirements in a way that does not match a more specific error code or endpoint declines to use the more specific error code.
20-
case H3_GENERAL_PROTOCOL_ERROR = 0x0101
37+
public static let generalProtocolError = HTTP3ErrorCode(rawValue: 0x0101)
2138
/// An internal error has occurred in the HTTP stack.
22-
case H3_INTERNAL_ERROR = 0x0102
39+
public static let internalError = HTTP3ErrorCode(rawValue: 0x0102)
2340
/// The endpoint detected that its peer created a stream that it will not accept.
24-
case H3_STREAM_CREATION_ERROR = 0x0103
41+
public static let streamCreationError = HTTP3ErrorCode(rawValue: 0x0103)
2542
/// A stream required by the HTTP/3 connection was closed or reset.
26-
case H3_CLOSED_CRITICAL_STREAM = 0x0104
43+
public static let closedCriticalStream = HTTP3ErrorCode(rawValue: 0x0104)
2744
/// A frame was received that was not permitted in the current state or on the current stream.
28-
case H3_FRAME_UNEXPECTED = 0x0105
45+
public static let frameUnexpected = HTTP3ErrorCode(rawValue: 0x0105)
2946
/// A frame that fails to satisfy layout requirements or with an invalid size was received.
30-
case H3_FRAME_ERROR = 0x0106
47+
public static let frameError = HTTP3ErrorCode(rawValue: 0x0106)
3148
/// The endpoint detected that its peer is exhibiting a behavior that might be generating excessive load.
32-
case H3_EXCESSIVE_LOAD = 0x0107
49+
public static let excessiveLoad = HTTP3ErrorCode(rawValue: 0x0107)
3350
/// A stream ID or push ID was used incorrectly, such as exceeding a limit, reducing a limit, or being reused.
34-
case H3_ID_ERROR = 0x0108
51+
public static let idError = HTTP3ErrorCode(rawValue: 0x0108)
3552
/// An endpoint detected an error in the payload of a SETTINGS frame.
36-
case H3_SETTINGS_ERROR = 0x0109
53+
public static let settingsError = HTTP3ErrorCode(rawValue: 0x0109)
3754
/// No SETTINGS frame was received at the beginning of the control stream.
38-
case H3_MISSING_SETTINGS = 0x010a
55+
public static let missingSettings = HTTP3ErrorCode(rawValue: 0x010a)
3956
/// A server rejected a request without performing any application processing.
40-
case H3_REQUEST_REJECTED = 0x010b
57+
public static let requestRejected = HTTP3ErrorCode(rawValue: 0x010b)
4158
/// The request or its response (including pushed response) is cancelled.
42-
case H3_REQUEST_CANCELLED = 0x010c
59+
public static let requestCancelled = HTTP3ErrorCode(rawValue: 0x010c)
4360
/// The client's stream terminated without containing a fully formed request.
44-
case H3_REQUEST_INCOMPLETE = 0x010d
61+
public static let requestIncomplete = HTTP3ErrorCode(rawValue: 0x010d)
4562
/// An HTTP message was malformed and cannot be processed.
46-
case H3_MESSAGE_ERROR = 0x010e
63+
public static let messageError = HTTP3ErrorCode(rawValue: 0x010e)
4764
/// The TCP connection established in response to a CONNECT request was reset or abnormally closed.
48-
case H3_CONNECT_ERROR = 0x010f
65+
public static let connectError = HTTP3ErrorCode(rawValue: 0x010f)
4966
/// The requested operation cannot be served over HTTP/3. The peer should retry over HTTP/1.1.
50-
case H3_VERSION_FALLBACK = 0x0110
67+
public static let versionFallback = HTTP3ErrorCode(rawValue: 0x0110)
5168

5269
// MARK: QPACK (RFC 9204)
5370

5471
/// The decoder failed to interpret an encoded field section and is not able to continue decoding that field section.
55-
case QPACK_DECOMPRESSION_FAILED = 0x0200
72+
public static let qpackDecompressionFailed = HTTP3ErrorCode(rawValue: 0x0200)
5673
/// The decoder failed to interpret an encoder instruction received on the encoder stream.
57-
case QPACK_ENCODER_STREAM_ERROR = 0x0201
74+
public static let qpackEncoderStreamError = HTTP3ErrorCode(rawValue: 0x0201)
5875
/// The encoder failed to interpret a decoder instruction received on the decoder stream.
59-
case QPACK_DECODER_STREAM_ERROR = 0x0202
76+
public static let qpackDecoderStreamError = HTTP3ErrorCode(rawValue: 0x0202)
77+
}
78+
79+
extension HTTP3ErrorCode: CustomStringConvertible {
80+
public var description: String {
81+
let name: String
82+
switch self {
83+
case .noError: name = "H3_NO_ERROR"
84+
case .generalProtocolError: name = "H3_GENERAL_PROTOCOL_ERROR"
85+
case .internalError: name = "H3_INTERNAL_ERROR"
86+
case .streamCreationError: name = "H3_STREAM_CREATION_ERROR"
87+
case .closedCriticalStream: name = "H3_CLOSED_CRITICAL_STREAM"
88+
case .frameUnexpected: name = "H3_FRAME_UNEXPECTED"
89+
case .frameError: name = "H3_FRAME_ERROR"
90+
case .excessiveLoad: name = "H3_EXCESSIVE_LOAD"
91+
case .idError: name = "H3_ID_ERROR"
92+
case .settingsError: name = "H3_SETTINGS_ERROR"
93+
case .missingSettings: name = "H3_MISSING_SETTINGS"
94+
case .requestRejected: name = "H3_REQUEST_REJECTED"
95+
case .requestCancelled: name = "H3_REQUEST_CANCELLED"
96+
case .requestIncomplete: name = "H3_REQUEST_INCOMPLETE"
97+
case .messageError: name = "H3_MESSAGE_ERROR"
98+
case .connectError: name = "H3_CONNECT_ERROR"
99+
case .versionFallback: name = "H3_VERSION_FALLBACK"
100+
case .qpackDecompressionFailed: name = "QPACK_DECOMPRESSION_FAILED"
101+
case .qpackEncoderStreamError: name = "QPACK_ENCODER_STREAM_ERROR"
102+
case .qpackDecoderStreamError: name = "QPACK_DECODER_STREAM_ERROR"
103+
default: name = "UNKNOWN"
104+
}
105+
return "HTTP3ErrorCode<0x\(String(self.rawValue, radix: 16)) \(name)>"
106+
}
60107
}

Sources/HTTP3/HTTP3Frame.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ package enum HTTP3FrameType: Hashable {
9797
code: .forbiddenFrameType,
9898
message: "\(rawValue) is not allowed",
9999
cause: nil,
100-
errorCode: .H3_FRAME_UNEXPECTED, // RFC 9114 § 7.2.8
100+
errorCode: .frameUnexpected, // RFC 9114 § 7.2.8
101101
location: location
102102
)
103103
}

Sources/HTTP3/HTTP3FrameDecoder.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ package struct HTTP3FrameDecoder: ~Copyable {
109109
code: .invalidFramePayload,
110110
message: "Payload too large",
111111
cause: nil,
112-
errorCode: .H3_EXCESSIVE_LOAD,
112+
errorCode: .excessiveLoad,
113113
location: .here()
114114
)
115115
}
@@ -163,7 +163,7 @@ package struct HTTP3FrameDecoder: ~Copyable {
163163
code: .invalidFramePayload,
164164
message: "Payload too large",
165165
cause: nil,
166-
errorCode: .H3_EXCESSIVE_LOAD,
166+
errorCode: .excessiveLoad,
167167
location: .here()
168168
)
169169
}
@@ -182,7 +182,7 @@ package struct HTTP3FrameDecoder: ~Copyable {
182182
code: .invalidFramePayload,
183183
message: "Invalid frame payload",
184184
cause: nil,
185-
errorCode: .H3_FRAME_ERROR,
185+
errorCode: .frameError,
186186
location: .here()
187187
)
188188
}
@@ -202,7 +202,7 @@ package struct HTTP3FrameDecoder: ~Copyable {
202202
code: .invalidFramePayload,
203203
message: "Frame length longer than payload",
204204
cause: nil,
205-
errorCode: .H3_FRAME_ERROR,
205+
errorCode: .frameError,
206206
location: .here()
207207
)
208208
}
@@ -270,7 +270,7 @@ extension ByteBuffer {
270270
code: .integerTooLarge,
271271
message: "Integer is too large",
272272
cause: error,
273-
errorCode: .H3_GENERAL_PROTOCOL_ERROR,
273+
errorCode: .generalProtocolError,
274274
location: .here()
275275
)
276276
}

Sources/HTTP3/HTTP3FrameValidator.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ package enum HTTP3FrameValidator: ~Copyable {
5252
code: .firstControlFrameNotSettings,
5353
message: "Expected settings, got unknown",
5454
cause: nil,
55-
errorCode: .H3_MISSING_SETTINGS,
55+
errorCode: .missingSettings,
5656
location: .here()
5757
)
5858
)
@@ -82,7 +82,7 @@ package enum HTTP3FrameValidator: ~Copyable {
8282
code: .firstControlFrameNotSettings,
8383
message: "Expected settings, got \(frame.type)",
8484
cause: nil,
85-
errorCode: .H3_MISSING_SETTINGS,
85+
errorCode: .missingSettings,
8686
location: .here()
8787
)
8888
)
@@ -96,7 +96,7 @@ package enum HTTP3FrameValidator: ~Copyable {
9696
code: .unexpectedFrame,
9797
message: "Expected cancelPush or goaway or maxPushID, got \(frame.type)",
9898
cause: nil,
99-
errorCode: .H3_FRAME_UNEXPECTED,
99+
errorCode: .frameUnexpected,
100100
location: .here()
101101
)
102102
)
@@ -108,7 +108,7 @@ package enum HTTP3FrameValidator: ~Copyable {
108108
code: .unexpectedFrame,
109109
message: "Received a second settings frame",
110110
cause: nil,
111-
errorCode: .H3_FRAME_UNEXPECTED,
111+
errorCode: .frameUnexpected,
112112
location: location
113113
)
114114
}
@@ -160,7 +160,7 @@ package enum HTTP3FrameValidator: ~Copyable {
160160
code: .unexpectedFrame,
161161
message: "Expected headers or data, got \(frame.type)",
162162
cause: nil,
163-
errorCode: .H3_FRAME_UNEXPECTED,
163+
errorCode: .frameUnexpected,
164164
location: .here()
165165
)
166166
)
@@ -214,7 +214,7 @@ package enum HTTP3FrameValidator: ~Copyable {
214214
code: .malformedMessage,
215215
message: "A HTTP response was sent before a request",
216216
cause: nil,
217-
errorCode: .H3_MESSAGE_ERROR,
217+
errorCode: .messageError,
218218
location: .here()
219219
)
220220
)
@@ -243,7 +243,7 @@ package enum HTTP3FrameValidator: ~Copyable {
243243
code: .unexpectedFrame,
244244
message: "Expected headers, got \(frame.type)",
245245
cause: nil,
246-
errorCode: .H3_FRAME_UNEXPECTED,
246+
errorCode: .frameUnexpected,
247247
location: .here()
248248
)
249249
)
@@ -265,7 +265,7 @@ package enum HTTP3FrameValidator: ~Copyable {
265265
code: .unexpectedFrame,
266266
message: "Expected headers or data, got \(frame.type)",
267267
cause: nil,
268-
errorCode: .H3_FRAME_UNEXPECTED,
268+
errorCode: .frameUnexpected,
269269
location: .here()
270270
)
271271
)
@@ -280,7 +280,7 @@ package enum HTTP3FrameValidator: ~Copyable {
280280
code: .unexpectedFrame,
281281
message: "Expected no further frames after response trailers, got \(frame.type)",
282282
cause: nil,
283-
errorCode: .H3_FRAME_UNEXPECTED,
283+
errorCode: .frameUnexpected,
284284
location: .here()
285285
)
286286
)
@@ -311,7 +311,7 @@ package enum HTTP3FrameValidator: ~Copyable {
311311
code: .unexpectedFrame,
312312
message: "Expected headers, got \(frame.type)",
313313
cause: nil,
314-
errorCode: .H3_FRAME_UNEXPECTED,
314+
errorCode: .frameUnexpected,
315315
location: .here()
316316
)
317317
)
@@ -335,7 +335,7 @@ package enum HTTP3FrameValidator: ~Copyable {
335335
code: .unexpectedFrame,
336336
message: "Expected headers or data, got \(frame.type)",
337337
cause: nil,
338-
errorCode: .H3_FRAME_UNEXPECTED,
338+
errorCode: .frameUnexpected,
339339
location: .here()
340340
)
341341
)
@@ -350,7 +350,7 @@ package enum HTTP3FrameValidator: ~Copyable {
350350
code: .unexpectedFrame,
351351
message: "Expected no further frames after request trailers, got \(frame.type)",
352352
cause: nil,
353-
errorCode: .H3_FRAME_UNEXPECTED,
353+
errorCode: .frameUnexpected,
354354
location: .here()
355355
)
356356
)

0 commit comments

Comments
 (0)