diff --git a/dumper/doc.go b/dumper/doc.go deleted file mode 100644 index 8947471..0000000 --- a/dumper/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package dumper is a utility package for dumping http request contents -package dumper - -// The dumper package is a utility for dumping HTTP request contents, useful for debugging and logging purposes. diff --git a/dumper/response.go b/dumper/response.go deleted file mode 100644 index b9698e9..0000000 --- a/dumper/response.go +++ /dev/null @@ -1,77 +0,0 @@ -package dumper - -import ( - "bufio" - "bytes" - "fmt" - "io" - "net" - "net/http" - - echo "github.com/theopenlane/echox" -) - -// Dumper is a response writer that captures the response body -type Dumper struct { - // ResponseWriter is the original response writer - http.ResponseWriter - // mw is the multi writer that writes to the original response writer and the buffer - mw io.Writer - // buf is the buffer that captures the response body - buf *bytes.Buffer -} - -// NewDumper returns a new Dumper -func NewDumper(resp *echo.Response) *Dumper { - buf := new(bytes.Buffer) - - return &Dumper{ - ResponseWriter: resp.Writer, - // multi - mw: io.MultiWriter(resp.Writer, buf), - buf: buf, - } -} - -// Write writes the response body -func (d *Dumper) Write(b []byte) (int, error) { - // Write to the original response writer and the buffer - nBytes, err := d.mw.Write(b) - if err != nil { - err = fmt.Errorf("error writing response: %w", err) - } - - return nBytes, err -} - -// GetResponse returns the response body out of the buffer -func (d *Dumper) GetResponse() string { - // Return the response body out of the buffer - return d.buf.String() -} - -// Flush flushes the response writer if it implements http.Flusher interface and is not nil -func (d *Dumper) Flush() { - if flusher, ok := d.ResponseWriter.(http.Flusher); ok { - // Flush the response writer - flusher.Flush() - } -} - -// Hijack hijacks the response writer and returns the connection and read writer -func (d *Dumper) Hijack() (net.Conn, *bufio.ReadWriter, error) { - // Hijack the response writer and return the connection and read writer; does not work with HTTP/2 so needs to be checked - if hijacker, ok := d.ResponseWriter.(http.Hijacker); ok { - conn, rw, err := hijacker.Hijack() - - if err != nil { - err = fmt.Errorf("error hijacking response: %w", err) - } - // close the connection - defer conn.Close() - - return conn, rw, err - } - - return nil, nil, nil -} diff --git a/dumper/response_test.go b/dumper/response_test.go deleted file mode 100644 index ef068c2..0000000 --- a/dumper/response_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package dumper - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/brianvoe/gofakeit/v7" - "github.com/stretchr/testify/require" - - // TODO: look into updating the echox dependency - echo "github.com/theopenlane/echox" -) - -func TestDumper(t *testing.T) { - responseString := gofakeit.SentenceSimple() - e := echo.New() - - e.GET("/", func(c echo.Context) error { - respDumper := NewDumper(c.Response()) - c.Response().Writer = respDumper - - defer func() { - require.Equal(t, respDumper.GetResponse(), responseString) - }() - - return c.String(http.StatusOK, responseString) - }) - - w := httptest.NewRecorder() - r := httptest.NewRequest(http.MethodGet, "/", nil) - e.ServeHTTP(w, r) - - require.Equal(t, w.Body.String(), responseString) -} diff --git a/slice/doc.go b/slice/doc.go deleted file mode 100644 index c10145b..0000000 --- a/slice/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package sliceutil contains utilities for working with slices in Go -package sliceutil diff --git a/slice/sliceutil.go b/slice/sliceutil.go deleted file mode 100644 index f4395d6..0000000 --- a/slice/sliceutil.go +++ /dev/null @@ -1,163 +0,0 @@ -package sliceutil - -import ( - "slices" - "strconv" -) - -// PruneEmptyStrings from the slice -func PruneEmptyStrings(v []string) []string { - return PruneEqual(v, "") -} - -// PruneEqual removes items from the slice equal to the specified value -func PruneEqual[T comparable](inputSlice []T, equalTo T) (r []T) { - for i := range inputSlice { - if inputSlice[i] != equalTo { - r = append(r, inputSlice[i]) - } - } - - return -} - -// Dedupe removes duplicates from a slice of elements preserving the order -func Dedupe[T comparable](inputSlice []T) (result []T) { - seen := make(map[T]struct{}) - for _, inputValue := range inputSlice { - if _, ok := seen[inputValue]; !ok { - seen[inputValue] = struct{}{} - - result = append(result, inputValue) - } - } - - return -} - -// Contains if a slice contains an element -func Contains[T comparable](inputSlice []T, element T) bool { - return slices.Contains(inputSlice, element) -} - -// ContainsItems checks if s1 contains s2 -func ContainsItems[T comparable](s1 []T, s2 []T) bool { - return slices.ContainsFunc(s1, func(e T) bool { - return Contains(s2, e) - }) -} - -// ToInt converts a slice of strings to a slice of ints -func ToInt(s []string) ([]int, error) { - var ns []int - - for _, ss := range s { - n, err := strconv.Atoi(ss) - if err != nil { - return nil, err - } - - ns = append(ns, n) - } - - return ns, nil -} - -// Equal checks if the items of two slices are equal respecting the order -func Equal[T comparable](s1, s2 []T) bool { - return slices.Equal(s1, s2) -} - -// IsEmpty checks if the slice has length zero -func IsEmpty[V comparable](s []V) bool { - return len(s) == 0 -} - -// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified -// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, -// the number of appearances of each of them in both lists should match -func ElementsMatch[V comparable](s1, s2 []V) bool { - if IsEmpty(s1) && IsEmpty(s2) { - return true - } - - extraS1, extrS2 := Diff(s1, s2) - - return IsEmpty(extraS1) && IsEmpty(extrS2) -} - -// Diff calculates the extra elements between two sequences -func Diff[V comparable](s1, s2 []V) (extraS1, extraS2 []V) { - s1Len := len(s1) - s2Len := len(s2) - - visited := make([]bool, s2Len) - - for i := 0; i < s1Len; i++ { - element := s1[i] - found := false - - for j := 0; j < s2Len; j++ { - if visited[j] { - continue - } - - if s2[j] == element { - visited[j] = true - found = true - - break - } - } - - if !found { - extraS1 = append(extraS1, element) - } - } - - for j := 0; j < s2Len; j++ { - if visited[j] { - continue - } - - extraS2 = append(extraS2, s2[j]) - } - - return -} - -// Merge and dedupe multiple items -func Merge[V comparable](ss ...[]V) []V { - var final []V - - for _, s := range ss { - final = append(final, s...) - } - - return Dedupe(final) -} - -// MergeItems takes in multiple items of a comparable type and merges them into a single slice -// while removing any duplicates. It then returns the resulting slice with duplicate elements removed -func MergeItems[V comparable](items ...V) []V { - return Dedupe(items) -} - -// FirstNonZero function takes a slice of comparable type inputs, and returns -// the first non-zero element in the slice along with a boolean value indicating if a non-zero element was found or not -func FirstNonZero[T comparable](inputs []T) (T, bool) { - var zero T - - for _, v := range inputs { - if v != zero { - return v, true - } - } - - return zero, false -} - -// Clone a slice through built-in copy -func Clone[T comparable](t []T) []T { - return slices.Clone(t) -} diff --git a/slice/sliceutil_test.go b/slice/sliceutil_test.go deleted file mode 100644 index c26d040..0000000 --- a/slice/sliceutil_test.go +++ /dev/null @@ -1,247 +0,0 @@ -package sliceutil - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -var unexpected = "unexpected result" - -func TestPruneEmptyStrings(t *testing.T) { - test := []string{"a", "", "", "b"} - // converts back - res := PruneEmptyStrings(test) - require.Equal(t, []string{"a", "b"}, res, "strings not pruned correctly") -} - -func TestPruneEqual(t *testing.T) { - testStr := []string{"a", "", "", "b"} - // converts back - resStr := PruneEqual(testStr, "b") - require.Equal(t, []string{"a", "", ""}, resStr, "strings not pruned correctly") - - testInt := []int{1, 2, 3, 4} - // converts back - resInt := PruneEqual(testInt, 2) - require.Equal(t, []int{1, 3, 4}, resInt, "ints not pruned correctly") -} - -func TestDedupe(t *testing.T) { - testStr := []string{"a", "a", "b", "b"} - // converts back - resStr := Dedupe(testStr) - require.Equal(t, []string{"a", "b"}, resStr, "strings not deduped correctly") - - testInt := []int{1, 1, 2, 2} - // converts back - res := Dedupe(testInt) - require.Equal(t, []int{1, 2}, res, "ints not deduped correctly") -} - -func TestContains(t *testing.T) { - testSliceStr := []string{"a", "b"} - testElemStr := "a" - // converts back - resStr := Contains(testSliceStr, testElemStr) - require.True(t, resStr, unexpected) - - testSliceInt := []int{1, 2} - testElemInt := 1 - // converts back - resInt := Contains(testSliceInt, testElemInt) - require.True(t, resInt, unexpected) -} - -func TestContainsItems(t *testing.T) { - test1Str := []string{"a", "b", "c"} - test2Str := []string{"a", "c"} - // converts back - resStr := ContainsItems(test1Str, test2Str) - require.True(t, resStr, unexpected) - - test1Int := []int{1, 2, 3} - test2Int := []int{1, 3} - // converts back - resInt := ContainsItems(test1Int, test2Int) - require.True(t, resInt, unexpected) -} - -func TestToInt(t *testing.T) { - test1 := []string{"1", "2"} - test2 := []int{1, 2} - // converts back - res, err := ToInt(test1) - require.Nil(t, err) - require.Equal(t, test2, res, unexpected) -} - -func TestEqual(t *testing.T) { - test1 := []string{"1", "2"} - require.True(t, Equal(test1, test1), unexpected) - require.False(t, Equal(test1, []string{"2", "1"}), unexpected) -} - -func TestIsEmpty(t *testing.T) { - require.True(t, IsEmpty([]string{})) - require.False(t, IsEmpty([]string{"a"})) -} - -func TestElementsMatch(t *testing.T) { - require.True(t, ElementsMatch([]string{}, []string{})) - require.True(t, ElementsMatch([]int{1}, []int{1})) - require.True(t, ElementsMatch([]int{1, 2}, []int{2, 1})) - require.False(t, ElementsMatch([]int{1}, []int{2})) -} - -func TestDiff(t *testing.T) { - s1 := []int{1, 2, 3} - s2 := []int{3, 4, 5} - extraS1, extraS2 := Diff(s1, s2) - require.ElementsMatch(t, extraS1, []int{1, 2}) - require.ElementsMatch(t, extraS2, []int{4, 5}) -} - -func TestMerge(t *testing.T) { - tests := []struct { - input [][]int - expected []int - }{ - {[][]int{{1, 2, 3}, {3, 4, 5}, {5, 6, 7}}, []int{1, 2, 3, 4, 5, 6, 7}}, - {[][]int{{1, 1, 2}, {2, 3, 3}, {3, 4, 5}}, []int{1, 2, 3, 4, 5}}, - {[][]int{{1, 2, 3}, {4, 5, 6}}, []int{1, 2, 3, 4, 5, 6}}, - } - - for _, test := range tests { - output := Merge(test.input...) - require.ElementsMatch(t, test.expected, output) - } -} - -func TestMergeItems(t *testing.T) { - tests := []struct { - input []int - expected []int - }{ - {[]int{1, 2, 3, 3, 4, 5, 5, 6, 7}, []int{1, 2, 3, 4, 5, 6, 7}}, - {[]int{1, 1, 2, 2, 3, 3}, []int{1, 2, 3}}, - {[]int{1, 2, 3, 4, 5, 6}, []int{1, 2, 3, 4, 5, 6}}, - } - - for _, test := range tests { - // merge single basic types (int, string, etc) - output := MergeItems(test.input...) - require.ElementsMatch(t, test.expected, output) - } -} - -func TestFirstNonZeroInt(t *testing.T) { - testCases := []struct { - Input []int - ExpectedOutput interface{} - ExpectedFound bool - }{ - { - Input: []int{0, 0, 3, 5, 10}, - ExpectedOutput: 3, - ExpectedFound: true, - }, - { - Input: []int{}, - ExpectedOutput: 0, - ExpectedFound: false, - }, - } - - for _, tc := range testCases { - output, found := FirstNonZero(tc.Input) - require.Equal(t, tc.ExpectedOutput, output) - require.Equal(t, tc.ExpectedFound, found) - } -} - -func TestFirstNonZeroString(t *testing.T) { - testCases := []struct { - Input []string - ExpectedOutput interface{} - ExpectedFound bool - }{ - { - Input: []string{"", "foo", "test"}, - ExpectedOutput: "foo", - ExpectedFound: true, - }, - { - Input: []string{}, - ExpectedOutput: "", - ExpectedFound: false, - }, - } - - for _, tc := range testCases { - output, found := FirstNonZero(tc.Input) - require.Equal(t, tc.ExpectedOutput, output) - require.Equal(t, tc.ExpectedFound, found) - } -} - -func TestFirstNonZeroFloat(t *testing.T) { - testCases := []struct { - Input []float64 - ExpectedOutput interface{} - ExpectedFound bool - }{ - { - Input: []float64{0.0, 0.0, 0.0, 1.2, 3.4}, - ExpectedOutput: 1.2, - ExpectedFound: true, - }, - { - Input: []float64{}, - ExpectedOutput: 0.0, - ExpectedFound: false, - }, - } - - for _, tc := range testCases { - output, found := FirstNonZero(tc.Input) - require.Equal(t, tc.ExpectedOutput, output) - require.Equal(t, tc.ExpectedFound, found) - } -} - -func TestFirstNonZeroBool(t *testing.T) { - testCases := []struct { - Input []bool - ExpectedOutput interface{} - ExpectedFound bool - }{ - { - Input: []bool{false, false, false}, - ExpectedOutput: false, - ExpectedFound: false, - }, - { - Input: []bool{}, - ExpectedOutput: false, - ExpectedFound: false, - }, - } - - for _, tc := range testCases { - output, found := FirstNonZero(tc.Input) - require.Equal(t, tc.ExpectedOutput, output) - require.Equal(t, tc.ExpectedFound, found) - } -} - -func TestClone(t *testing.T) { - intSlice := []int{1, 2, 3} - require.Equal(t, intSlice, Clone(intSlice)) - - stringSlice := []string{"a", "b", "c"} - require.Equal(t, stringSlice, Clone(stringSlice)) - - bytesSlice := []byte{1, 2, 3} - require.Equal(t, bytesSlice, Clone(bytesSlice)) -} diff --git a/sqlite/doc.go b/sqlite/doc.go deleted file mode 100644 index eb885a4..0000000 --- a/sqlite/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package sqlite implements a connect hook around the sqlite3 driver so that the underlying connection can be fetched from the driver for more advanced operations such as backups. See: https://github.com/mattn/go-sqlite3/blob/master/_example/hook/hook.go. -// To use make sure you import this package so that the init code registers the driver: import _ github.com/theopenlane/utils/sqlite -// Then you can use sql.Open in the same way you would with sqlite3: sql.Open("_sqlite3", "path/to/database.db") -package sqlite diff --git a/sqlite/errors.go b/sqlite/errors.go deleted file mode 100644 index ec4cf43..0000000 --- a/sqlite/errors.go +++ /dev/null @@ -1,10 +0,0 @@ -package sqlite - -import ( - "errors" -) - -// Error constants -var ( - ErrUnknownConnectionType = errors.New("unknown connection type") -) diff --git a/sqlite/sqlite.go b/sqlite/sqlite.go deleted file mode 100644 index 29b30b9..0000000 --- a/sqlite/sqlite.go +++ /dev/null @@ -1,115 +0,0 @@ -package sqlite - -import ( - "database/sql" - "database/sql/driver" - "sync" - - "github.com/mattn/go-sqlite3" -) - -// init creates the connections map and registers the driver with the SQL package. -func init() { - conns = make(map[uint64]*Conn) - - sql.Register(DriverName, &Driver{}) -} - -// In order to use this driver, specify the DriverName to sql.Open. -const ( - DriverName = "_sqlite3" -) - -var ( - seq uint64 - mu sync.Mutex - conns map[uint64]*Conn -) - -// Driver embeds a sqlite3 driver but overrides the Open function to ensure the -// connection created is a local connection with a sequence ID. It then maintains the -// connection locally until it is closed so that the underlying sqlite3 connection can -// be returned on demand. -type Driver struct { - sqlite3.SQLiteDriver -} - -// Open implements the sql.Driver interface and returns a sqlite3 connection that can -// be fetched by the user using GetLastConn. The connection ensures it's cleaned up -// when it's closed. This method is not used by the user, but rather by sql.Open. -func (d *Driver) Open(dsn string) (driver.Conn, error) { - inner, err := d.SQLiteDriver.Open(dsn) - if err != nil { - return nil, err - } - - var ( - ok bool - sconn *sqlite3.SQLiteConn - ) - - if sconn, ok = inner.(*sqlite3.SQLiteConn); !ok { - return inner, ErrUnknownConnectionType - } - - mu.Lock() - seq++ - conn := &Conn{cid: seq, SQLiteConn: sconn} - conns[conn.cid] = conn - mu.Unlock() - - return conn, nil -} - -// Conn wraps a sqlite3.SQLiteConn and maintains an ID so that the connection can be closed -type Conn struct { - cid uint64 - *sqlite3.SQLiteConn -} - -// Close executes when the connection is closed so the connection is removed from the array of connections -func (c *Conn) Close() error { - mu.Lock() - delete(conns, c.cid) - mu.Unlock() - - return c.SQLiteConn.Close() -} - -// Backup function is here to provide access to SQLite3 backup functionality -// on the sqlite3 connection. For more details on how to use the backup see the -// following links: -// -// https://www.sqlite.org/backup.html -// https://github.com/mattn/go-sqlite3/blob/master/_example/hook/hook.go -// https://github.com/mattn/go-sqlite3/blob/master/backup_test.go -// -// This is primarily used by the backups package and this method provides access -// directly to the underlying CGO call. This means the CGO call must be called correctly -// for example: the Finish() method MUST BE CALLED otherwise your code will panic. -func (c *Conn) Backup(dest string, srcConn *Conn, src string) (*sqlite3.SQLiteBackup, error) { - return c.SQLiteConn.Backup(dest, srcConn.SQLiteConn, src) -} - -// GetLastConn returns the last connection created by the driver. Unfortunately, there -// is no way to guarantee which connection will be returned since the sql.Open package -// does not provide any interface to the underlying connection object. The best a -// process can do is ping the server to open a new connection and then fetch the last -// connection immediately. -func GetLastConn() (*Conn, bool) { - mu.Lock() - - defer mu.Unlock() - - conn, ok := conns[seq] - - return conn, ok -} - -// NumConns is for testing purposes, returns the number of active connections -func NumConns() int { - mu.Lock() - defer mu.Unlock() - - return len(conns) -} diff --git a/sqlite/sqlite_test.go b/sqlite/sqlite_test.go deleted file mode 100644 index e793acf..0000000 --- a/sqlite/sqlite_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package sqlite_test - -import ( - "context" - "database/sql" - "fmt" - "io" - "path/filepath" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/theopenlane/utils/sqlite" -) - -func TestDriver(t *testing.T) { - db, err := sql.Open("_sqlite3", filepath.Join(t.TempDir(), "test.db")) - require.NoError(t, err, "could not open connection to testdb") - - conn, err := db.Conn(context.Background()) - require.NoError(t, err, "could not create connection with custom driver") - require.Equal(t, 1, sqlite.NumConns()) - - // Get the underlying sqlite3 connection - sqlc, ok := sqlite.GetLastConn() - require.True(t, ok, "connection was not in connection map?") - require.IsType(t, &sqlite.Conn{}, sqlc, "connection of wrong type returned") - - err = conn.Close() - require.NoError(t, err, "could not close connection") - - err = db.Close() - require.NoError(t, err, "could not close database") - require.Equal(t, 0, sqlite.NumConns()) -} - -func TestOpenMany(t *testing.T) { - tmpdir := t.TempDir() - expectedConnections := 12 - closers := make([]io.Closer, expectedConnections) - conns := make([]*sqlite.Conn, expectedConnections) - - for i := 0; i < expectedConnections; i++ { - db, err := sql.Open("_sqlite3", filepath.Join(tmpdir, fmt.Sprintf("test-%d.db", i+1))) - require.NoError(t, err, "could not open connection to database") - require.NoError(t, db.PingContext(context.Background()), "could not ping database to establish a connection") - closers[i] = db - - var ok bool - conns[i], ok = sqlite.GetLastConn() - require.True(t, ok, "expected new connection") - } - - // Ensure that we created the expected number of connections - require.Equal(t, expectedConnections, sqlite.NumConns()) - require.Len(t, closers, expectedConnections) - require.Len(t, conns, expectedConnections) - - // Should have different connections - for i := 1; i < len(conns); i++ { - require.NotSame(t, conns[i-1], conns[i], "expected connections to be different") - } - - // Close each connection - for _, closer := range closers { - require.NoError(t, closer.Close(), "expected no error during close") - - expectedConnections-- - - require.Equal(t, expectedConnections, sqlite.NumConns()) - } -}