Skip to content

Commit

Permalink
Refactor API and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bernerdschaefer committed Jun 4, 2013
1 parent 00c52d7 commit 605fd9b
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 226 deletions.
108 changes: 62 additions & 46 deletions decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import (
"bufio"
"bytes"
"io"
"unicode/utf8"
)

// A Decoder reads and decodes EventSource messages from an input stream.
// A Decoder reads and decodes EventSource events from an input stream.
type Decoder struct {
r *bufio.Reader
}
Expand All @@ -18,73 +19,88 @@ func NewDecoder(r io.Reader) *Decoder {
}
}

// Reads an event. If a returned []byte value is nil, it was not included in
// the event.
func (d *Decoder) Read() (id, event, data []byte, err error) {
var buf bytes.Buffer
var dataBuf bytes.Buffer
// ReadField reads a single line from the stream and parses it as a field. A
// complete event is signalled by an empty key and value. The returned error
// may either be an error from the stream, or an InvalidEncodingErr if the
// value is not valid UTF-8.
func (d *Decoder) ReadField() (field string, value []byte, err error) {
var buf []byte

for {
// BUG(bernerd): The EventSource spec defines valid line endings as CR, LF,
// CRLF. We only support LF or CRLF.
l, isPrefix, err := d.r.ReadLine()
line, isPrefix, err := d.r.ReadLine()

if err != nil {
return nil, nil, nil, err
return "", nil, err
}

buf.Write(l)
buf = append(buf, line...)

if isPrefix {
continue
if !isPrefix {
break
}
}

line := make([]byte, buf.Len())
copy(line, buf.Bytes())
buf.Reset()
if len(buf) == 0 {
return "", nil, nil
}

if len(line) == 0 {
break
}
parts := bytes.SplitN(buf, []byte{':'}, 2)
field = string(parts[0])

if line[0] == ':' {
continue // comment
}
if len(parts) == 2 {
value = parts[1]
}

// §7. If value starts with a U+0020 SPACE character, remove it from value.
if len(value) > 0 && value[0] == ' ' {
value = value[1:]
}

var field string
var value []byte
parts := bytes.SplitN(line, []byte{':'}, 2)
if !utf8.ValidString(field) || !utf8.Valid(value) {
err = InvalidEncodingErr
}

if len(parts) == 2 {
field = string(parts[0])
value = parts[1]
} else {
field = string(parts[0])
value = []byte{}
return
}

// Decode reads the next event from its input and stores it in the provided
// Event pointer.
func (d *Decoder) Decode(e *Event) error {
var wroteData bool

// set default event type
e.Type = "message"

for {
field, value, err := d.ReadField()

if err != nil {
return err
}

if len(value) > 0 && value[0] == ' ' {
value = value[1:]
if len(field) == 0 && len(value) == 0 {
break
}

switch field {
// BUG(bernerd): Server sent "retry" fields are ignored. Should this be
// supported?
case "id":
id = value
e.ID = string(value)
if len(e.ID) == 0 {
e.ResetID = true
}
case "retry":
e.Retry = string(value)
case "event":
event = value
e.Type = string(value)
case "data":
dataBuf.Write(value)
dataBuf.WriteRune('\n')
if wroteData {
e.Data = append(e.Data, '\n')
} else {
wroteData = true
}
e.Data = append(e.Data, value...)
}
}

data = dataBuf.Bytes()

if len(data) > 0 && data[len(data)-1] == '\n' {
data = data[:len(data)-1]
}

return
return nil
}
159 changes: 50 additions & 109 deletions decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package eventsource

import (
"bytes"
"io"
"reflect"
"testing"
)

Expand All @@ -15,125 +15,66 @@ func longLine() string {
return string(buf)
}

type messageExpectation struct {
id []byte
event []byte
data []byte
error
}

var fixtures = []struct {
source string
expectations []messageExpectation
}{
{
"data: message 1\r\n\r\ndata: message\r\ndata:2\r\n\r\ndata: message 3\r\n\r\n",
[]messageExpectation{
{nil, nil, []byte("message 1"), nil},
{nil, nil, []byte("message\n2"), nil},
{nil, nil, []byte("message 3"), nil},
{nil, nil, nil, io.EOF},
},
},

{
": this is a comment\r\n\r\ndata: space\r\n\r\ndata:nospace\r\n\r\n",
[]messageExpectation{
{nil, nil, nil, nil},
{nil, nil, []byte("space"), nil},
{nil, nil, []byte("nospace"), nil},
},
},
func TestDecoderReadField(t *testing.T) {
table := []struct {
in string
field string
value []byte
err error
}{
{"\n", "", nil, nil},
{"id", "id", nil, nil},
{"id:", "id", nil, nil},
{"id:1", "id", []byte("1"), nil},
{"id: 1", "id", []byte("1"), nil},
{"data: " + longLine(), "data", []byte(longLine()), nil},
{"\xFF\xFE\xFD", "\xFF\xFE\xFD", nil, InvalidEncodingErr},
{"data: \xFF\xFE\xFD", "data", []byte("\xFF\xFE\xFD"), InvalidEncodingErr},
}

{
"event: add\ndata: 123\n\nevent: remove\ndata: 321\n\nevent: add\ndata: 123\n\n",
[]messageExpectation{
{nil, []byte("add"), []byte("123"), nil},
{nil, []byte("remove"), []byte("321"), nil},
{nil, []byte("add"), []byte("123"), nil},
},
},
for i, tt := range table {
dec := NewDecoder(bytes.NewBufferString(tt.in))

{
"data: first event\nid: 1\n\ndata:second event\nid\n\ndata: third event\n\n",
[]messageExpectation{
{[]byte("1"), nil, []byte("first event"), nil},
{[]byte{}, nil, []byte("second event"), nil},
{nil, nil, []byte("third event"), nil},
},
},
field, value, err := dec.ReadField()

{
"data\n\ndata:\ndata:\n\ndata:\n",
[]messageExpectation{
{nil, nil, []byte(""), nil},
{nil, nil, []byte("\n"), nil},
{nil, nil, nil, io.EOF},
},
},
if err != tt.err {
t.Errorf("%d. expected err=%q, got %q", i, tt.err, err)
continue
}

{
"data: {\"ok\": true}\n\n",
[]messageExpectation{
{nil, nil, []byte(`{"ok": true}`), nil},
},
},
if exp, got := tt.field, field; exp != got {
t.Errorf("%d. expected field=%q, got %q", i, exp, got)
}

{
"data:" + longLine() + "\n\n",
[]messageExpectation{
{nil, nil, []byte(longLine()), nil},
},
},
if exp, got := tt.value, value; !bytes.Equal(exp, got) {
t.Errorf("%d. expected value=%q, got %q", i, exp, got)
}
}
}

func TestDecoder(t *testing.T) {
for i, f := range fixtures {
dec := NewDecoder(bytes.NewBufferString(f.source))

for j, e := range f.expectations {
id, event, data, err := dec.Read()

if err != e.error {
t.Errorf("%d.%d expected err=%q, got %q", i, j, e.error, err)
continue
}

if e.id == nil && id != nil {
t.Errorf("%d.%d expected id=nil, got %q", i, j, id)
}

if e.id != nil && id == nil {
t.Errorf("%d.%d expected id==%q, got nil", i, j, e.id)
}

if !bytes.Equal(e.id, id) {
t.Errorf("%d.%d expected id=%q, got %q", i, j, e.id, id)
}

if !bytes.Equal(e.event, event) {
t.Errorf("%d.%d expected event=%q, got %q", i, j, e.event, event)
}

if e.event == nil && event != nil {
t.Errorf("%d.%d expected event=nil, got %q", i, j, event)
}

if !bytes.Equal(e.data, data) {
t.Errorf("%d.%d expected data=%q, got %q", i, j, e.data, data)
}
func TestDecoderDecode(t *testing.T) {
table := []struct {
in string
out Event
}{
{"event: type\ndata\n\n", Event{Type: "type"}},
{"id: 123\ndata\n\n", Event{Type: "message", ID: "123"}},
{"retry: 10000\ndata\n\n", Event{Type: "message", Retry: "10000"}},
{"data: data\n\n", Event{Type: "message", Data: []byte("data")}},
{"id\ndata\n\n", Event{Type: "message", ResetID: true}},
}

if e.data == nil && data != nil {
t.Errorf("%d.%d expected data=nil, got %q", i, j, data)
}
for i, tt := range table {
dec := NewDecoder(bytes.NewBufferString(tt.in))

if e.data != nil && data == nil {
t.Errorf("%d.%d expected data=%q, got nil", i, j, e.data)
}
var event Event
if err := dec.Decode(&event); err != nil {
t.Errorf("%d. %s", i, err)
continue
}

if _, _, _, err := dec.Read(); err != io.EOF {
t.Errorf("%d. expected last read to be EOF, was %s", i, err)
if !reflect.DeepEqual(event, tt.out) {
t.Errorf("%d. expected %#v, got %#v", i, tt.out, event)
}
}
}
Loading

0 comments on commit 605fd9b

Please sign in to comment.