Skip to content

Commit

Permalink
feat(utils): add common utils package
Browse files Browse the repository at this point in the history
  • Loading branch information
Planxnx committed Oct 23, 2023
1 parent 442842e commit de9eb33
Show file tree
Hide file tree
Showing 23 changed files with 1,319 additions and 4 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

Miscellaneous useful Go packages by [Cleverse](https://about.cleverse.com)

## utils

Optimized common generic utilities for Cleverse Golang projects.

[See here](utils/README.md).

## errors

Package errors adds stacktrace support to errors in go.
Expand Down
3 changes: 0 additions & 3 deletions go.mod

This file was deleted.

Empty file removed go.sum
Empty file.
2 changes: 1 addition & 1 deletion go.work
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
go 1.21

use (
./
./errors
./utils
)
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
12 changes: 12 additions & 0 deletions utils/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[![GoDoc](https://godoc.org/github.com/Cleverse/go-utilities/utils?status.svg)](http://godoc.org/github.com/Cleverse/go-utilities/utils)
[![Report card](https://goreportcard.com/badge/github.com/Cleverse/go-utilities/utils)](https://goreportcard.com/report/github.com/Cleverse/go-utilities/utils)

# utils

Optimized common generic utilities for Cleverse projects.

## Installation

```shell
go get github.com/Cleverse/go-utilities/utils
```
12 changes: 12 additions & 0 deletions utils/bytes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package utils

import (
"crypto/rand"
)

// RandomBytes returns a random byte slice with the given length with crypto/rand.
func RandomBytes(length int) []byte {
b := make([]byte, length)
Must(rand.Read(b))
return b
}
96 changes: 96 additions & 0 deletions utils/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package utils

import (
"fmt"
"reflect"

"github.com/Cleverse/go-utilities/errors"
)

// Must is used to simplify error handling.
// It's helpful to wraps a call to a function returning a value and an error. and panics if err is error or false.
//
// warning: this is not safe, use with caution!! (avoid to use it's in runtime)
func Must[T any](data T, err any, messageArgs ...interface{}) T {
must(err, messageArgs...)
return data
}

// MustNotError is used to simplify error handling.
//
// warning: this is not safe, use with caution!! (avoid to use it's in runtime)
func MustNotError[T any](data T, err error) T {
if err != nil {
panic(errors.WithStack(err))
}
return data
}

// UnsafeMust is used to simplify error/ok handling by ignoring it in runtime.
//
// warning: this is not safe, use with caution!!
// be careful when value is pointer, it may be nil. (safe in runtime, but need to check nil before use)
func UnsafeMust[T any, E any](data T, e E) T {
return data
}

// MustOK is used to simplify ok handling.
// for case ok should be true.
//
// warning: this is not safe, use with caution!! (avoid to use it's in runtime)
func MustOK[T any](data T, ok bool) T {
if !ok {
panic(errors.Errorf("got not ok, but should ok"))
}
return data
}

// MustNotOK is used to simplify ok handling.
// for case ok should be false.
//
// warning: this is not safe, use with caution!! (avoid to use it's in runtime)
func MustNotOK[T any](data T, ok bool) T {
if ok {
panic(errors.Errorf("got ok, but should not ok"))
}
return data
}

// must panics if err is error or false.
func must(err any, messageArgs ...interface{}) {
if err == nil {
return
}

switch e := err.(type) {
case bool:
if !e {
panic(Default[string](msgFormatter(messageArgs...), "not ok"))
}
case error:
if e == nil {
return
}
message := msgFormatter(messageArgs...)
if message != "" {
panic(message + ": " + e.Error())
}
panic(errors.WithStack(e))
default:
panic("must: invalid err type '" + reflect.TypeOf(err).Name() + "', should either be a bool or an error")
}
}

func msgFormatter(msgAndArgs ...interface{}) string {
switch len(msgAndArgs) {
case 0:
return ""
case 1:
if msgAsStr, ok := msgAndArgs[0].(string); ok {
return msgAsStr
}
return fmt.Sprintf("%+v", msgAndArgs[0])
default:
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
}
}
14 changes: 14 additions & 0 deletions utils/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/Cleverse/go-utilities/utils

go 1.19

require (
github.com/Cleverse/go-utilities/errors v0.0.0-20231019072721-442842e3dc09
github.com/stretchr/testify v1.8.4
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
12 changes: 12 additions & 0 deletions utils/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
github.com/Cleverse/go-utilities/errors v0.0.0-20231019072721-442842e3dc09 h1:kNAgub14cUBr7uoTxQSrf0qiMNJSzMhIDa8RFdv6hkk=
github.com/Cleverse/go-utilities/errors v0.0.0-20231019072721-442842e3dc09/go.mod h1:1QK+h746G1DwellQ6KK2rBCJusZqIDTZ9QFVGnUX9+Q=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
66 changes: 66 additions & 0 deletions utils/hex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package utils

import (
"encoding/hex"

"github.com/Cleverse/go-utilities/errors"
)

// RandomHex returns a random hex string with the given length.
func RandomHex(length int) string {
// TODO: reduce memory allocation by using a same buffer for random and hex encoding.
return hex.EncodeToString(RandomBytes(length))
}

// Has0xPrefix checks if the input string has 0x prefix or not.
//
// Returns `true“ if the input string has 0x prefix, otherwise `false`.
func Has0xPrefix(input string) bool {
return len(input) >= 2 && input[0] == '0' && (input[1] == 'x' || input[1] == 'X')
}

// Trim0xPrefix returns the input string without 0x prefix.
func Trim0xPrefix(input string) string {
if Has0xPrefix(input) {
return input[2:]
}
return input
}

// Add0xPrefix returns the input string with 0x prefix.
func Add0xPrefix(input string) string {
if !Has0xPrefix(input) {
return "0x" + input
}
return input
}

// Flip0xPrefix returns the input string with 0x prefix if it doesn't have 0x prefix, otherwise returns the input string without 0x prefix.
func Flip0xPrefix(input string) string {
if Has0xPrefix(input) {
return input[2:]
}
return "0x" + input
}

// IsHex verifies whether a string can represent a valid hex-encoded or not.
func IsHex(str string) bool {
str = Trim0xPrefix(str)
for _, c := range []byte(str) {
if !isHexCharacter(c) {
return false
}
}
return true
}

// isHexCharacter returns bool of c being a valid hexadecimal.
func isHexCharacter(c byte) bool {
return ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F')
}

// DecodeHex decodes a hex string into a byte slice. str can be prefixed with 0x.
func DecodeHex(str string) ([]byte, error) {
b, err := hex.DecodeString(Trim0xPrefix(str))
return b, errors.WithStack(err)
}
82 changes: 82 additions & 0 deletions utils/hex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package utils

import (
"testing"

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

func TestHex0XPrefix(t *testing.T) {
type TestCase struct {
Input string
Has0xPrefix bool
Func func(string) string
Expected string
}

testCases := []TestCase{
{
Input: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: true,
Func: Trim0xPrefix,
Expected: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
{
Input: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: true,
Func: Add0xPrefix,
Expected: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
{
Input: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: true,
Func: Flip0xPrefix,
Expected: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
{
Input: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: false,
Func: Trim0xPrefix,
Expected: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
{
Input: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: false,
Func: Add0xPrefix,
Expected: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
{
Input: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: false,
Func: Flip0xPrefix,
Expected: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},

{
Input: "0XEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: true,
Func: Trim0xPrefix,
Expected: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
{
Input: "0XEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: true,
Func: Add0xPrefix,
Expected: "0XEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
{
Input: "0XEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
Has0xPrefix: true,
Func: Flip0xPrefix,
Expected: "EeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
},
}

for _, tc := range testCases {
t.Run(tc.Input, func(t *testing.T) {
assert := assert.New(t)
assert.Equal(tc.Has0xPrefix, Has0xPrefix(tc.Input), "Has0xPrefix should be equal")
assert.Equal(tc.Expected, tc.Func(tc.Input), "actual result from `Func(string) string` should equal to expected")
})
}
}
30 changes: 30 additions & 0 deletions utils/map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package utils

// CopyMapOfArray high performance copy map of array
// to new map of array with shared backing array.
// Reference: https://cs.opensource.google/go/go/+/master:src/net/http/header.go;l=94
func CopyMapOfArray[K comparable, V any](src map[K][]V) map[K][]V {
if src == nil {
return nil
}

// Find total number of values.
totalValue := 0
for _, val := range src {
totalValue += len(val)
}

tmp := make([]V, totalValue) // use shared backing array for reduce memory allocation.
dst := make(map[K][]V, len(src))
for k, val := range src {
if val == nil {
dst[k] = nil
continue
}
n := copy(tmp, val) // copy values to shared array.
dst[k] = tmp[:n:n] // point to specific length and capacity of shared backing array.
tmp = tmp[n:] // move pointer to next position.
}

return dst
}
Loading

0 comments on commit de9eb33

Please sign in to comment.