-
-
Notifications
You must be signed in to change notification settings - Fork 26
Expand file tree
/
Copy pathServerEvent.swift
More file actions
151 lines (130 loc) · 4.18 KB
/
ServerEvent.swift
File metadata and controls
151 lines (130 loc) · 4.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//
// ServerEvent.swift
// EventSource
//
// Copyright © 2023 Firdavs Khaydarov (Recouse). All rights reserved.
// Licensed under the MIT License.
//
import Foundation
/// Protocol for defining a basic event structure. It is used by the ``EventParser``
/// and should be implemented as a custom type when a custom ``EventParser`` is required.
public protocol EVEvent: Sendable {
var id: String? { get }
var event: String? { get }
var data: String? { get }
var other: [String: String]? { get }
var time: String? { get }
}
public extension EVEvent {
/// Checks if all event fields are empty.
var isEmpty: Bool {
if let id, !id.isEmpty {
return false
}
if let event, !event.isEmpty {
return false
}
if let data, !data.isEmpty {
return false
}
if let other, !other.isEmpty {
return false
}
if let time, !time.isEmpty {
return false
}
return true
}
}
/// Default implementation of ``EventSourceEvent`` used in the package.
public struct ServerEvent: EVEvent {
public var id: String?
public var event: String?
public var data: String?
public var other: [String: String]?
public var time: String?
init(
id: String? = nil,
event: String? = nil,
data: String? = nil,
other: [String: String]? = nil,
time: String? = nil
) {
self.id = id
self.event = event
self.data = data
self.other = other
self.time = time
}
public static func parse(from data: Data, mode: EventSource.Mode = .default) -> ServerEvent? {
let rows: [Data] = {
switch mode {
case .default:
let (separatedMessages, remainingData) = data.split(separators: singleSeparators)
return separatedMessages + [remainingData]
case .dataOnly:
return [data] // Do not split data in data-only mode
}
}()
var message = ServerEvent()
for row in rows {
// Skip the line if it is empty or it starts with a colon character
if row.isEmpty || row.first == colon {
continue
}
let keyValue = row.split(separator: colon, maxSplits: 1)
let key = keyValue[0].utf8String
// If value starts with a SPACE character, remove it from value
let valueString = keyValue[safe: 1]?.utf8String
let value = if let valueString, valueString.hasPrefix(" ") {
String(valueString.dropFirst())
} else {
valueString
}
switch key {
case "id":
message.id = value
case "event":
message.event = value
case "data":
if let existingData = message.data {
message.data = existingData + "\n" + (value ?? "")
} else {
message.data = value
}
case "time":
message.time = value
default:
// If the line is not empty but does not contain a colon character
// add it to the other fields using the whole line as the field name,
// and the empty string as the field value.
if row.contains(colon) == false {
let string = row.utf8String
if var other = message.other {
other[string] = ""
message.other = other
} else {
message.other = [string: ""]
}
}
}
}
if message.isEmpty {
return nil
}
return message
}
}
fileprivate extension Data {
var utf8String: String {
String(decoding: self, as: UTF8.self)
}
}
package extension Array {
subscript(safe index: Int) -> Element? {
guard index >= 0, index < endIndex else {
return nil
}
return self[index]
}
}