Initial import
This commit is contained in:
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Allowlisting gitignore template for GO projects prevents us
|
||||
# from adding various unwanted local files, such as generated
|
||||
# files, developer configurations or IDE-specific files etc.
|
||||
#
|
||||
# Recommended: Go.AllowList.gitignore
|
||||
|
||||
# Ignore everything
|
||||
*
|
||||
|
||||
# But these files...
|
||||
!.gitignore
|
||||
|
||||
!*.go
|
||||
!go.sum
|
||||
!go.mod
|
||||
|
||||
!README.md
|
||||
!LICENSE
|
||||
|
||||
!Makefile
|
||||
|
||||
!*.sh
|
||||
!*.md
|
||||
|
||||
# ...even if they are in subdirectories
|
||||
!*/
|
||||
40
Makefile
Normal file
40
Makefile
Normal file
@@ -0,0 +1,40 @@
|
||||
.PHONY: help
|
||||
|
||||
help: ## This help.
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
GCFLAGS=
|
||||
LDFLAGS_EXTRA=
|
||||
|
||||
clean:
|
||||
rm coverage.out && true
|
||||
|
||||
lint:
|
||||
go-critic check ./... || true
|
||||
|
||||
debug-test: ## Debug test with dlv
|
||||
dlv test -- -test.v
|
||||
|
||||
connect:
|
||||
dlv connect 127.0.0.1:36666
|
||||
|
||||
test:
|
||||
go test \
|
||||
-failfast \
|
||||
-race ./... -v
|
||||
|
||||
test-watch:
|
||||
gotestsum \
|
||||
--watch \
|
||||
-- \
|
||||
-failfast \
|
||||
./...
|
||||
|
||||
cover:
|
||||
go test -coverprofile=coverage.out
|
||||
go tool cover -func=coverage.out
|
||||
@rm coverage.out
|
||||
|
||||
.PHONY: run
|
||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module git.gsuntres.com/general/commons
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require go.mongodb.org/mongo-driver/v2 v2.5.0
|
||||
6
go.sum
Normal file
6
go.sum
Normal file
@@ -0,0 +1,6 @@
|
||||
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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
2
main.go
Normal file
2
main.go
Normal file
@@ -0,0 +1,2 @@
|
||||
// Commons includes utility functions used across projects.
|
||||
package commons
|
||||
11
math.go
Normal file
11
math.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package commons
|
||||
|
||||
// MathMin will compare two integers and return the one with the smaller value.
|
||||
func MathMin[T ~int | ~int8 | ~int16 | ~int32 | ~int64 |
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
|
||||
~float32 | ~float64 | ~string](a, b T) T {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
36
math_test.go
Normal file
36
math_test.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMathMin(t *testing.T) {
|
||||
v := MathMin[int64](int64(15), int64(98))
|
||||
|
||||
if v != 15 {
|
||||
t.Fatalf("Should have been 15 not %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMathMin_Swap(t *testing.T) {
|
||||
v := MathMin[int64](int64(100), int64(12))
|
||||
|
||||
if v != 12 {
|
||||
t.Fatalf("Should have been 12 not %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMathMin_String(t *testing.T) {
|
||||
v := MathMin[string]("15", "152")
|
||||
|
||||
if v != "15" {
|
||||
t.Fatalf("Should have been 15 not %v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMathMin() {
|
||||
fmt.Printf("%d", MathMin[int64](int64(10), int64(20)))
|
||||
// Output:
|
||||
// 10
|
||||
}
|
||||
27
string_test.go
Normal file
27
string_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStringNormalize(t *testing.T) {
|
||||
v := " some text here "
|
||||
|
||||
vv := StringNormalize(v)
|
||||
|
||||
if vv != "some text here" {
|
||||
t.Fatal("Text should have been normalized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringIsBlank(t *testing.T) {
|
||||
if !StringIsBlank("") {
|
||||
t.Fatal("Failed to check string is blank")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringIsNotBlank(t *testing.T) {
|
||||
if !StringIsNotBlank("fasdfdas") {
|
||||
t.Fatal("Failed to check string is not blank")
|
||||
}
|
||||
}
|
||||
25
strings.go
Normal file
25
strings.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var whitespace = regexp.MustCompile(`\s+`)
|
||||
|
||||
// StringNormalize will replace arbitrary lengths of consecutive white space with a single one.
|
||||
func StringNormalize(s string) string {
|
||||
s = whitespace.ReplaceAllString(s, " ")
|
||||
|
||||
return strings.TrimSpace(s)
|
||||
}
|
||||
|
||||
//StringIsBlank checks if a string is blank. Will trim the string before the check takes place.
|
||||
func StringIsBlank(s string) bool {
|
||||
return len(strings.TrimSpace(s)) == 0
|
||||
}
|
||||
|
||||
// StringIsNotBlank check if a string is NOT blank.
|
||||
func StringIsNotBlank(s string) bool {
|
||||
return !StringIsBlank(s)
|
||||
}
|
||||
134
struct.go
Normal file
134
struct.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"log"
|
||||
"reflect"
|
||||
"strings"
|
||||
// "encoding/json"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
// MapToStruct will convert a map[string]any to a struct.
|
||||
func MapToStruct(m map[string]any, o any) error {
|
||||
b, err := bson.Marshal(m)
|
||||
if err != nil {
|
||||
log.Printf("Failed marshal %v", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
err = bson.Unmarshal(b, o)
|
||||
if err != nil {
|
||||
log.Printf("Failed to unmarshal %v", err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MapIsSubset given two map[string]any m1 and m2 will determine if m1 is a subset of m2.
|
||||
// Only fields' name is evaluated not their values.
|
||||
func MapIsSubset(subset, superset any) bool {
|
||||
sub := subset.(map[string]any)
|
||||
sup := superset.(map[string]any)
|
||||
|
||||
isSubset := true
|
||||
|
||||
for k, _ := range sub {
|
||||
_, ok := sup[k]
|
||||
if !ok {
|
||||
isSubset = false
|
||||
}
|
||||
}
|
||||
|
||||
return isSubset
|
||||
}
|
||||
|
||||
// MapIsSubsetOfStruct given a map[string]any and a struct will determine if the map is a subset of the struct.
|
||||
// Only fields' name is evaluated not their values.
|
||||
func MapIsSubsetOfStruct(m map[string]any, s any) bool {
|
||||
v := reflect.ValueOf(s)
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem() // Dereference if it's a pointer
|
||||
}
|
||||
|
||||
// Ensure we are working with a struct
|
||||
if v.Kind() != reflect.Struct {
|
||||
return false
|
||||
}
|
||||
|
||||
isSubset := true
|
||||
|
||||
t := v.Type()
|
||||
for key := range m {
|
||||
|
||||
// FieldByName only finds exported fields
|
||||
if _, found := t.FieldByName(key); !found {
|
||||
isSubset = false
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return isSubset
|
||||
}
|
||||
|
||||
// StructHasJsonName determines if a struct has the given json tag name.
|
||||
func StructHasJsonName(s any, targetName string) bool {
|
||||
t := reflect.TypeOf(s)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
tag := field.Tag.Get("json")
|
||||
|
||||
// The name is the first part before any commas (e.g., "user_id,omitempty")
|
||||
name := strings.Split(tag, ",")[0]
|
||||
|
||||
if name == targetName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// StructHasJsonName determines if a struct has the given bson tag name.
|
||||
func StructHasBsonName(s any, targetName string) bool {
|
||||
t := reflect.TypeOf(s)
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
field := t.Field(i)
|
||||
tag := field.Tag.Get("bson")
|
||||
|
||||
// The name is the first part before any commas (e.g., "user_id,omitempty")
|
||||
name := strings.Split(tag, ",")[0]
|
||||
|
||||
if name == targetName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// StructCopyMatching copies the fields of one struct to another only if they have the same name and type.
|
||||
func StructCopyMatching(source, target any) {
|
||||
sVal := reflect.ValueOf(source).Elem()
|
||||
tVal := reflect.ValueOf(target).Elem()
|
||||
|
||||
for i := 0; i < sVal.NumField(); i++ {
|
||||
sField := sVal.Type().Field(i)
|
||||
tField, ok := tVal.Type().FieldByName(sField.Name)
|
||||
|
||||
if ok && sField.Type == tField.Type {
|
||||
tVal.FieldByName(sField.Name).Set(sVal.Field(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
203
struct_test.go
Normal file
203
struct_test.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package commons
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMapToStruct(t *testing.T) {
|
||||
o := map[string]any {
|
||||
"name": "name1",
|
||||
"age": 22,
|
||||
"active": true,
|
||||
}
|
||||
|
||||
type O struct {
|
||||
Name string
|
||||
Age int
|
||||
Active bool
|
||||
}
|
||||
|
||||
var oo O
|
||||
|
||||
if err := MapToStruct(o, &oo); err != nil {
|
||||
t.Fatalf("1 Failed to convert %v", err)
|
||||
}
|
||||
|
||||
if oo.Name != "name1" || oo.Age != 22 || !oo.Active {
|
||||
t.Fatalf("2 Failed to convert %v", oo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapIsSubset(t *testing.T) {
|
||||
super := map[string]any {
|
||||
"name": "name1",
|
||||
"age": 22,
|
||||
"active": "2",
|
||||
}
|
||||
|
||||
sub := map[string]any {
|
||||
"name": "a",
|
||||
}
|
||||
|
||||
if !MapIsSubset(sub, super) {
|
||||
t.Fatal("Should have indicated that map is a subset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapIsSubset_Nosubset(t *testing.T) {
|
||||
super := map[string]any {
|
||||
"name": "name1",
|
||||
"age": 22,
|
||||
"active": "2",
|
||||
}
|
||||
|
||||
sub := map[string]any {
|
||||
"foo": "a",
|
||||
}
|
||||
|
||||
if MapIsSubset(sub, super) {
|
||||
t.Fatal("Should have indicated that map is a NOT subset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapIsSubsetOfStruct_NoSubset(t *testing.T) {
|
||||
type O struct {
|
||||
Name string
|
||||
Age int `json:"age"`
|
||||
Notes string
|
||||
}
|
||||
|
||||
oo := map[string]any{
|
||||
"name": "A Name",
|
||||
"age": 1,
|
||||
"notes": "dsafda",
|
||||
"other": "dd",
|
||||
}
|
||||
|
||||
if MapIsSubsetOfStruct(oo, &O{}) {
|
||||
t.Fatal("Should have indicated that map is a NOT subset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapIsSubsetOfStruct(t *testing.T) {
|
||||
type O struct {
|
||||
Name string
|
||||
Age int `json:"age"`
|
||||
Notes string
|
||||
}
|
||||
|
||||
oo := map[string]any{
|
||||
"Name": "A Name",
|
||||
}
|
||||
|
||||
if !MapIsSubsetOfStruct(oo, &O{}) {
|
||||
t.Fatal("Should have indicated that map is a subset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapIsSubsetOfStruct_SomeFields(t *testing.T) {
|
||||
type O struct {
|
||||
Name string
|
||||
Age int `json:"age"`
|
||||
Notes string
|
||||
}
|
||||
|
||||
oo := map[string]any{
|
||||
"Name": "A Name",
|
||||
"Foo": "bar",
|
||||
}
|
||||
|
||||
if MapIsSubsetOfStruct(oo, &O{}) {
|
||||
t.Fatal("Should have indicated that map is a NOT subset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructHasJsonName_True(t *testing.T) {
|
||||
type O struct {
|
||||
Name string
|
||||
Age int `json:"age"`
|
||||
Notes string
|
||||
}
|
||||
|
||||
o := &O{
|
||||
}
|
||||
|
||||
if !StructHasJsonName(o, "age") {
|
||||
t.Fatal("False negative for json tag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructHasJsonName_False(t *testing.T) {
|
||||
type O struct {
|
||||
Name string
|
||||
Age int `json:"age"`
|
||||
Notes string
|
||||
}
|
||||
|
||||
o := &O{
|
||||
}
|
||||
|
||||
if StructHasJsonName(o, "foo") {
|
||||
t.Fatal("False positive for json tag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructHasBsonName_True(t *testing.T) {
|
||||
type O struct {
|
||||
Name string
|
||||
Age int `bson:"age"`
|
||||
Notes string
|
||||
}
|
||||
|
||||
o := &O{
|
||||
}
|
||||
|
||||
if !StructHasBsonName(o, "age") {
|
||||
t.Fatal("False negative for bson tag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructHasBsonName_False(t *testing.T) {
|
||||
type O struct {
|
||||
Name string
|
||||
Age int `bson:"age"`
|
||||
Notes string
|
||||
}
|
||||
|
||||
o := &O{
|
||||
}
|
||||
|
||||
if StructHasBsonName(o, "foo") {
|
||||
t.Fatal("False positive for bson tag")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyMatching(t *testing.T) {
|
||||
|
||||
type O struct {
|
||||
Name string
|
||||
Age int
|
||||
Notes string
|
||||
}
|
||||
|
||||
type OO struct {
|
||||
Name string
|
||||
Age int
|
||||
Active bool
|
||||
}
|
||||
|
||||
from := &O{
|
||||
Name: "Nick",
|
||||
Age: 15,
|
||||
Notes: "Some notes about nick",
|
||||
}
|
||||
|
||||
var to OO
|
||||
|
||||
StructCopyMatching(from, &to)
|
||||
|
||||
if to.Name != "Nick" && to.Age != 15 && to.Active != true {
|
||||
t.Fatalf("Failed to copy matching fields to %v", to)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user