From 2d6ebedb0f2217d81d26c8672d81d3d26a587e7a Mon Sep 17 00:00:00 2001 From: Richard Hillmann Date: Thu, 16 Jun 2022 17:36:08 +0200 Subject: [PATCH] feat: Replace data fillcontent with custom reader --- .github/workflows/main.yml | 2 +- .golangci.yml | 1 + app.go | 19 +-------- content.go | 66 ++++++++++++++++++++++++++++++ content_test.go | 82 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 content.go create mode 100644 content_test.go diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e6afbeb..61e3f4c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest env: GO_VERSION: 1.17 - GOLANGCI_LINT_VERSION: v1.44.0 + GOLANGCI_LINT_VERSION: v1.46.2 CGO_ENABLED: 0 steps: diff --git a/.golangci.yml b/.golangci.yml index 86523a5..1713c7b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -39,6 +39,7 @@ linters: - varnamelen - nilnil - ifshort + - exhaustruct issues: exclude-use-default: false diff --git a/app.go b/app.go index 36665c3..965c213 100644 --- a/app.go +++ b/app.go @@ -1,7 +1,6 @@ package main import ( - "bytes" "crypto/tls" "crypto/x509" "encoding/json" @@ -181,7 +180,7 @@ func dataHandler(w http.ResponseWriter, r *http.Request) { attachment = false } - content := fillContent(size) + content := &contentReader{size: size} if attachment { w.Header().Set("Content-Disposition", "Attachment") @@ -311,22 +310,6 @@ func healthHandler(w http.ResponseWriter, req *http.Request) { } } -func fillContent(length int64) io.ReadSeeker { - charset := "-ABCDEFGHIJKLMNOPQRSTUVWXYZ" - b := make([]byte, length) - - for i := range b { - b[i] = charset[i%len(charset)] - } - - if length > 0 { - b[0] = '|' - b[length-1] = '|' - } - - return bytes.NewReader(b) -} - func getEnv(key, fallback string) string { value := os.Getenv(key) if len(value) == 0 { diff --git a/content.go b/content.go new file mode 100644 index 0000000..fce810e --- /dev/null +++ b/content.go @@ -0,0 +1,66 @@ +package main + +import ( + "errors" + "io" +) + +const contentCharset = "-ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +type contentReader struct { + size int64 + cur int64 +} + +// Read implements the io.Read interface. +func (r *contentReader) Read(p []byte) (int, error) { + length := r.size - 1 + + if r.cur >= length { + return 0, io.EOF + } + if len(p) == 0 { + return 0, nil + } + + var n int + if r.cur == 0 { + p[n] = '|' + r.cur++ + n++ + } + + for n < len(p) && r.cur <= length { + p[n] = contentCharset[int(r.cur)%len(contentCharset)] + r.cur++ + n++ + } + + if r.cur >= length { + p[n-1] = '|' + } + + return n, nil +} + +// Seek implements the io.Seek interface. +func (r *contentReader) Seek(offset int64, whence int) (int64, error) { + switch whence { + default: + return 0, errors.New("seek: invalid whence") + case io.SeekStart: + offset += 0 + case io.SeekCurrent: + offset += r.cur + case io.SeekEnd: + offset += r.size - 1 + } + + if offset < 0 { + return 0, errors.New("seek: invalid offset") + } + + r.cur = offset + + return offset, nil +} diff --git a/content_test.go b/content_test.go new file mode 100644 index 0000000..29eff84 --- /dev/null +++ b/content_test.go @@ -0,0 +1,82 @@ +package main + +import ( + "io" + "testing" +) + +func Test_contentReader_Read(t *testing.T) { + tests := []struct { + name string + size int64 + content string + }{ + { + name: "simple", + size: 40, + content: "|ABCDEFGHIJKLMNOPQRSTUVWXYZ-ABCDEFGHIJK|", + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + d := &contentReader{size: test.size} + + b, err := io.ReadAll(d) + if err != nil { + t.Errorf("contentReader.Read() error = %v", err) + return + } + + if string(b) != test.content { + t.Errorf("return content does not match expected value: got %s want %s", b, test.content) + } + }) + } +} + +func Test_contentReader_ReadSeek(t *testing.T) { + tests := []struct { + name string + offset int64 + seekWhence int + size int64 + content string + }{ + { + name: "simple", + offset: 10, + seekWhence: io.SeekCurrent, + size: 40, + content: "JKLMNOPQRSTUVWXYZ-ABCDEFGHIJK|", + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + d := &contentReader{size: test.size} + + _, err := d.Seek(test.offset, test.seekWhence) + if err != nil { + t.Errorf("contentReader.Seek() error = %v", err) + return + } + + b, err := io.ReadAll(d) + if err != nil { + t.Errorf("contentReader.Read() error = %v", err) + return + } + + if string(b) != test.content { + t.Errorf("return content does not match expected value: got %s want %s", b, test.content) + } + }) + } +}