Skip to content

Commit

Permalink
feat: defaults package
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie authored Dec 29, 2024
1 parent b490678 commit d7a4394
Show file tree
Hide file tree
Showing 6 changed files with 567 additions and 0 deletions.
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.23.0
toolchain go1.23.4

require (
bou.ke/monkey v1.0.2
firebase.google.com/go/v4 v4.15.1
github.com/gofiber/fiber/v2 v2.52.5
github.com/google/uuid v1.6.0
Expand All @@ -16,6 +17,7 @@ require (
golang.org/x/mod v0.22.0
golang.org/x/sync v0.10.0
google.golang.org/api v0.214.0
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
gorm.io/gorm v1.25.12
helm.sh/helm v2.17.0+incompatible
k8s.io/apimachinery v0.32.0
Expand Down Expand Up @@ -56,6 +58,8 @@ require (
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
Expand All @@ -66,6 +70,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
bou.ke/monkey v1.0.2 h1:kWcnsrCNUatbxncxR/ThdYqbytgOIArtYWqcQLQzKLI=
bou.ke/monkey v1.0.2/go.mod h1:OqickVX3tNx6t33n1xvtTtu85YN5s6cKwVug+oHMaIA=
cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs=
cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q=
cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU=
Expand Down Expand Up @@ -115,13 +117,15 @@ github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/openfga/go-sdk v0.6.3 h1:FO3uDYeV+1y844iVvD7MJYKtmIEP1r4mis7kWCaDG2A=
github.com/openfga/go-sdk v0.6.3/go.mod h1:zui7pHE3eLAYh2fFmEMrWg9XbxYns2WW5Xr/GEgili4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
Expand Down
174 changes: 174 additions & 0 deletions utilx/defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
package utilx

import (
"reflect"
"regexp"
"strconv"
"strings"
"time"
)

// SetDefaults sets the default values for the fields of the given struct.
//
// Usage
//
// type ExampleBasic struct {
// Foo bool `default:"true"`
// Bar string `default:"33"`
// Qux int8
// Dur time.Duration `default:"2m3s"`
// }
//
// foo := &ExampleBasic{}
// SetDefaults(foo)
func SetDefaults(variable interface{}) {
getDefaultFiller().Fill(variable)
}

var defaultFiller *Filler = nil

func getDefaultFiller() *Filler {
if defaultFiller == nil {
defaultFiller = newDefaultFiller()
}

return defaultFiller
}

// nolint: exhaustive
func newDefaultFiller() *Filler {
funcs := make(map[reflect.Kind]FillerFunc, 0)
funcs[reflect.Bool] = func(field *FieldData) {
value, _ := strconv.ParseBool(field.TagValue)
field.Value.SetBool(value)
}

funcs[reflect.Int] = func(field *FieldData) {
value, _ := strconv.ParseInt(field.TagValue, 10, 64)
field.Value.SetInt(value)
}

funcs[reflect.Int8] = funcs[reflect.Int]
funcs[reflect.Int16] = funcs[reflect.Int]
funcs[reflect.Int32] = funcs[reflect.Int]
funcs[reflect.Int64] = func(field *FieldData) {
if field.Field.Type == reflect.TypeOf(time.Second) {
value, _ := time.ParseDuration(field.TagValue)
field.Value.Set(reflect.ValueOf(value))
} else {
value, _ := strconv.ParseInt(field.TagValue, 10, 64)
field.Value.SetInt(value)
}
}

funcs[reflect.Float32] = func(field *FieldData) {
value, _ := strconv.ParseFloat(field.TagValue, 64)
field.Value.SetFloat(value)
}

funcs[reflect.Float64] = funcs[reflect.Float32]

funcs[reflect.Uint] = func(field *FieldData) {
value, _ := strconv.ParseUint(field.TagValue, 10, 64)
field.Value.SetUint(value)
}

funcs[reflect.Uint8] = funcs[reflect.Uint]
funcs[reflect.Uint16] = funcs[reflect.Uint]
funcs[reflect.Uint32] = funcs[reflect.Uint]
funcs[reflect.Uint64] = funcs[reflect.Uint]

funcs[reflect.String] = func(field *FieldData) {
tagValue := parseDateTimeString(field.TagValue)
field.Value.SetString(tagValue)
}

funcs[reflect.Struct] = func(field *FieldData) {
fields := getDefaultFiller().GetFieldsFromValue(field.Value, nil)
getDefaultFiller().SetDefaultValues(fields)
}

types := make(map[TypeHash]FillerFunc, 1)
types["time.Duration"] = func(field *FieldData) {
d, _ := time.ParseDuration(field.TagValue)
field.Value.Set(reflect.ValueOf(d))
}

funcs[reflect.Slice] = func(field *FieldData) {
k := field.Value.Type().Elem().Kind()
switch k {
case reflect.Uint8:
if field.Value.Bytes() != nil {
return
}
field.Value.SetBytes([]byte(field.TagValue))
case reflect.Struct:
count := field.Value.Len()
for i := 0; i < count; i++ {
fields := getDefaultFiller().GetFieldsFromValue(field.Value.Index(i), nil)
getDefaultFiller().SetDefaultValues(fields)
}
default:
// 处理形如 [1,2,3,4]
reg := regexp.MustCompile(`^\[(.*)\]$`)
matchs := reg.FindStringSubmatch(field.TagValue)
if len(matchs) != 2 {
return
}
if matchs[1] == "" {
field.Value.Set(reflect.MakeSlice(field.Value.Type(), 0, 0))
} else {
defaultValue := strings.Split(matchs[1], ",")
result := reflect.MakeSlice(field.Value.Type(), len(defaultValue), len(defaultValue))
for i := 0; i < len(defaultValue); i++ {
itemValue := result.Index(i)
item := &FieldData{
Value: itemValue,
Field: reflect.StructField{},
TagValue: defaultValue[i],
Parent: nil,
}
funcs[k](item)
}
field.Value.Set(result)
}
}
}

return &Filler{FuncByKind: funcs, FuncByType: types, Tag: "default"}
}

func parseDateTimeString(data string) string {
pattern := regexp.MustCompile(`\{\{(\w+\:(?:-|)\d*,(?:-|)\d*,(?:-|)\d*)\}\}`)
matches := pattern.FindAllStringSubmatch(data, -1) // matches is [][]string
for _, match := range matches {

tags := strings.Split(match[1], ":")
if len(tags) == 2 {

valueStrings := strings.Split(tags[1], ",")
if len(valueStrings) == 3 {
var values [3]int
for key, valueString := range valueStrings {
num, _ := strconv.ParseInt(valueString, 10, 64)
values[key] = int(num)
}

switch tags[0] {

case "date":
str := time.Now().AddDate(values[0], values[1], values[2]).Format("2006-01-02")
data = strings.ReplaceAll(data, match[0], str)
case "time":
str := time.Now().Add((time.Duration(values[0]) * time.Hour) +
(time.Duration(values[1]) * time.Minute) +
(time.Duration(values[2]) * time.Second)).Format("15:04:05")
data = strings.ReplaceAll(data, match[0], str)
}
}
}

}

return data
}
137 changes: 137 additions & 0 deletions utilx/defaults_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package utilx

import (
"testing"
"time"

"bou.ke/monkey"
. "gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.

func Test(t *testing.T) {
monkey.Patch(time.Now, func() time.Time {
t, _ := time.Parse("2006-01-02 15:04:05", "2020-06-10 12:00:00")
return t
})

TestingT(t)
}

type DefaultsSuite struct{}

var _ = Suite(&DefaultsSuite{})

type Parent struct {
Children []Child
}

type Child struct {
Name string
Age int `default:"10"`
}

type ExampleBasic struct {
Bool bool `default:"true"`
Integer int `default:"33"`
Integer8 int8 `default:"8"`
Integer16 int16 `default:"16"`
Integer32 int32 `default:"32"`
Integer64 int64 `default:"64"`
UInteger uint `default:"11"`
UInteger8 uint8 `default:"18"`
UInteger16 uint16 `default:"116"`
UInteger32 uint32 `default:"132"`
UInteger64 uint64 `default:"164"`
String string `default:"foo"`
Bytes []byte `default:"bar"`
Float32 float32 `default:"3.2"`
Float64 float64 `default:"6.4"`
Struct struct {
Bool bool `default:"true"`
Integer int `default:"33"`
}
Duration time.Duration `default:"1s"`
Children []Child
Second time.Duration `default:"1s"`
StringSlice []string `default:"[1,2,3,4]"`
IntSlice []int `default:"[1,2,3,4]"`
IntSliceSlice [][]int `default:"[[1],[2],[3],[4]]"`
StringSliceSlice [][]string `default:"[[1],[]]"`

DateTime string `default:"{{date:1,-10,0}} {{time:1,-5,10}}"`
}

func (s *DefaultsSuite) TestSetDefaultsBasic(c *C) {
foo := &ExampleBasic{}
SetDefaults(foo)

s.assertTypes(c, foo)
}

type ExampleNested struct {
Struct ExampleBasic
}

func (s *DefaultsSuite) TestSetDefaultsNested(c *C) {
foo := &ExampleNested{}
SetDefaults(foo)

s.assertTypes(c, &foo.Struct)
}

func (s *DefaultsSuite) assertTypes(c *C, foo *ExampleBasic) {
c.Assert(foo.Bool, Equals, true)
c.Assert(foo.Integer, Equals, 33)
c.Assert(foo.Integer8, Equals, int8(8))
c.Assert(foo.Integer16, Equals, int16(16))
c.Assert(foo.Integer32, Equals, int32(32))
c.Assert(foo.Integer64, Equals, int64(64))
c.Assert(foo.UInteger, Equals, uint(11))
c.Assert(foo.UInteger8, Equals, uint8(18))
c.Assert(foo.UInteger16, Equals, uint16(116))
c.Assert(foo.UInteger32, Equals, uint32(132))
c.Assert(foo.UInteger64, Equals, uint64(164))
c.Assert(foo.String, Equals, "foo")
c.Assert(string(foo.Bytes), Equals, "bar")
c.Assert(foo.Float32, Equals, float32(3.2))
c.Assert(foo.Float64, Equals, 6.4)
c.Assert(foo.Struct.Bool, Equals, true)
c.Assert(foo.Duration, Equals, time.Second)
c.Assert(foo.Children, IsNil)
c.Assert(foo.Second, Equals, time.Second)
c.Assert(foo.StringSlice, DeepEquals, []string{"1", "2", "3", "4"})
c.Assert(foo.IntSlice, DeepEquals, []int{1, 2, 3, 4})
c.Assert(foo.IntSliceSlice, DeepEquals, [][]int{{1}, {2}, {3}, {4}})
c.Assert(foo.StringSliceSlice, DeepEquals, [][]string{{"1"}, {}})
c.Assert(foo.DateTime, Equals, "2020-08-10 12:55:10")
}

func (s *DefaultsSuite) TestSetDefaultsWithValues(c *C) {
foo := &ExampleBasic{
Integer: 55,
UInteger: 22,
Float32: 9.9,
String: "bar",
Bytes: []byte("foo"),
Children: []Child{{Name: "alice"}, {Name: "bob", Age: 2}},
}

SetDefaults(foo)

c.Assert(foo.Integer, Equals, 55)
c.Assert(foo.UInteger, Equals, uint(22))
c.Assert(foo.Float32, Equals, float32(9.9))
c.Assert(foo.String, Equals, "bar")
c.Assert(string(foo.Bytes), Equals, "foo")
c.Assert(foo.Children[0].Age, Equals, 10)
c.Assert(foo.Children[1].Age, Equals, 2)
}

func (s *DefaultsSuite) BenchmarkLogic(c *C) {
for i := 0; i < c.N; i++ {
foo := &ExampleBasic{}
SetDefaults(foo)
}
}
Loading

0 comments on commit d7a4394

Please sign in to comment.