소스 검색

Initial commit

Manu Mtz-Almeida 10 년 전
커밋
d43a7ecd18
2개의 변경된 파일225개의 추가작업 그리고 0개의 파일을 삭제
  1. 77 0
      sse-encoder.go
  2. 148 0
      sse_test.go

+ 77 - 0
sse-encoder.go

@@ -0,0 +1,77 @@
+package sse
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+
+	"reflect"
+	"strings"
+)
+
+type Event struct {
+	Event string
+	Id    string
+	Retry uint
+	Data  interface{}
+}
+
+func Encode(w io.Writer, event Event) error {
+	writeId(w, event.Id)
+	writeEvent(w, event.Event)
+	writeRetry(w, event.Retry)
+	return writeData(w, event.Data)
+}
+
+func writeId(w io.Writer, id string) {
+	if len(id) > 0 {
+		w.Write([]byte("id: "))
+		w.Write([]byte(escape(id)))
+		w.Write([]byte("\n"))
+	}
+}
+
+func writeEvent(w io.Writer, event string) {
+	if len(event) > 0 {
+		w.Write([]byte("event: "))
+		w.Write([]byte(escape(event)))
+		w.Write([]byte("\n"))
+	}
+}
+
+func writeRetry(w io.Writer, retry uint) {
+	if retry > 0 {
+		fmt.Fprintf(w, "retry: %d\n", retry)
+	}
+}
+
+func writeData(w io.Writer, data interface{}) error {
+	w.Write([]byte("data: "))
+	switch typeOfData(data) {
+	case reflect.Struct, reflect.Slice, reflect.Map:
+		err := json.NewEncoder(w).Encode(data)
+		if err != nil {
+			return err
+		}
+		w.Write([]byte("\n"))
+	default:
+		text := fmt.Sprint(data)
+		fmt.Fprint(w, escape(text), "\n\n")
+	}
+	return nil
+}
+
+func typeOfData(data interface{}) reflect.Kind {
+	value := reflect.ValueOf(data)
+	valueType := value.Kind()
+	if valueType == reflect.Ptr {
+		valueType = value.Elem().Kind()
+	}
+	return valueType
+}
+
+func escape(str string) string {
+	str = strings.Replace(str, "\n", "\\n", -1)
+	str = strings.Replace(str, "\r", "\\r", -1)
+	return str
+}

+ 148 - 0
sse_test.go

@@ -0,0 +1,148 @@
+package sse
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestEncodeOnlyData(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Data: "junk\n\njk\nid:fake",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "data: junk\\n\\njk\\nid:fake\n\n")
+}
+
+func TestEncodeWithEvent(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Event: "t\n:<>\r\test",
+		Data:  "junk\n\njk\nid:fake",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "event: t\\n:<>\\r\test\ndata: junk\\n\\njk\\nid:fake\n\n")
+}
+
+func TestEncodeWithId(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Id:   "t\n:<>\r\test",
+		Data: "junk\n\njk\nid:fa\rke",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "id: t\\n:<>\\r\test\ndata: junk\\n\\njk\\nid:fa\\rke\n\n")
+}
+
+func TestEncodeWithRetry(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Retry: 11,
+		Data:  "junk\n\njk\nid:fake\n",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "retry: 11\ndata: junk\\n\\njk\\nid:fake\\n\n\n")
+}
+
+func TestEncodeWithEverything(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Event: "abc",
+		Id:    "12345",
+		Retry: 10,
+		Data:  "some data",
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "id: 12345\nevent: abc\nretry: 10\ndata: some data\n\n")
+}
+
+func TestEncodeMap(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Event: "a map",
+		Data: map[string]interface{}{
+			"foo": "b\n\rar",
+			"bar": "id: 2",
+		},
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "event: a map\ndata: {\"bar\":\"id: 2\",\"foo\":\"b\\n\\rar\"}\n\n")
+}
+
+func TestEncodeSlice(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Event: "a slice",
+		Data:  []interface{}{1, "text", map[string]interface{}{"foo": "bar"}},
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "event: a slice\ndata: [1,\"text\",{\"foo\":\"bar\"}]\n\n")
+}
+
+func TestEncodeStruct(t *testing.T) {
+	myStruct := struct {
+		A int
+		B string `json:"value"`
+	}{1, "number"}
+
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Event: "a struct",
+		Data:  myStruct,
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "event: a struct\ndata: {\"A\":1,\"value\":\"number\"}\n\n")
+
+	w.Reset()
+	err = Encode(w, Event{
+		Event: "a struct",
+		Data:  &myStruct,
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "event: a struct\ndata: {\"A\":1,\"value\":\"number\"}\n\n")
+}
+
+func TestEncodeInteger(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Event: "an integer",
+		Data:  1,
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "event: an integer\ndata: 1\n\n")
+}
+
+func TestEncodeFloat(t *testing.T) {
+	w := new(bytes.Buffer)
+	err := Encode(w, Event{
+		Event: "Float",
+		Data:  1.5,
+	})
+	assert.NoError(t, err)
+	assert.Equal(t, w.String(), "event: Float\ndata: 1.5\n\n")
+}
+
+func TestEncodeStream(t *testing.T) {
+	w := new(bytes.Buffer)
+
+	Encode(w, Event{
+		Event: "float",
+		Data:  1.5,
+	})
+
+	Encode(w, Event{
+		Id:   "123",
+		Data: map[string]interface{}{"foo": "bar", "bar": "foo"},
+	})
+
+	Encode(w, Event{
+		Id:    "124",
+		Event: "chat",
+		Data:  "hi! dude",
+	})
+	fmt.Println(w.String())
+	assert.Equal(t, w.String(), "event: float\ndata: 1.5\n\nid: 123\ndata: {\"bar\":\"foo\",\"foo\":\"bar\"}\n\nid: 124\nevent: chat\ndata: hi! dude\n\n")
+}