Files
sultan/pkg/stream/decoder.go
T
br c8df9d7651 erste module drin.
zwischenstand: Architektur aktualisiert, TOFU implementiert,
2026-10-01 22:22:13 +02:00

81 lines
1.9 KiB
Go

package stream
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"strings"
)
// MaxEventSize begrenzt die Datenmenge eines einzelnen Events.
const MaxEventSize = 1 << 20
// ErrEventTooLarge meldet ein Event über MaxEventSize.
var ErrEventTooLarge = errors.New("stream: Event zu groß")
// Decoder liest Events aus einem SSE-Stream. Kommentarzeilen (Keepalives)
// und unbekannte Felder werden übersprungen; maßgeblich ist allein das JSON
// in den data-Zeilen.
type Decoder struct {
sc *bufio.Scanner
}
// NewDecoder liest aus r.
func NewDecoder(r io.Reader) *Decoder {
sc := bufio.NewScanner(r)
sc.Buffer(make([]byte, 4096), MaxEventSize+1024)
return &Decoder{sc: sc}
}
// Next liefert das nächste Event. Am sauberen Stream-Ende kommt io.EOF, endet
// der Stream mitten in einem Event io.ErrUnexpectedEOF.
func (d *Decoder) Next() (Event, error) {
var data strings.Builder
pending := false
for d.sc.Scan() {
line := d.sc.Text() // ScanLines entfernt auch ein \r vor dem \n
switch {
case line == "":
if !pending {
continue
}
return parseEvent(data.String())
case strings.HasPrefix(line, ":"):
// Kommentar / Keepalive
case strings.HasPrefix(line, "data:"):
v := strings.TrimPrefix(strings.TrimPrefix(line, "data:"), " ")
if pending {
data.WriteByte('\n')
}
data.WriteString(v)
pending = true
if data.Len() > MaxEventSize {
return Event{}, ErrEventTooLarge
}
}
}
if err := d.sc.Err(); err != nil {
if errors.Is(err, bufio.ErrTooLong) {
return Event{}, ErrEventTooLarge
}
return Event{}, err
}
if pending {
return Event{}, io.ErrUnexpectedEOF
}
return Event{}, io.EOF
}
func parseEvent(data string) (Event, error) {
var ev Event
if err := json.Unmarshal([]byte(data), &ev); err != nil {
return Event{}, fmt.Errorf("%w: %v", ErrInvalidEvent, err)
}
if ev.Type == "" {
return Event{}, fmt.Errorf("%w: Typ fehlt", ErrInvalidEvent)
}
return ev, nil
}