diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go index b4fb356ee319..f68748c68172 100644 --- a/core/mapping/unmarshaler.go +++ b/core/mapping/unmarshaler.go @@ -18,6 +18,7 @@ import ( ) const ( + comma = "," defaultKeyName = "key" delimiter = '.' ignoreKey = "-" @@ -36,6 +37,7 @@ var ( defaultCacheLock sync.Mutex emptyMap = map[string]any{} emptyValue = reflect.ValueOf(lang.Placeholder) + stringSliceType = reflect.TypeOf([]string{}) ) type ( @@ -80,40 +82,11 @@ func (u *Unmarshaler) Unmarshal(i, v any) error { return u.unmarshal(i, v, "") } -func (u *Unmarshaler) unmarshal(i, v any, fullName string) error { - valueType := reflect.TypeOf(v) - if valueType.Kind() != reflect.Ptr { - return errValueNotSettable - } - - elemType := Deref(valueType) - switch iv := i.(type) { - case map[string]any: - if elemType.Kind() != reflect.Struct { - return errTypeMismatch - } - - return u.unmarshalValuer(mapValuer(iv), v, fullName) - case []any: - if elemType.Kind() != reflect.Slice { - return errTypeMismatch - } - - return u.fillSlice(elemType, reflect.ValueOf(v).Elem(), iv, fullName) - default: - return errUnsupportedType - } -} - // UnmarshalValuer unmarshals m into v. func (u *Unmarshaler) UnmarshalValuer(m Valuer, v any) error { return u.unmarshalValuer(simpleValuer{current: m}, v, "") } -func (u *Unmarshaler) unmarshalValuer(m Valuer, v any, fullName string) error { - return u.unmarshalWithFullName(simpleValuer{current: m}, v, fullName) -} - func (u *Unmarshaler) fillMap(fieldType reflect.Type, value reflect.Value, mapValue any, fullName string) error { if !value.CanSet() { @@ -173,13 +146,18 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, baseType := fieldType.Elem() dereffedBaseType := Deref(baseType) dereffedBaseKind := dereffedBaseType.Kind() - conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap()) if refValue.Len() == 0 { - value.Set(conv) + value.Set(reflect.MakeSlice(reflect.SliceOf(baseType), 0, 0)) return nil } + if u.opts.fromArray { + refValue = makeStringSlice(refValue) + } + var valid bool + conv := reflect.MakeSlice(reflect.SliceOf(baseType), refValue.Len(), refValue.Cap()) + for i := 0; i < refValue.Len(); i++ { ithValue := refValue.Index(i).Interface() if ithValue == nil { @@ -191,17 +169,9 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value, switch dereffedBaseKind { case reflect.Struct: - target := reflect.New(dereffedBaseType) - val, ok := ithValue.(map[string]any) - if !ok { - return errTypeMismatch - } - - if err := u.unmarshal(val, target.Interface(), sliceFullName); err != nil { + if err := u.fillStructElement(baseType, conv.Index(i), ithValue, sliceFullName); err != nil { return err } - - SetValue(fieldType.Elem(), conv.Index(i), target.Elem()) case reflect.Slice: if err := u.fillSlice(dereffedBaseType, conv.Index(i), ithValue, sliceFullName); err != nil { return err @@ -236,7 +206,7 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect. return errUnsupportedType } - baseFieldType := Deref(fieldType.Elem()) + baseFieldType := fieldType.Elem() baseFieldKind := baseFieldType.Kind() conv := reflect.MakeSlice(reflect.SliceOf(baseFieldType), len(slice), cap(slice)) @@ -257,29 +227,39 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int, } ithVal := slice.Index(index) + ithValType := ithVal.Type() + switch v := value.(type) { case fmt.Stringer: return setValueFromString(baseKind, ithVal, v.String()) case string: return setValueFromString(baseKind, ithVal, v) case map[string]any: - return u.fillMap(ithVal.Type(), ithVal, value, fullName) + // deref to handle both pointer and non-pointer types. + switch Deref(ithValType).Kind() { + case reflect.Struct: + return u.fillStructElement(ithValType, ithVal, v, fullName) + case reflect.Map: + return u.fillMap(ithValType, ithVal, value, fullName) + default: + return errTypeMismatch + } default: // don't need to consider the difference between int, int8, int16, int32, int64, // uint, uint8, uint16, uint32, uint64, because they're handled as json.Number. if ithVal.Kind() == reflect.Ptr { - baseType := Deref(ithVal.Type()) + baseType := Deref(ithValType) if !reflect.TypeOf(value).AssignableTo(baseType) { return errTypeMismatch } target := reflect.New(baseType).Elem() target.Set(reflect.ValueOf(value)) - SetValue(ithVal.Type(), ithVal, target) + SetValue(ithValType, ithVal, target) return nil } - if !reflect.TypeOf(value).AssignableTo(ithVal.Type()) { + if !reflect.TypeOf(value).AssignableTo(ithValType) { return errTypeMismatch } @@ -310,6 +290,23 @@ func (u *Unmarshaler) fillSliceWithDefault(derefedType reflect.Type, value refle return u.fillSlice(derefedType, value, slice, fullName) } +func (u *Unmarshaler) fillStructElement(baseType reflect.Type, target reflect.Value, + value any, fullName string) error { + val, ok := value.(map[string]any) + if !ok { + return errTypeMismatch + } + + // use Deref(baseType) to get the base type in case the type is a pointer type. + ptr := reflect.New(Deref(baseType)) + if err := u.unmarshal(val, ptr.Interface(), fullName); err != nil { + return err + } + + SetValue(baseType, target, ptr.Elem()) + return nil +} + func (u *Unmarshaler) fillUnmarshalerStruct(fieldType reflect.Type, value reflect.Value, targetValue string) error { if !value.CanSet() { @@ -952,6 +949,35 @@ func (u *Unmarshaler) processNamedFieldWithoutValue(fieldType reflect.Type, valu return nil } +func (u *Unmarshaler) unmarshal(i, v any, fullName string) error { + valueType := reflect.TypeOf(v) + if valueType.Kind() != reflect.Ptr { + return errValueNotSettable + } + + elemType := Deref(valueType) + switch iv := i.(type) { + case map[string]any: + if elemType.Kind() != reflect.Struct { + return errTypeMismatch + } + + return u.unmarshalValuer(mapValuer(iv), v, fullName) + case []any: + if elemType.Kind() != reflect.Slice { + return errTypeMismatch + } + + return u.fillSlice(elemType, reflect.ValueOf(v).Elem(), iv, fullName) + default: + return errUnsupportedType + } +} + +func (u *Unmarshaler) unmarshalValuer(m Valuer, v any, fullName string) error { + return u.unmarshalWithFullName(simpleValuer{current: m}, v, fullName) +} + func (u *Unmarshaler) unmarshalWithFullName(m valuerWithParent, v any, fullName string) error { rv := reflect.ValueOf(v) if err := ValidatePtr(rv); err != nil { @@ -1146,6 +1172,35 @@ func join(elem ...string) string { return builder.String() } +func makeStringSlice(refValue reflect.Value) reflect.Value { + if refValue.Len() != 1 { + return refValue + } + + element := refValue.Index(0) + if element.Kind() != reflect.String { + return refValue + } + + val, ok := element.Interface().(string) + if !ok { + return refValue + } + + splits := strings.Split(val, comma) + if len(splits) <= 1 { + return refValue + } + + slice := reflect.MakeSlice(stringSliceType, len(splits), len(splits)) + for i, split := range splits { + // allow empty strings + slice.Index(i).Set(reflect.ValueOf(split)) + } + + return slice +} + func newInitError(name string) error { return fmt.Errorf("field %q is not set", name) } diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go index 3270632dc447..ae2aba0edc86 100644 --- a/core/mapping/unmarshaler_test.go +++ b/core/mapping/unmarshaler_test.go @@ -351,7 +351,7 @@ func TestUnmarshalIntSliceOfPtr(t *testing.T) { assert.Error(t, UnmarshalKey(m, &in)) }) - t.Run("int slice with nil", func(t *testing.T) { + t.Run("int slice with nil element", func(t *testing.T) { type inner struct { Ints []int `key:"ints"` } @@ -365,6 +365,21 @@ func TestUnmarshalIntSliceOfPtr(t *testing.T) { assert.Empty(t, in.Ints) } }) + + t.Run("int slice with nil", func(t *testing.T) { + type inner struct { + Ints []int `key:"ints"` + } + + m := map[string]any{ + "ints": []any(nil), + } + + var in inner + if assert.NoError(t, UnmarshalKey(m, &in)) { + assert.Empty(t, in.Ints) + } + }) } func TestUnmarshalIntWithDefault(t *testing.T) { @@ -1374,20 +1389,82 @@ func TestUnmarshalWithFloatPtr(t *testing.T) { } func TestUnmarshalIntSlice(t *testing.T) { - var v struct { - Ages []int `key:"ages"` - Slice []int `key:"slice"` - } - m := map[string]any{ - "ages": []int{1, 2}, - "slice": []any{}, - } + t.Run("int slice from int", func(t *testing.T) { + var v struct { + Ages []int `key:"ages"` + Slice []int `key:"slice"` + } + m := map[string]any{ + "ages": []int{1, 2}, + "slice": []any{}, + } - ast := assert.New(t) - if ast.NoError(UnmarshalKey(m, &v)) { - ast.ElementsMatch([]int{1, 2}, v.Ages) - ast.Equal([]int{}, v.Slice) - } + ast := assert.New(t) + if ast.NoError(UnmarshalKey(m, &v)) { + ast.ElementsMatch([]int{1, 2}, v.Ages) + ast.Equal([]int{}, v.Slice) + } + }) + + t.Run("int slice from one int", func(t *testing.T) { + var v struct { + Ages []int `key:"ages"` + } + m := map[string]any{ + "ages": []int{2}, + } + + ast := assert.New(t) + unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray()) + if ast.NoError(unmarshaler.Unmarshal(m, &v)) { + ast.ElementsMatch([]int{2}, v.Ages) + } + }) + + t.Run("int slice from one int string", func(t *testing.T) { + var v struct { + Ages []int `key:"ages"` + } + m := map[string]any{ + "ages": []string{"2"}, + } + + ast := assert.New(t) + unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray()) + if ast.NoError(unmarshaler.Unmarshal(m, &v)) { + ast.ElementsMatch([]int{2}, v.Ages) + } + }) + + t.Run("int slice from one json.Number", func(t *testing.T) { + var v struct { + Ages []int `key:"ages"` + } + m := map[string]any{ + "ages": []json.Number{"2"}, + } + + ast := assert.New(t) + unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray()) + if ast.NoError(unmarshaler.Unmarshal(m, &v)) { + ast.ElementsMatch([]int{2}, v.Ages) + } + }) + + t.Run("int slice from one int strings", func(t *testing.T) { + var v struct { + Ages []int `key:"ages"` + } + m := map[string]any{ + "ages": []string{"1,2"}, + } + + ast := assert.New(t) + unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray()) + if ast.NoError(unmarshaler.Unmarshal(m, &v)) { + ast.ElementsMatch([]int{1, 2}, v.Ages) + } + }) } func TestUnmarshalString(t *testing.T) { @@ -1442,6 +1519,36 @@ func TestUnmarshalStringSliceFromString(t *testing.T) { } }) + t.Run("slice from empty string", func(t *testing.T) { + var v struct { + Names []string `key:"names"` + } + m := map[string]any{ + "names": []string{""}, + } + + ast := assert.New(t) + unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray()) + if ast.NoError(unmarshaler.Unmarshal(m, &v)) { + ast.ElementsMatch([]string{""}, v.Names) + } + }) + + t.Run("slice from empty and valid string", func(t *testing.T) { + var v struct { + Names []string `key:"names"` + } + m := map[string]any{ + "names": []string{","}, + } + + ast := assert.New(t) + unmarshaler := NewUnmarshaler(defaultKeyName, WithFromArray()) + if ast.NoError(unmarshaler.Unmarshal(m, &v)) { + ast.ElementsMatch([]string{"", ""}, v.Names) + } + }) + t.Run("slice from string with slice error", func(t *testing.T) { var v struct { Names []int `key:"names"` @@ -5862,6 +5969,38 @@ func TestUnmarshal_Unmarshaler(t *testing.T) { }) } +func TestParseJsonStringValue(t *testing.T) { + t.Run("string", func(t *testing.T) { + type GoodsInfo struct { + Sku int64 `json:"sku,optional"` + } + + type GetReq struct { + GoodsList []*GoodsInfo `json:"goods_list"` + } + + input := map[string]any{"goods_list": "[{\"sku\":11},{\"sku\":22}]"} + var v GetReq + assert.NotPanics(t, func() { + assert.NoError(t, UnmarshalJsonMap(input, &v)) + assert.Equal(t, 2, len(v.GoodsList)) + assert.ElementsMatch(t, []int64{11, 22}, []int64{v.GoodsList[0].Sku, v.GoodsList[1].Sku}) + }) + }) + + t.Run("string with invalid type", func(t *testing.T) { + type GetReq struct { + GoodsList []*int `json:"goods_list"` + } + + input := map[string]any{"goods_list": "[{\"sku\":11},{\"sku\":22}]"} + var v GetReq + assert.NotPanics(t, func() { + assert.Error(t, UnmarshalJsonMap(input, &v)) + }) + }) +} + func BenchmarkDefaultValue(b *testing.B) { for i := 0; i < b.N; i++ { var a struct { diff --git a/go.mod b/go.mod index e37068798830..641b3c84827c 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.20 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/alicebob/miniredis/v2 v2.33.0 + github.com/alicebob/miniredis/v2 v2.34.0 github.com/fatih/color v1.18.0 - github.com/fullstorydev/grpcurl v1.9.1 + github.com/fullstorydev/grpcurl v1.9.2 github.com/go-sql-driver/mysql v1.8.1 github.com/golang-jwt/jwt/v4 v4.5.1 github.com/golang/mock v1.6.0 @@ -33,12 +33,12 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 go.uber.org/automaxprocs v1.6.0 go.uber.org/goleak v1.3.0 - golang.org/x/net v0.31.0 - golang.org/x/sys v0.27.0 + golang.org/x/net v0.33.0 + golang.org/x/sys v0.28.0 golang.org/x/time v0.8.0 google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d google.golang.org/grpc v1.65.0 - google.golang.org/protobuf v1.35.2 + google.golang.org/protobuf v1.36.1 gopkg.in/cheggaaa/pb.v1 v1.0.28 gopkg.in/h2non/gock.v1 v1.1.2 gopkg.in/yaml.v2 v2.4.0 @@ -109,11 +109,11 @@ require ( go.uber.org/atomic v1.10.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.9.0 // indirect - golang.org/x/term v0.26.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index eee226c81534..dcefca8b724f 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7Oputl github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302 h1:uvdUDbHQHO85qeSydJtItA4T55Pw6BtAejd0APRJOCE= github.com/alicebob/gopher-json v0.0.0-20230218143504-906a9b012302/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= -github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUiji6lZrA= -github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= +github.com/alicebob/miniredis/v2 v2.34.0 h1:mBFWMaJSNL9RwdGRyEDoAAv8OQc5UlEhLDQggTglU/0= +github.com/alicebob/miniredis/v2 v2.34.0/go.mod h1:kWShP4b58T1CW0Y5dViCd5ztzrDqRWqM3nksiyXk5s8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -37,8 +37,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/fullstorydev/grpcurl v1.9.1 h1:YxX1aCcCc4SDBQfj9uoWcTLe8t4NWrZe1y+mk83BQgo= -github.com/fullstorydev/grpcurl v1.9.1/go.mod h1:i8gKLIC6s93WdU3LSmkE5vtsCxyRmihUj5FK1cNW5EM= +github.com/fullstorydev/grpcurl v1.9.2 h1:ObqVQTZW7aFnhuqQoppUrvep2duMBanB0UYK2Mm8euo= +github.com/fullstorydev/grpcurl v1.9.2/go.mod h1:jLfcF55HAz6TYIJY9xFFWgsl0D7o2HlxA5Z4lUG0Tdo= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -229,8 +229,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -242,8 +242,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -251,8 +251,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -264,18 +264,18 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -295,8 +295,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/readme-cn.md b/readme-cn.md index 6e4a53cbe7bc..99add87b7960 100644 --- a/readme-cn.md +++ b/readme-cn.md @@ -299,6 +299,8 @@ go-zero 已被许多公司用于生产部署,接入场景如在线教育、电 >100. 上海邑脉科技有限公司 >101. 上海巨瓴科技有限公司 >102. 深圳市兴海物联科技有限公司 +>103. 爱芯元智半导体股份有限公司 +>104. 杭州升恒科技有限公司 如果贵公司也已使用 go-zero,欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记,仅仅为了推广,不做其它用途。 diff --git a/rest/handler/loghandler.go b/rest/handler/loghandler.go index eaf59a16b826..62f48c56bc83 100644 --- a/rest/handler/loghandler.go +++ b/rest/handler/loghandler.go @@ -158,9 +158,9 @@ func logDetails(r *http.Request, response *detailLoggedResponseWriter, timer *ut logger := logx.WithContext(r.Context()) buf.WriteString(fmt.Sprintf("[HTTP] %s - %d - %s - %s\n=> %s\n", r.Method, code, r.RemoteAddr, timex.ReprOfDuration(duration), dumpRequest(r))) - if duration > defaultSlowThreshold { + if duration > slowThreshold.Load() { logger.Slowf("[HTTP] %s - %d - %s - slowcall(%s)\n=> %s\n", r.Method, code, r.RemoteAddr, - fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)), dumpRequest(r)) + timex.ReprOfDuration(duration), dumpRequest(r)) } body := logs.Flush() diff --git a/rest/httpx/requests_test.go b/rest/httpx/requests_test.go index 437b9a136fb5..fd7fb3a5ac5e 100644 --- a/rest/httpx/requests_test.go +++ b/rest/httpx/requests_test.go @@ -88,6 +88,36 @@ func TestParseFormArray(t *testing.T) { } }) + t.Run("slice with empty", func(t *testing.T) { + var v struct { + Name []string `form:"name,optional"` + } + + r, err := http.NewRequest( + http.MethodGet, + "/a", + http.NoBody) + assert.NoError(t, err) + if assert.NoError(t, Parse(r, &v)) { + assert.ElementsMatch(t, []string{}, v.Name) + } + }) + + t.Run("slice with empty", func(t *testing.T) { + var v struct { + Name []string `form:"name,optional"` + } + + r, err := http.NewRequest( + http.MethodGet, + "/a?name=", + http.NoBody) + assert.NoError(t, err) + if assert.NoError(t, Parse(r, &v)) { + assert.ElementsMatch(t, []string{""}, v.Name) + } + }) + t.Run("slice with empty and non-empty", func(t *testing.T) { var v struct { Name []string `form:"name"` @@ -99,7 +129,67 @@ func TestParseFormArray(t *testing.T) { http.NoBody) assert.NoError(t, err) if assert.NoError(t, Parse(r, &v)) { - assert.ElementsMatch(t, []string{"1"}, v.Name) + assert.ElementsMatch(t, []string{"", "1"}, v.Name) + } + }) + + t.Run("slice with one value on array format", func(t *testing.T) { + var v struct { + Names []string `form:"names"` + } + + r, err := http.NewRequest( + http.MethodGet, + "/a?names=1,2,3", + http.NoBody) + assert.NoError(t, err) + if assert.NoError(t, Parse(r, &v)) { + assert.ElementsMatch(t, []string{"1", "2", "3"}, v.Names) + } + }) + + t.Run("slice with one value on combined array format", func(t *testing.T) { + var v struct { + Names []string `form:"names"` + } + + r, err := http.NewRequest( + http.MethodGet, + "/a?names=[1,2,3]&names=4", + http.NoBody) + assert.NoError(t, err) + if assert.NoError(t, Parse(r, &v)) { + assert.ElementsMatch(t, []string{"[1,2,3]", "4"}, v.Names) + } + }) + + t.Run("slice with one value on integer array format", func(t *testing.T) { + var v struct { + Numbers []int `form:"numbers"` + } + + r, err := http.NewRequest( + http.MethodGet, + "/a?numbers=1,2,3", + http.NoBody) + assert.NoError(t, err) + if assert.NoError(t, Parse(r, &v)) { + assert.ElementsMatch(t, []int{1, 2, 3}, v.Numbers) + } + }) + + t.Run("slice with one value on array format brackets", func(t *testing.T) { + var v struct { + Names []string `form:"names"` + } + + r, err := http.NewRequest( + http.MethodGet, + "/a?names[]=1&names[]=2&names[]=3", + http.NoBody) + assert.NoError(t, err) + if assert.NoError(t, Parse(r, &v)) { + assert.ElementsMatch(t, []string{"1", "2", "3"}, v.Names) } }) } @@ -528,6 +618,26 @@ func TestCustomUnmarshalerStructRequest(t *testing.T) { assert.Equal(t, "hello", v.Foo.Name) } +func TestParseJsonStringRequest(t *testing.T) { + type GoodsInfo struct { + Sku int64 `json:"sku,optional"` + } + + type GetReq struct { + GoodsList []*GoodsInfo `json:"goods_list"` + } + + input := `{"goods_list":"[{\"sku\":11},{\"sku\":22}]"}` + r := httptest.NewRequest(http.MethodPost, "/a", strings.NewReader(input)) + r.Header.Set(ContentType, JsonContentType) + var v GetReq + assert.NotPanics(t, func() { + assert.NoError(t, Parse(r, &v)) + assert.Equal(t, 2, len(v.GoodsList)) + assert.ElementsMatch(t, []int64{11, 22}, []int64{v.GoodsList[0].Sku, v.GoodsList[1].Sku}) + }) +} + func BenchmarkParseRaw(b *testing.B) { r, err := http.NewRequest(http.MethodGet, "http://hello.com/a?name=hello&age=18&percent=3.4", http.NoBody) if err != nil { diff --git a/rest/httpx/util.go b/rest/httpx/util.go index 19248ae74bb3..c22ad8e0d28b 100644 --- a/rest/httpx/util.go +++ b/rest/httpx/util.go @@ -2,12 +2,23 @@ package httpx import ( "errors" + "fmt" "net/http" + "strings" ) -const xForwardedFor = "X-Forwarded-For" +const ( + xForwardedFor = "X-Forwarded-For" + arraySuffix = "[]" + // most servers and clients have a limit of 8192 bytes (8 KB) + // one parameter at least take 4 chars, for example `?a=b&c=d` + maxFormParamCount = 2048 +) -// GetFormValues returns the form values. +// GetFormValues returns the form values supporting three array notation formats: +// 1. Standard notation: /api?names=alice&names=bob +// 2. Comma notation: /api?names=alice,bob +// 3. Bracket notation: /api?names[]=alice&names[]=bob func GetFormValues(r *http.Request) (map[string]any, error) { if err := r.ParseForm(); err != nil { return nil, err @@ -19,16 +30,23 @@ func GetFormValues(r *http.Request) (map[string]any, error) { } } + var n int params := make(map[string]any, len(r.Form)) for name, values := range r.Form { filtered := make([]string, 0, len(values)) for _, v := range values { - if len(v) > 0 { + if n < maxFormParamCount { filtered = append(filtered, v) + n++ + } else { + return nil, fmt.Errorf("too many form values, error: %s", r.Form.Encode()) } } if len(filtered) > 0 { + if strings.HasSuffix(name, arraySuffix) { + name = name[:len(name)-2] + } params[name] = filtered } } diff --git a/rest/httpx/util_test.go b/rest/httpx/util_test.go index 8e804cbf78ea..19725d47b1ab 100644 --- a/rest/httpx/util_test.go +++ b/rest/httpx/util_test.go @@ -1,7 +1,9 @@ package httpx import ( + "fmt" "net/http" + "net/url" "strings" "testing" @@ -23,3 +25,23 @@ func TestGetRemoteAddrNoHeader(t *testing.T) { assert.True(t, len(GetRemoteAddr(r)) == 0) } + +func TestGetFormValues_TooManyValues(t *testing.T) { + form := url.Values{} + + // Add more values than the limit + for i := 0; i < maxFormParamCount+10; i++ { + form.Add("param", fmt.Sprintf("value%d", i)) + } + + // Create a new request with the form data + req, err := http.NewRequest("POST", "/test", strings.NewReader(form.Encode())) + assert.NoError(t, err) + + // Set the content type for form data + req.Header.Set(ContentType, "application/x-www-form-urlencoded") + + _, err = GetFormValues(req) + assert.Error(t, err) + assert.Contains(t, err.Error(), "too many form values") +} diff --git a/rest/router/patrouter_test.go b/rest/router/patrouter_test.go index dca589e9fc00..02f21ece3d93 100644 --- a/rest/router/patrouter_test.go +++ b/rest/router/patrouter_test.go @@ -516,28 +516,55 @@ func TestParsePtrInRequestEmpty(t *testing.T) { } func TestParseQueryOptional(t *testing.T) { - r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil) - assert.Nil(t, err) + t.Run("optional with string", func(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever&zipcode=", nil) + assert.Nil(t, err) - router := NewRouter() - err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( - func(w http.ResponseWriter, r *http.Request) { - v := struct { - Nickname string `form:"nickname"` - Zipcode int64 `form:"zipcode,optional"` - }{} + router := NewRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Nickname string `form:"nickname"` + Zipcode string `form:"zipcode,optional"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%s", v.Nickname, v.Zipcode)) + assert.Nil(t, err) + })) + assert.Nil(t, err) - err = httpx.Parse(r, &v) - assert.Nil(t, err) - _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) - assert.Nil(t, err) - })) - assert.Nil(t, err) + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) - rr := httptest.NewRecorder() - router.ServeHTTP(rr, r) + assert.Equal(t, "whatever:", rr.Body.String()) + }) - assert.Equal(t, "whatever:0", rr.Body.String()) + t.Run("optional with int", func(t *testing.T) { + r, err := http.NewRequest(http.MethodGet, "http://hello.com/kevin/2017?nickname=whatever", nil) + assert.Nil(t, err) + + router := NewRouter() + err = router.Handle(http.MethodGet, "/:name/:year", http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + v := struct { + Nickname string `form:"nickname"` + Zipcode int `form:"zipcode,optional"` + }{} + + err = httpx.Parse(r, &v) + assert.Nil(t, err) + _, err = io.WriteString(w, fmt.Sprintf("%s:%d", v.Nickname, v.Zipcode)) + assert.Nil(t, err) + })) + assert.Nil(t, err) + + rr := httptest.NewRecorder() + router.ServeHTTP(rr, r) + + assert.Equal(t, "whatever:0", rr.Body.String()) + }) } func TestParse(t *testing.T) { diff --git a/tools/goctl/api/gogen/genroutes.go b/tools/goctl/api/gogen/genroutes.go index 6d9295f7fb07..9770a57e1341 100644 --- a/tools/goctl/api/gogen/genroutes.go +++ b/tools/goctl/api/gogen/genroutes.go @@ -139,12 +139,7 @@ rest.WithPrefix("%s"),`, g.prefix) return err } - // why we check this, maybe some users set value 1, it's 1ns, not 1s. - if duration < timeoutThreshold { - return fmt.Errorf("timeout should not less than 1ms, now %v", duration) - } - - timeout = fmt.Sprintf("\n rest.WithTimeout(%d * time.Millisecond),", duration.Milliseconds()) + timeout = fmt.Sprintf("\n rest.WithTimeout(%s),", formatDuration(duration)) hasTimeout = true } @@ -211,6 +206,16 @@ rest.WithPrefix("%s"),`, g.prefix) }) } +func formatDuration(duration time.Duration) string { + if duration < time.Microsecond { + return fmt.Sprintf("%d * time.Nanosecond", duration.Nanoseconds()) + } + if duration < time.Millisecond { + return fmt.Sprintf("%d * time.Microsecond", duration.Microseconds()) + } + return fmt.Sprintf("%d * time.Millisecond", duration.Milliseconds()) +} + func genRouteImports(parentPkg string, api *spec.ApiSpec) string { importSet := collection.NewSet() importSet.AddStr(fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, contextDir))) diff --git a/tools/goctl/api/gogen/genroutes_test.go b/tools/goctl/api/gogen/genroutes_test.go new file mode 100644 index 000000000000..9aac41822e8f --- /dev/null +++ b/tools/goctl/api/gogen/genroutes_test.go @@ -0,0 +1,27 @@ +package gogen + +import ( + "testing" + "time" +) + +func Test_formatDuration(t *testing.T) { + tests := []struct { + duration time.Duration + expected string + }{ + {0, "0 * time.Nanosecond"}, + {time.Nanosecond, "1 * time.Nanosecond"}, + {100 * time.Nanosecond, "100 * time.Nanosecond"}, + {500 * time.Microsecond, "500 * time.Microsecond"}, + {2 * time.Millisecond, "2 * time.Millisecond"}, + {time.Second, "1000 * time.Millisecond"}, + } + + for _, test := range tests { + result := formatDuration(test.duration) + if result != test.expected { + t.Errorf("formatDuration(%v) = %v; want %v", test.duration, result, test.expected) + } + } +} diff --git a/tools/goctl/bug/cmd.go b/tools/goctl/bug/cmd.go index b78de540fdde..92649b585c97 100644 --- a/tools/goctl/bug/cmd.go +++ b/tools/goctl/bug/cmd.go @@ -6,4 +6,4 @@ import ( ) // Cmd describes a bug command. -var Cmd = cobrax.NewCommand("bug", cobrax.WithRunE(cobra.NoArgs), cobrax.WithArgs(cobra.NoArgs)) +var Cmd = cobrax.NewCommand("bug", cobrax.WithRunE(runE), cobrax.WithArgs(cobra.NoArgs)) diff --git a/tools/goctl/docker/docker.tpl b/tools/goctl/docker/docker.tpl index d1b5ff44c81e..b897b4056556 100644 --- a/tools/goctl/docker/docker.tpl +++ b/tools/goctl/docker/docker.tpl @@ -4,29 +4,34 @@ LABEL stage=gobuilder ENV CGO_ENABLED 0 {{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct + RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories -{{end}}{{if .HasTimezone}} +{{- end}}{{if .HasTimezone}} RUN apk update --no-cache && apk add --no-cache tzdata -{{end}} +{{- end}} + WORKDIR /build ADD go.mod . ADD go.sum . RUN go mod download COPY . . -{{if .Argument}}COPY {{.GoRelPath}}/etc /app/etc -{{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoMainFrom}} + +RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoMainFrom}} FROM {{.BaseImage}} COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt -{{if .HasTimezone}}COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}} +{{if .HasTimezone -}} +COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}} ENV TZ {{.Timezone}} {{end}} WORKDIR /app -COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}}{{if .Argument}} -COPY --from=builder /app/etc /app/etc{{end}} +COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}} +{{if .Argument -}} +COPY {{.GoRelPath}}/etc /app/etc +{{- end}} {{if .HasPort}} EXPOSE {{.Port}} {{end}} diff --git a/tools/goctl/go.mod b/tools/goctl/go.mod index 44fa30147a50..e3e7237ccc07 100644 --- a/tools/goctl/go.mod +++ b/tools/goctl/go.mod @@ -4,21 +4,21 @@ go 1.20 require ( github.com/DATA-DOG/go-sqlmock v1.5.2 - github.com/emicklei/proto v1.13.2 + github.com/emicklei/proto v1.14.0 github.com/fatih/structtag v1.2.0 github.com/go-sql-driver/mysql v1.8.1 github.com/gookit/color v1.5.4 github.com/iancoleman/strcase v0.3.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 github.com/zeromicro/antlr v0.0.1 github.com/zeromicro/ddl-parser v1.0.5 - github.com/zeromicro/go-zero v1.7.3 - golang.org/x/text v0.19.0 + github.com/zeromicro/go-zero v1.7.4 + golang.org/x/text v0.21.0 google.golang.org/grpc v1.65.0 - google.golang.org/protobuf v1.35.1 + google.golang.org/protobuf v1.36.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -35,7 +35,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect @@ -93,13 +93,13 @@ require ( go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.9.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.28.0 // indirect - golang.org/x/net v0.30.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/net v0.31.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/time v0.7.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/time v0.8.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/tools/goctl/go.sum b/tools/goctl/go.sum index e06be845a5df..0a9986e7957c 100644 --- a/tools/goctl/go.sum +++ b/tools/goctl/go.sum @@ -30,10 +30,10 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY= -github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/emicklei/proto v1.14.0 h1:WYxC0OrBuuC+FUCTZvb8+fzEHdZMwLEF+OnVfZA3LXU= +github.com/emicklei/proto v1.14.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -158,8 +158,9 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I= github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8= @@ -173,8 +174,8 @@ github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M= github.com/zeromicro/ddl-parser v1.0.5 h1:LaVqHdzMTjasua1yYpIYaksxKqRzFrEukj2Wi2EbWaQ= github.com/zeromicro/ddl-parser v1.0.5/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8= -github.com/zeromicro/go-zero v1.7.3 h1:yDUQF2DXDhUHc77/NZF6mzsoRPMBfldjPmG2O/ZSzss= -github.com/zeromicro/go-zero v1.7.3/go.mod h1:9JIW3gHBGuc9LzvjZnNwINIq9QdiKu3AigajLtkJamQ= +github.com/zeromicro/go-zero v1.7.4 h1:lyIUsqbpVRzM4NmXu5pRM3XrdRdUuWOkQmHiNmJF0VU= +github.com/zeromicro/go-zero v1.7.4/go.mod h1:jmv4hTdUBkDn6kxgI+WrKQw0q6LKxDElGPMfCLOeeEY= go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk= go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM= go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA= @@ -215,8 +216,8 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -225,16 +226,16 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -243,17 +244,17 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -270,8 +271,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/tools/goctl/internal/version/version.go b/tools/goctl/internal/version/version.go index ce2225ecafe2..bdbbc6282157 100644 --- a/tools/goctl/internal/version/version.go +++ b/tools/goctl/internal/version/version.go @@ -6,7 +6,7 @@ import ( ) // BuildVersion is the version of goctl. -const BuildVersion = "1.7.3" +const BuildVersion = "1.7.4" var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5} diff --git a/tools/goctl/pkg/parser/api/parser/analyzer.go b/tools/goctl/pkg/parser/api/parser/analyzer.go index cecb0c5977c4..34722ef642af 100644 --- a/tools/goctl/pkg/parser/api/parser/analyzer.go +++ b/tools/goctl/pkg/parser/api/parser/analyzer.go @@ -176,11 +176,7 @@ func (a *Analyzer) convertKV(kv []*ast.KVExpr) map[string]string { var ret = map[string]string{} for _, v := range kv { key := strings.TrimSuffix(v.Key.Token.Text, ":") - if key == summaryKeyText { - ret[key] = v.Value.RawText() - } else { - ret[key] = v.Value.Token.Text - } + ret[key] = v.Value.RawText() } return ret diff --git a/tools/goctl/pkg/parser/api/parser/parser.go b/tools/goctl/pkg/parser/api/parser/parser.go index 2328f414b3e7..d4c267d6c1e7 100644 --- a/tools/goctl/pkg/parser/api/parser/parser.go +++ b/tools/goctl/pkg/parser/api/parser/parser.go @@ -13,15 +13,13 @@ import ( ) const ( - idAPI = "api" - summaryKeyExprText = "summary:" - summaryKeyText = "summary" - groupKeyText = "group" - infoTitleKey = "Title" - infoDescKey = "Desc" - infoVersionKey = "Version" - infoAuthorKey = "Author" - infoEmailKey = "Email" + idAPI = "api" + groupKeyText = "group" + infoTitleKey = "Title" + infoDescKey = "Desc" + infoVersionKey = "Version" + infoAuthorKey = "Author" + infoEmailKey = "Email" ) // Parser is the parser for api file. @@ -1201,12 +1199,6 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr { expr.Value = node return expr } else if p.peekTokenIs(token.STRING) { - if expr.Key.Token.Text != summaryKeyExprText { - if p.notExpectPeekToken(token.QUO, token.DURATION, token.IDENT, token.INT) { - return nil - } - } - if !p.nextToken() { return nil } diff --git a/tools/goctl/pkg/parser/api/parser/parser_test.go b/tools/goctl/pkg/parser/api/parser/parser_test.go index 7e4b6524fc3b..adc7b3acfa26 100644 --- a/tools/goctl/pkg/parser/api/parser/parser_test.go +++ b/tools/goctl/pkg/parser/api/parser/parser_test.go @@ -305,6 +305,7 @@ func TestParser_Parse_atServerStmt(t *testing.T) { "prefix3:": "v1/v2_", "prefix4:": "a-b-c", "summary:": `"test"`, + "key:": `"bar"`, } p := New("foo.api", atServerTestAPI) diff --git a/tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api b/tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api index 5980510e9b26..d1d67f06f0f8 100644 --- a/tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api +++ b/tools/goctl/pkg/parser/api/parser/testdata/atserver_test.api @@ -19,4 +19,5 @@ prefix3: v1/v2_ prefix4: a-b-c summary:"test" + key:"bar" ) diff --git a/tools/goctl/pkg/parser/api/parser/testdata/test.api b/tools/goctl/pkg/parser/api/parser/testdata/test.api index d9701074ef9e..ea5017dc676a 100644 --- a/tools/goctl/pkg/parser/api/parser/testdata/test.api +++ b/tools/goctl/pkg/parser/api/parser/testdata/test.api @@ -129,6 +129,7 @@ service test { @server ( jwt: Auth group: Group1 + foo:"bar" ) service test { @doc "ping" diff --git a/tools/goctl/pkg/parser/api/scanner/scanner.go b/tools/goctl/pkg/parser/api/scanner/scanner.go index 7aac005664c1..e19891040991 100644 --- a/tools/goctl/pkg/parser/api/scanner/scanner.go +++ b/tools/goctl/pkg/parser/api/scanner/scanner.go @@ -29,8 +29,6 @@ const ( // string mode end ) -var missingInput = errors.New("missing input") - type mode int // Scanner is a lexical scanner. @@ -629,7 +627,7 @@ func NewScanner(filename string, src interface{}) (*Scanner, error) { } if len(data) == 0 { - return nil, missingInput + return nil, fmt.Errorf("filename: %s,missing input", filename) } var runeList []rune diff --git a/tools/goctl/pkg/parser/api/scanner/scanner_test.go b/tools/goctl/pkg/parser/api/scanner/scanner_test.go index 5e2f1956814a..42d20ff4e7c2 100644 --- a/tools/goctl/pkg/parser/api/scanner/scanner_test.go +++ b/tools/goctl/pkg/parser/api/scanner/scanner_test.go @@ -62,13 +62,13 @@ func TestNewScanner(t *testing.T) { { filename: "foo", src: "", - expected: missingInput, + expected: "missing input", }, } for _, v := range testData { s, err := NewScanner(v.filename, v.src) if err != nil { - assert.Equal(t, v.expected.(error).Error(), err.Error()) + assert.Contains(t, err.Error(), v.expected) } else { assert.Equal(t, v.expected, s.filename) } diff --git a/tools/goctl/util/ctx/gomod.go b/tools/goctl/util/ctx/gomod.go index 15a301863a29..888d6889bc98 100644 --- a/tools/goctl/util/ctx/gomod.go +++ b/tools/goctl/util/ctx/gomod.go @@ -82,13 +82,15 @@ func getRealModule(workDir string, execRun execx.RunFunc) (*Module, error) { if err != nil { return nil, err } - + if workDir[len(workDir)-1] != os.PathSeparator { + workDir = workDir + string(os.PathSeparator) + } for _, m := range modules { realDir, err := pathx.ReadLink(m.Dir) if err != nil { return nil, fmt.Errorf("failed to read go.mod, dir: %s, error: %w", m.Dir, err) } - + realDir += string(os.PathSeparator) if strings.HasPrefix(workDir, realDir) { return &m, nil } diff --git a/tools/goctl/util/ctx/gomod_test.go b/tools/goctl/util/ctx/gomod_test.go index 2859f4eb12b1..e1bae88cb08c 100644 --- a/tools/goctl/util/ctx/gomod_test.go +++ b/tools/goctl/util/ctx/gomod_test.go @@ -98,6 +98,60 @@ func Test_getRealModule(t *testing.T) { GoVersion: "go1.20", }, }, + { + name: "go work duplicate prefix", + args: args{ + workDir: "D:\\code\\company\\core-ee\\service", + execRun: func(arg, dir string, in ...*bytes.Buffer) (string, error) { + return ` + { + "Path": "gitee.com/unitedrhino/core", + "Dir": "D:\\code\\company\\core", + "GoMod": "D:\\code\\company\\core\\go.mod", + "GoVersion": "1.21.4" + } + { + "Path": "gitee.com/unitedrhino/core-ee", + "Dir": "D:\\code\\company\\core-ee", + "GoMod": "D:\\code\\company\\core-ee\\go.mod", + "GoVersion": "1.21.4" + }`, nil + }, + }, + want: &Module{ + Path: "gitee.com/unitedrhino/core-ee", + Dir: "D:\\code\\company\\core-ee", + GoMod: "D:\\code\\company\\core-ee\\go.mod", + GoVersion: "1.21.4", + }, + }, + { + name: "go work duplicate prefix2", + args: args{ + workDir: "D:\\code\\company\\core-ee", + execRun: func(arg, dir string, in ...*bytes.Buffer) (string, error) { + return ` + { + "Path": "gitee.com/unitedrhino/core", + "Dir": "D:\\code\\company\\core", + "GoMod": "D:\\code\\company\\core\\go.mod", + "GoVersion": "1.21.4" + } + { + "Path": "gitee.com/unitedrhino/core-ee", + "Dir": "D:\\code\\company\\core-ee", + "GoMod": "D:\\code\\company\\core-ee\\go.mod", + "GoVersion": "1.21.4" + }`, nil + }, + }, + want: &Module{ + Path: "gitee.com/unitedrhino/core-ee", + Dir: "D:\\code\\company\\core-ee", + GoMod: "D:\\code\\company\\core-ee\\go.mod", + GoVersion: "1.21.4", + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {