@@ -28,9 +28,19 @@ where Upstream.Element == ArraySlice<UInt8> {
28
28
/// The upstream sequence.
29
29
private let upstream : Upstream
30
30
31
+ /// An optional closure that determines whether the given byte sequence is the terminating byte sequence defined by the API.
32
+ /// - Parameter: A byte chunk.
33
+ /// - Returns: `True` if the given byte sequence is the terminating byte sequence defined by the API.
34
+ private let terminate : ( @Sendable ( ArraySlice < UInt8 > ) -> Bool ) ?
35
+
31
36
/// Creates a new sequence.
32
- /// - Parameter upstream: The upstream sequence of arbitrary byte chunks.
33
- public init ( upstream: Upstream ) { self . upstream = upstream }
37
+ /// - Parameters:
38
+ /// - upstream: The upstream sequence of arbitrary byte chunks.
39
+ /// - terminate: An optional closure that determines whether the given byte sequence is the terminating byte sequence defined by the API.
40
+ public init ( upstream: Upstream , terminate: ( @Sendable ( ArraySlice < UInt8 > ) -> Bool ) ? ) {
41
+ self . upstream = upstream
42
+ self . terminate = terminate
43
+ }
34
44
}
35
45
36
46
extension ServerSentEventsDeserializationSequence : AsyncSequence {
@@ -48,6 +58,17 @@ extension ServerSentEventsDeserializationSequence: AsyncSequence {
48
58
/// The state machine of the iterator.
49
59
var stateMachine : StateMachine = . init( )
50
60
61
+ /// An optional closure that determines whether the given byte sequence is the terminating byte sequence defined by the API.
62
+ /// - Parameter: A byte chunk.
63
+ /// - Returns: `True` if the given byte sequence is the terminating byte sequence defined by the API.
64
+ let terminate : ( ( ArraySlice < UInt8 > ) -> Bool ) ?
65
+
66
+ init ( upstream: any AsyncIteratorProtocol , terminate: ( ( ArraySlice < UInt8 > ) -> Bool ) ? ) {
67
+ self . upstream = upstream as! UpstreamIterator
68
+ self . stateMachine = . init( terminate: terminate)
69
+ self . terminate = terminate
70
+ }
71
+
51
72
/// Asynchronously advances to the next element and returns it, or ends the
52
73
/// sequence if there is no next element.
53
74
public mutating func next( ) async throws -> ServerSentEvent ? {
@@ -70,7 +91,7 @@ extension ServerSentEventsDeserializationSequence: AsyncSequence {
70
91
/// Creates the asynchronous iterator that produces elements of this
71
92
/// asynchronous sequence.
72
93
public func makeAsyncIterator( ) -> Iterator < Upstream . AsyncIterator > {
73
- Iterator ( upstream: upstream. makeAsyncIterator ( ) )
94
+ Iterator ( upstream: upstream. makeAsyncIterator ( ) , terminate : terminate )
74
95
}
75
96
}
76
97
@@ -79,26 +100,34 @@ extension AsyncSequence where Element == ArraySlice<UInt8>, Self: Sendable {
79
100
/// Returns another sequence that decodes each event's data as the provided type using the provided decoder.
80
101
///
81
102
/// Use this method if the event's `data` field is not JSON, or if you don't want to parse it using `asDecodedServerSentEventsWithJSONData`.
103
+ /// - Parameter: An optional closure that determines whether the given byte sequence is the terminating byte sequence defined by the API.
82
104
/// - Returns: A sequence that provides the events.
83
- public func asDecodedServerSentEvents( ) -> ServerSentEventsDeserializationSequence <
105
+ public func asDecodedServerSentEvents( terminate : ( @ Sendable ( ArraySlice < UInt8 > ) -> Bool ) ? = nil ) -> ServerSentEventsDeserializationSequence <
84
106
ServerSentEventsLineDeserializationSequence < Self >
85
- > { . init( upstream: ServerSentEventsLineDeserializationSequence ( upstream: self ) ) }
107
+ > { . init( upstream: ServerSentEventsLineDeserializationSequence ( upstream: self ) , terminate: terminate) }
108
+
109
+ /// Convenience function for `asDecodedServerSentEvents` that directly receives the terminating byte sequence.
110
+ public func asDecodedServerSentEvents( terminatingSequence: ArraySlice < UInt8 > ) -> ServerSentEventsDeserializationSequence <
111
+ ServerSentEventsLineDeserializationSequence < Self >
112
+ > { asDecodedServerSentEvents ( terminate: { incomingSequence in return incomingSequence == terminatingSequence } ) }
86
113
87
114
/// Returns another sequence that decodes each event's data as the provided type using the provided decoder.
88
115
///
89
116
/// Use this method if the event's `data` field is JSON.
90
117
/// - Parameters:
91
118
/// - dataType: The type to decode the JSON data into.
92
119
/// - decoder: The JSON decoder to use.
120
+ /// - terminate: An optional closure that determines whether the given byte sequence is the terminating byte sequence defined by the API.
93
121
/// - Returns: A sequence that provides the events with the decoded JSON data.
94
122
public func asDecodedServerSentEventsWithJSONData< JSONDataType: Decodable > (
95
123
of dataType: JSONDataType . Type = JSONDataType . self,
96
- decoder: JSONDecoder = . init( )
124
+ decoder: JSONDecoder = . init( ) ,
125
+ terminate: ( @Sendable ( ArraySlice < UInt8 > ) -> Bool ) ? = nil
97
126
) -> AsyncThrowingMapSequence <
98
127
ServerSentEventsDeserializationSequence < ServerSentEventsLineDeserializationSequence < Self > > ,
99
128
ServerSentEventWithJSONData < JSONDataType >
100
129
> {
101
- asDecodedServerSentEvents ( )
130
+ asDecodedServerSentEvents ( terminate : terminate )
102
131
. map { event in
103
132
ServerSentEventWithJSONData (
104
133
event: event. event,
@@ -110,6 +139,19 @@ extension AsyncSequence where Element == ArraySlice<UInt8>, Self: Sendable {
110
139
)
111
140
}
112
141
}
142
+
143
+ public func asDecodedServerSentEventsWithJSONData< JSONDataType: Decodable > (
144
+ of dataType: JSONDataType . Type = JSONDataType . self,
145
+ decoder: JSONDecoder = . init( ) ,
146
+ terminatingData: ArraySlice < UInt8 >
147
+ ) -> AsyncThrowingMapSequence <
148
+ ServerSentEventsDeserializationSequence < ServerSentEventsLineDeserializationSequence < Self > > ,
149
+ ServerSentEventWithJSONData < JSONDataType >
150
+ > {
151
+ asDecodedServerSentEventsWithJSONData ( of: dataType, decoder: decoder) { incomingData in
152
+ terminatingData == incomingData
153
+ }
154
+ }
113
155
}
114
156
115
157
extension ServerSentEventsDeserializationSequence . Iterator {
@@ -133,8 +175,16 @@ extension ServerSentEventsDeserializationSequence.Iterator {
133
175
/// The current state of the state machine.
134
176
private( set) var state : State
135
177
178
+
179
+ /// An optional closure that determines whether the given byte sequence is the terminating byte sequence defined by the API.
180
+ /// - Parameter: A sequence of byte chunks.
181
+ /// - Returns: `True` if the given byte sequence is the terminating byte sequence defined by the API.
182
+ let terminate : ( ( ArraySlice < UInt8 > ) -> Bool ) ?
183
+
136
184
/// Creates a new state machine.
137
- init ( ) { self . state = . accumulatingEvent( . init( ) , buffer: [ ] ) }
185
+ init ( terminate: ( ( ArraySlice < UInt8 > ) -> Bool ) ? = nil ) {
186
+ self . state = . accumulatingEvent( . init( ) , buffer: [ ] )
187
+ self . terminate = terminate}
138
188
139
189
/// An action returned by the `next` method.
140
190
enum NextAction {
@@ -165,6 +215,14 @@ extension ServerSentEventsDeserializationSequence.Iterator {
165
215
state = . accumulatingEvent( . init( ) , buffer: buffer)
166
216
// If the last character of data is a newline, strip it.
167
217
if event. data? . hasSuffix ( " \n " ) ?? false { event. data? . removeLast ( ) }
218
+
219
+ if let terminate = terminate {
220
+ if let data = event. data {
221
+ if terminate ( ArraySlice ( Data ( data. utf8) ) ) {
222
+ return . returnNil
223
+ }
224
+ }
225
+ }
168
226
return . emitEvent( event)
169
227
}
170
228
if line. first! == ASCII . colon {
0 commit comments