Skip to content

feat: additional helpers packages #416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ linters-settings:
main:
files:
- $all
- "!**/uuid_test.go"
deny:
- pkg: "github.com/gofrs/uuid"
desc: 'use github.com/google/uuid instead'
desc: 'use github.com/google/uuid instead'
- pkg: "golang.org/x/exp/slices"
desc: 'use "slices" instead'
2 changes: 1 addition & 1 deletion config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ package config
import (
"fmt"
"reflect"
"slices"
"time"

"github.com/fsnotify/fsnotify"
"github.com/joho/godotenv"
"github.com/spf13/viper"
"golang.org/x/exp/slices"
)

func (c *Config) load() {
Expand Down
21 changes: 21 additions & 0 deletions config/valueloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package config

// SingleValueLoader returns a ValueLoader that always returns the same value.
func SingleValueLoader[T any](v T) ValueLoader[T] {
return &loader[T]{v}
}

// ValueLoader is an interface that can be used to load a value.
type ValueLoader[T any] interface {
Load() T
}

// loader is a ValueLoader that always returns the same value.
type loader[T any] struct {
v T
}

// Load returns the value.
func (l *loader[T]) Load() T {
return l.v
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-redis/redis/v8 v8.11.5
github.com/gofrs/uuid v4.4.0+incompatible
github.com/golang/mock v1.6.0
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
Expand Down Expand Up @@ -47,9 +48,9 @@ require (
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.22.0
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a
golang.org/x/oauth2 v0.19.0
golang.org/x/sync v0.7.0
golang.org/x/text v0.14.0
google.golang.org/api v0.172.0
google.golang.org/protobuf v1.33.0
gopkg.in/alexcesaro/statsd.v2 v2.0.0
Expand Down Expand Up @@ -142,10 +143,10 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.17.0 // indirect
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down
15 changes: 15 additions & 0 deletions httputil/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package httputil
import (
"io"
"net/http"
"strings"
)

const (
headerXForwardedFor = "X-Forwarded-For"
)

// CloseResponse closes the response's body. But reads at least some of the body so if it's
Expand All @@ -15,3 +20,13 @@ func CloseResponse(resp *http.Response) {
resp.Body.Close()
}
}

func GetRequestIP(req *http.Request) string {
addresses := strings.Split(req.Header.Get(headerXForwardedFor), ",")
if addresses[0] == "" {
splits := strings.Split(req.RemoteAddr, ":")
return strings.Join(splits[:len(splits)-1], ":") // When there is no load-balancer
}

return strings.ReplaceAll(addresses[0], " ", "")
}
48 changes: 48 additions & 0 deletions httputil/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package httputil_test

import (
"net/http"
"testing"

"github.com/stretchr/testify/require"

"github.com/rudderlabs/rudder-go-kit/httputil"
)

func TestGetRequestIP(t *testing.T) {
testCases := []struct {
name string
headerValue string
remoteAddr string
expectedResult string
}{
{
name: "X-Forwarded-For provided",
headerValue: "192.168.0.1, 192.168.0.2",
remoteAddr: "192.168.0.3:8080",
expectedResult: "192.168.0.1",
},
{
name: "X-Forwarded-For empty, RemoteAddr provided",
headerValue: "",
remoteAddr: "192.168.0.4:8080",
expectedResult: "192.168.0.4",
},
{
name: "X-Forwarded-For and RemoteAddr both empty",
headerValue: "",
remoteAddr: "",
expectedResult: "",
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
req := &http.Request{
Header: http.Header{"X-Forwarded-For": {testCase.headerValue}},
RemoteAddr: testCase.remoteAddr,
}
require.Equal(t, testCase.expectedResult, httputil.GetRequestIP(req))
})
}
}
80 changes: 80 additions & 0 deletions sanitize/sanitize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package sanitize

import (
"strings"
"unicode"

"golang.org/x/text/unicode/rangetable"
)

// invisibleRunes unicode.IsPrint does not include all invisible characters,
// so I got this list from https://invisible-characters.com/
var invisibleRunes = []rune{
'\u0000', // NULL
'\u0009', // CHARACTER TABULATION
'\u00A0', // NO-BREAK SPACE
'\u00AD', // SOFT HYPHEN
'\u034F', // COMBINING GRAPHEME JOINER
'\u061C', // ARABIC LETTER MARK
'\u115F', // HANGUL CHOSEONG FILLER
'\u1160', // HANGUL JUNGSEONG FILLER
'\u17B4', // KHMER VOWEL INHERENT AQ
'\u17B5', // KHMER VOWEL INHERENT AA
'\u180E', // MONGOLIAN VOWEL SEPARATOR
'\u2000', // EN QUAD
'\u2001', // EM QUAD
'\u2002', // EN SPACE
'\u2003', // EM SPACE
'\u2004', // THREE-PER-EM SPACE
'\u2005', // FOUR-PER-EM SPACE
'\u2006', // SIX-PER-EM SPACE
'\u2007', // FIGURE SPACE
'\u2008', // PUNCTUATION SPACE
'\u2009', // THIN SPACE
'\u200A', // HAIR SPACE
'\u200B', // ZERO WIDTH SPACE
'\u200C', // ZERO WIDTH NON-JOINER
'\u200D', // ZERO WIDTH JOINER
'\u200E', // LEFT-TO-RIGHT MARK
'\u200F', // RIGHT-TO-LEFT MARK
'\u202F', // NARROW NO-BREAK SPACE
'\u205F', // MEDIUM MATHEMATICAL SPACE
'\u2060', // WORD JOINER
'\u2061', // FUNCTION APPLICATION
'\u2062', // INVISIBLE TIMES
'\u2063', // INVISIBLE SEPARATOR
'\u2064', // INVISIBLE PLUS
'\u206A', // INHIBIT SYMMETRIC SWAPPING
'\u206B', // ACTIVATE SYMMETRIC SWAPPING
'\u206C', // INHIBIT ARABIC FORM SHAPING
'\u206D', // ACTIVATE ARABIC FORM SHAPING
'\u206E', // NATIONAL DIGIT SHAPES
'\u206F', // NOMINAL DIGIT SHAPES
'\u3000', // IDEOGRAPHIC SPACE
'\u2800', // BRAILLE PATTERN BLANK
'\u3164', // HANGUL FILLER
'\uFEFF', // ZERO WIDTH NO-BREAK SPACE
'\uFFA0', // HALF WIDTH HANGUL FILLER
}

var invisibleRangeTable *unicode.RangeTable

func init() {
invisibleRangeTable = rangetable.New(invisibleRunes...)
}

// Unicode removes irregularly invisible characters from a string.
//
// Irregularly invisible characters are defined as:
// - Non-printable characters according to Go's unicode package (unicode.IsPrint).
// - Characters in the invisibleRunes list (https://invisible-characters.com/).
//
// Note: Regular ASCII space (0x20) is not removed.
func Unicode(str string) string {
return strings.Map(func(r rune) rune {
if unicode.Is(invisibleRangeTable, r) || !unicode.IsPrint(r) {
return -1
}
return r
}, str)
}
79 changes: 79 additions & 0 deletions sanitize/sanitize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package sanitize

import (
"fmt"
"testing"
"unicode"

"github.com/stretchr/testify/require"
)

var out string

func BenchmarkMessageID(b *testing.B) {
dirtyMessageID := "\u0000 Test foo_bar-baz \u034F 123-222 "
properMessageID := "123e4567-e89b-12d3-a456-426614174000"

b.Run("in-place for loop - dirty", func(b *testing.B) {
for i := 0; i < b.N; i++ {
out = sanitizeMessageIDForLoop(dirtyMessageID)
}
})

b.Run("in-place for loop - proper", func(b *testing.B) {
for i := 0; i < b.N; i++ {
out = sanitizeMessageIDForLoop(properMessageID)
}
})

b.Run("strings map - dirty", func(b *testing.B) {
for i := 0; i < b.N; i++ {
out = Unicode(dirtyMessageID)
}
})

b.Run("strings map - proper", func(b *testing.B) {
for i := 0; i < b.N; i++ {
out = Unicode(properMessageID)
}
})
}

// incorrect implementation of sanitizeMessageID, but used for benchmarking
func sanitizeMessageIDForLoop(messageID string) string {
for i, r := range messageID {
if unicode.IsPrint(r) {
continue
}
if !unicode.Is(invisibleRangeTable, r) {
continue
}

messageID = messageID[:i] + messageID[i+1:]
}
return messageID
}

func TestSanitizeMessageID(t *testing.T) {
testcases := []struct {
in string
out string
}{
{"\u0000 Test \u0000foo_bar-baz 123-222 \u0000", " Test foo_bar-baz 123-222 "},
{"\u0000", ""},
{"\u0000 ", " "},
{"\u0000 \u0000", " "},
{"\u00A0\t\n\r\u034F", ""},
{"τυχαίο;", "τυχαίο;"},
}

for _, tc := range testcases {
cleanMessageID := Unicode(tc.in)
require.Equal(t, tc.out, cleanMessageID, fmt.Sprintf("%#v -> %#v", tc.in, tc.out))
}

for _, r := range invisibleRunes {
cleanMessageID := Unicode(string(r))
require.Empty(t, cleanMessageID, fmt.Sprintf("%U", r))
}
}
23 changes: 23 additions & 0 deletions stringify/stringify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package stringify

import (
"encoding/json"
"fmt"
)

// Any converts any data to string
func Any(data any) string {
if data == nil {
return ""
}
switch d := data.(type) {
case string:
return d
default:
dataBytes, err := json.Marshal(d)
if err != nil {
return fmt.Sprint(d)
}
return string(dataBytes)
}
}
Loading
Loading