2
0

chore: improve linting and error handling across the codebase

- Update golangci-lint-action to version 7 in GitHub workflow
- Specify version 2.0 for golangci-lint-action in GitHub workflow
- Set golangci-lint configuration version to "2"
- Enable specific linters and disable default linters in golangci configuration
- Add exclusions and formatters configurations in golangci configuration
- Replace deprecated `ioutil.ReadAll` with `io.ReadAll` in sse-decoder.go
- Use grouped variable declaration for `contentType` and `noCache` in sse-encoder.go
- Add error handling for `WriteString` operations in sse-encoder.go
- Add `nolint:exhaustive` comment for `kindOfData` switch statement in sse-encoder.go
- Adjust test assertions for better readability in sse_test.go
- Add error handling for `Encode` function calls in tests and benchmarks

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
Bo-Yi Wu
2025-04-08 08:38:00 +08:00
parent 47d1560405
commit c719ab6b53
5 changed files with 87 additions and 33 deletions
+2 -1
View File
@@ -21,8 +21,9 @@ jobs:
go-version: "^1" go-version: "^1"
- name: Setup golangci-lint - name: Setup golangci-lint
uses: golangci/golangci-lint-action@v6 uses: golangci/golangci-lint-action@v7
with: with:
version: v2.0
args: --verbose args: --verbose
- name: Bearer - name: Bearer
+48 -1
View File
@@ -1,3 +1,50 @@
version: "2"
linters: linters:
disable: default: none
enable:
- bodyclose
- dogsled
- dupl
- errcheck - errcheck
- exhaustive
- gochecknoinits
- goconst
- gocritic
- gocyclo
- goprintffuncname
- gosec
- govet
- ineffassign
- lll
- misspell
- nakedret
- noctx
- nolintlint
- rowserrcheck
- staticcheck
- unconvert
- unparam
- unused
- whitespace
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
+6 -5
View File
@@ -7,7 +7,6 @@ package sse
import ( import (
"bytes" "bytes"
"io" "io"
"io/ioutil"
) )
type decoder struct { type decoder struct {
@@ -22,7 +21,8 @@ func Decode(r io.Reader) ([]Event, error) {
func (d *decoder) dispatchEvent(event Event, data string) { func (d *decoder) dispatchEvent(event Event, data string) {
dataLength := len(data) dataLength := len(data)
if dataLength > 0 { if dataLength > 0 {
//If the data buffer's last character is a U+000A LINE FEED (LF) character, then remove the last character from the data buffer. // If the data buffer's last character is a U+000A LINE FEED (LF) character,
// then remove the last character from the data buffer.
data = data[:dataLength-1] data = data[:dataLength-1]
dataLength-- dataLength--
} }
@@ -37,7 +37,7 @@ func (d *decoder) dispatchEvent(event Event, data string) {
} }
func (d *decoder) decode(r io.Reader) ([]Event, error) { func (d *decoder) decode(r io.Reader) ([]Event, error) {
buf, err := ioutil.ReadAll(r) buf, err := io.ReadAll(r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -96,7 +96,8 @@ func (d *decoder) decode(r io.Reader) ([]Event, error) {
currentEvent.Id = string(value) currentEvent.Id = string(value)
case "retry": case "retry":
// If the field value consists of only characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), // If the field value consists of only characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9),
// then interpret the field value as an integer in base ten, and set the event stream's reconnection time to that integer. // then interpret the field value as an integer in base ten, and set the event stream's
// reconnection time to that integer.
// Otherwise, ignore the field. // Otherwise, ignore the field.
currentEvent.Id = string(value) currentEvent.Id = string(value)
case "data": case "data":
@@ -105,7 +106,7 @@ func (d *decoder) decode(r io.Reader) ([]Event, error) {
// then append a single U+000A LINE FEED (LF) character to the data buffer. // then append a single U+000A LINE FEED (LF) character to the data buffer.
dataBuffer.WriteString("\n") dataBuffer.WriteString("\n")
default: default:
//Otherwise. The field is ignored. // Otherwise. The field is ignored.
continue continue
} }
} }
+20 -18
View File
@@ -20,8 +20,10 @@ import (
const ContentType = "text/event-stream;charset=utf-8" const ContentType = "text/event-stream;charset=utf-8"
var contentType = []string{ContentType} var (
var noCache = []string{"no-cache"} contentType = []string{ContentType}
noCache = []string{"no-cache"}
)
var fieldReplacer = strings.NewReplacer( var fieldReplacer = strings.NewReplacer(
"\n", "\\n", "\n", "\\n",
@@ -48,48 +50,48 @@ func Encode(writer io.Writer, event Event) error {
func writeId(w stringWriter, id string) { func writeId(w stringWriter, id string) {
if len(id) > 0 { if len(id) > 0 {
w.WriteString("id:") _, _ = w.WriteString("id:")
fieldReplacer.WriteString(w, id) _, _ = fieldReplacer.WriteString(w, id)
w.WriteString("\n") _, _ = w.WriteString("\n")
} }
} }
func writeEvent(w stringWriter, event string) { func writeEvent(w stringWriter, event string) {
if len(event) > 0 { if len(event) > 0 {
w.WriteString("event:") _, _ = w.WriteString("event:")
fieldReplacer.WriteString(w, event) _, _ = fieldReplacer.WriteString(w, event)
w.WriteString("\n") _, _ = w.WriteString("\n")
} }
} }
func writeRetry(w stringWriter, retry uint) { func writeRetry(w stringWriter, retry uint) {
if retry > 0 { if retry > 0 {
w.WriteString("retry:") _, _ = w.WriteString("retry:")
w.WriteString(strconv.FormatUint(uint64(retry), 10)) _, _ = w.WriteString(strconv.FormatUint(uint64(retry), 10))
w.WriteString("\n") _, _ = w.WriteString("\n")
} }
} }
func writeData(w stringWriter, data interface{}) error { func writeData(w stringWriter, data interface{}) error {
w.WriteString("data:") _, _ = w.WriteString("data:")
bData, ok := data.([]byte) bData, ok := data.([]byte)
if ok { if ok {
dataReplacer.WriteString(w, string(bData)) _, _ = dataReplacer.WriteString(w, string(bData))
w.WriteString("\n\n") _, _ = w.WriteString("\n\n")
return nil return nil
} }
switch kindOfData(data) { switch kindOfData(data) { //nolint:exhaustive
case reflect.Struct, reflect.Slice, reflect.Map: case reflect.Struct, reflect.Slice, reflect.Map:
err := json.NewEncoder(w).Encode(data) err := json.NewEncoder(w).Encode(data)
if err != nil { if err != nil {
return err return err
} }
w.WriteString("\n") _, _ = w.WriteString("\n")
default: default:
dataReplacer.WriteString(w, fmt.Sprint(data)) _, _ = dataReplacer.WriteString(w, fmt.Sprint(data))
w.WriteString("\n\n") _, _ = w.WriteString("\n\n")
} }
return nil return nil
} }
+11 -8
View File
@@ -170,22 +170,25 @@ func TestEncodeFloat(t *testing.T) {
func TestEncodeStream(t *testing.T) { func TestEncodeStream(t *testing.T) {
w := new(bytes.Buffer) w := new(bytes.Buffer)
Encode(w, Event{ _ = Encode(w, Event{
Event: "float", Event: "float",
Data: 1.5, Data: 1.5,
}) })
Encode(w, Event{ _ = Encode(w, Event{
Id: "123", Id: "123",
Data: map[string]interface{}{"foo": "bar", "bar": "foo"}, Data: map[string]interface{}{"foo": "bar", "bar": "foo"},
}) })
Encode(w, Event{ _ = Encode(w, Event{
Id: "124", Id: "124",
Event: "chat", Event: "chat",
Data: "hi! dude", Data: "hi! dude",
}) })
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") assert.Equal(t, w.String(),
"event:float\ndata:1.5\n\n"+
"id:123\ndata:{\"bar\":\"foo\",\"foo\":\"bar\"}\n\n"+
"id:124\nevent:chat\ndata:hi! dude\n\n")
} }
func TestRenderSSE(t *testing.T) { func TestRenderSSE(t *testing.T) {
@@ -207,7 +210,7 @@ func BenchmarkResponseWriter(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
(Event{ _ = (Event{
Event: "new_message", Event: "new_message",
Data: "hi! how are you? I am fine. this is a long stupid message!!!", Data: "hi! how are you? I am fine. this is a long stupid message!!!",
}).Render(w) }).Render(w)
@@ -219,7 +222,7 @@ func BenchmarkFullSSE(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Encode(buf, Event{ _ = Encode(buf, Event{
Event: "new_message", Event: "new_message",
Id: "13435", Id: "13435",
Retry: 10, Retry: 10,
@@ -234,7 +237,7 @@ func BenchmarkNoRetrySSE(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Encode(buf, Event{ _ = Encode(buf, Event{
Event: "new_message", Event: "new_message",
Id: "13435", Id: "13435",
Data: "hi! how are you? I am fine. this is a long stupid message!!!", Data: "hi! how are you? I am fine. this is a long stupid message!!!",
@@ -248,7 +251,7 @@ func BenchmarkSimpleSSE(b *testing.B) {
b.ResetTimer() b.ResetTimer()
b.ReportAllocs() b.ReportAllocs()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Encode(buf, Event{ _ = Encode(buf, Event{
Event: "new_message", Event: "new_message",
Data: "hi! how are you? I am fine. this is a long stupid message!!!", Data: "hi! how are you? I am fine. this is a long stupid message!!!",
}) })