• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

supabase / supabase-swift / 29018885975

09 Jul 2026 12:40PM UTC coverage: 77.87% (-5.2%) from 83.062%
29018885975

Pull #1099

github

web-flow
Merge 2bab4f734 into 27f31bfb7
Pull Request #1099: feat(storage): OpenAPI codegen tool + generated Storage HTTP client

428 of 1233 new or added lines in 18 files covered. (34.71%)

6 existing lines in 2 files now uncovered.

8322 of 10687 relevant lines covered (77.87%)

33.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.43
/Sources/HTTPRuntime/ServerSentEvents.swift
1
//
2
//  ServerSentEvents.swift
3
//  HTTPRuntime
4
//
5
//  Created by Guilherme Souza on 08/07/26.
6
//
7
package import Foundation
8

9
/// A single Server-Sent Event frame (`text/event-stream`).
10
package struct ServerSentEvent: Sendable, Hashable {
11
  package var event: String?
12
  package var data: String
13
  package var id: String?
14
  package var retry: Int?
15

16
  package init(event: String? = nil, data: String, id: String? = nil, retry: Int? = nil) {
4✔
17
    self.event = event
4✔
18
    self.data = data
4✔
19
    self.id = id
4✔
20
    self.retry = retry
4✔
21
  }
4✔
22
}
23

24
extension AsyncThrowingStream where Element == Data, Failure == any Error {
25
  /// Parses this stream of raw byte chunks into SSE frames, splitting on the
26
  /// blank-line frame delimiter and coalescing multi-line `data:` fields per
27
  /// the SSE spec. Generated code maps each frame's `data` (JSON) into the
28
  /// operation's typed event union.
29
  package func serverSentEvents() -> AsyncThrowingStream<ServerSentEvent, any Error> {
2✔
30
    AsyncThrowingStream<ServerSentEvent, any Error> { continuation in
2✔
31
      let task = Task {
2✔
32
        var buffer = Data()
2✔
33
        do {
2✔
34
          for try await chunk in self {
3✔
35
            buffer.append(chunk)
3✔
36
            while let frame = Self.extractFrame(&buffer) {
4✔
37
              if let event = Self.parseFrame(frame) {
4✔
38
                continuation.yield(event)
4✔
39
              }
4✔
40
            }
4✔
41
          }
3✔
42
          // Flush any trailing frame without a terminating blank line.
2✔
43
          if !buffer.isEmpty, let event = Self.parseFrame(buffer) {
2✔
NEW
44
            continuation.yield(event)
×
NEW
45
          }
×
46
          continuation.finish()
2✔
47
        } catch {
2✔
NEW
48
          continuation.finish(throwing: error)
×
49
        }
2✔
50
      }
2✔
51
      continuation.onTermination = { _ in task.cancel() }
2✔
52
    }
2✔
53
  }
2✔
54

55
  /// Pulls one complete frame (bytes up to and including `\n\n`) out of the
56
  /// buffer, or returns nil if no full frame is buffered yet.
57
  private static func extractFrame(_ buffer: inout Data) -> Data? {
7✔
58
    let delimiter = Data([0x0A, 0x0A])  // "\n\n"
7✔
59
    guard let range = buffer.range(of: delimiter) else { return nil }
7✔
60
    let frame = buffer.subdata(in: buffer.startIndex..<range.lowerBound)
4✔
61
    buffer.removeSubrange(buffer.startIndex..<range.upperBound)
4✔
62
    return frame
4✔
63
  }
7✔
64

65
  private static func parseFrame(_ frame: Data) -> ServerSentEvent? {
4✔
66
    guard let text = String(data: frame, encoding: .utf8) else { return nil }
4✔
67
    var event: String?
4✔
68
    var id: String?
4✔
69
    var retry: Int?
4✔
70
    var dataLines: [String] = []
4✔
71
    for rawLine in text.split(separator: "\n", omittingEmptySubsequences: false) {
8✔
72
      let line = rawLine.hasSuffix("\r") ? String(rawLine.dropLast()) : String(rawLine)
8✔
73
      if line.isEmpty || line.hasPrefix(":") { continue }  // comment / blank
8✔
74
      let field: Substring
8✔
75
      let value: String
8✔
76
      if let colon = line.firstIndex(of: ":") {
8✔
77
        field = line[line.startIndex..<colon]
8✔
78
        var v = line[line.index(after: colon)...]
8✔
79
        if v.first == " " { v = v.dropFirst() }
8✔
80
        value = String(v)
8✔
81
      } else {
8✔
NEW
82
        field = Substring(line)
×
NEW
83
        value = ""
×
NEW
84
      }
×
85
      switch field {
8✔
86
      case "event": event = value
8✔
87
      case "data": dataLines.append(value)
8✔
88
      case "id": id = value
8✔
89
      case "retry": retry = Int(value)
8✔
90
      default: break
8✔
91
      }
8✔
92
    }
8✔
93
    guard !dataLines.isEmpty || event != nil else { return nil }
4✔
94
    return ServerSentEvent(
4✔
95
      event: event, data: dataLines.joined(separator: "\n"), id: id, retry: retry)
4✔
96
  }
4✔
97
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc