Repository: zeromicro/go-zero
Pull request #2483 (2022-10-05 14:47:30)

feat(goctl): better generate the api code of typescript
When generating the api code of typescript, if the tag contains the options modifier, the union type is generated instead of the declared type. Here is an example.

Api:

```api
type SomeType {
	Foo string `json:"foo,options=foo,bar,options,123"`
	Bar int `json:"bar,options=1,3,4,123"`
}
```

Typescript:

```typescript
export interface SomeType {
    foo: 'foo' | 'bar' | 'options' | '123'
    bar: 1 | 3 | 4 | 123
}
```

This can provide a good intelligent complement for the front-end IDE.

I have completed  the test file.


Patch:
diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go
index a4cdf3f6..2cd1b93e 100644
--- a/core/mapping/unmarshaler.go
+++ b/core/mapping/unmarshaler.go
@@ -25,9 +25,9 @@ var (
 	errValueNotStruct   = errors.New("value type is not struct")
 	keyUnmarshaler      = NewUnmarshaler(defaultKeyName)
 	durationType        = reflect.TypeOf(time.Duration(0))
-	cacheKeys           map[string][]string
+	cacheKeys           = make(map[string][]string)
 	cacheKeysLock       sync.Mutex
-	defaultCache        map[string]interface{}
+	defaultCache        = make(map[string]interface{})
 	defaultCacheLock    sync.Mutex
 	emptyMap            = map[string]interface{}{}
 	emptyValue          = reflect.ValueOf(lang.Placeholder)
@@ -49,11 +49,6 @@ type (
 	}
 )
 
-func init() {
-	cacheKeys = make(map[string][]string)
-	defaultCache = make(map[string]interface{})
-}
-
 // NewUnmarshaler returns a Unmarshaler.
 func NewUnmarshaler(key string, opts ...UnmarshalOption) *Unmarshaler {
 	unmarshaler := Unmarshaler{
diff --git a/core/mr/mapreduce.go b/core/mr/mapreduce.go
index 1734f7ce..108c505e 100644
--- a/core/mr/mapreduce.go
+++ b/core/mr/mapreduce.go
@@ -145,7 +145,7 @@ func MapReduceChan(source <-chan interface{}, mapper MapperFunc, reducer Reducer
 	return mapReduceWithPanicChan(source, panicChan, mapper, reducer, opts...)
 }
 
-// MapReduceChan maps all elements from source, and reduce the output elements with given reducer.
+// mapReduceWithPanicChan maps all elements from source, and reduce the output elements with given reducer.
 func mapReduceWithPanicChan(source <-chan interface{}, panicChan *onceChan, mapper MapperFunc,
 	reducer ReducerFunc, opts ...Option) (interface{}, error) {
 	options := buildOptions(opts...)
diff --git a/go.mod b/go.mod
index b8eaf4b9..c84e2543 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,7 @@ require (
 	github.com/stretchr/testify v1.8.0
 	go.etcd.io/etcd/api/v3 v3.5.5
 	go.etcd.io/etcd/client/v3 v3.5.5
-	go.mongodb.org/mongo-driver v1.10.2
+	go.mongodb.org/mongo-driver v1.10.3
 	go.opentelemetry.io/otel v1.10.0
 	go.opentelemetry.io/otel/exporters/jaeger v1.10.0
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.10.0
@@ -35,7 +35,7 @@ require (
 	go.uber.org/goleak v1.2.0
 	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
 	golang.org/x/time v0.0.0-20220411224347-583f2d630306
-	google.golang.org/grpc v1.49.0
+	google.golang.org/grpc v1.50.0
 	google.golang.org/protobuf v1.28.1
 	gopkg.in/cheggaaa/pb.v1 v1.0.28
 	gopkg.in/h2non/gock.v1 v1.1.2
diff --git a/go.sum b/go.sum
index 7f1d7eeb..70d0ed0f 100644
--- a/go.sum
+++ b/go.sum
@@ -476,8 +476,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.5 h1:9S0JUVvmrVl7wCF39iTQthdaaNIiAaQbmK75ogO6
 go.etcd.io/etcd/client/pkg/v3 v3.5.5/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ=
 go.etcd.io/etcd/client/v3 v3.5.5 h1:q++2WTJbUgpQu4B6hCuT7VkdwaTP7Qz6Daak3WzbrlI=
 go.etcd.io/etcd/client/v3 v3.5.5/go.mod h1:aApjR4WGlSumpnJ2kloS75h6aHUmAyaPLjHMxpc7E7c=
-go.mongodb.org/mongo-driver v1.10.2 h1:4Wk3cnqOrQCn0P92L3/mmurMxzdvWWs5J9jinAVKD+k=
-go.mongodb.org/mongo-driver v1.10.2/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
+go.mongodb.org/mongo-driver v1.10.3 h1:XDQEvmh6z1EUsXuIkXE9TaVeqHw6SwS1uf93jFs0HBA=
+go.mongodb.org/mongo-driver v1.10.3/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -838,8 +838,8 @@ google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzI
 google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
 google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
-google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw=
-google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
+google.golang.org/grpc v1.50.0 h1:fPVVDxY9w++VjTZsYvXWqEf9Rqar/e+9zYfxKK+W+YU=
+google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
diff --git a/tools/goctl/api/spec/fn.go b/tools/goctl/api/spec/fn.go
index 8821b3a7..d208a8c2 100644
--- a/tools/goctl/api/spec/fn.go
+++ b/tools/goctl/api/spec/fn.go
@@ -154,6 +154,27 @@ func (m Member) IsTagMember(tagKey string) bool {
 	return false
 }
 
+// GetEnumOptions return a slice contains all enumeration options
+func (m Member) GetEnumOptions() []string {
+	if !m.IsBodyMember() {
+		return nil
+	}
+
+	tags := m.Tags()
+	for _, tag := range tags {
+		if tag.Key == bodyTagKey {
+			options := tag.Options
+			for _, option := range options {
+				if strings.Index(option, "options=") == 0 {
+					option = strings.TrimPrefix(option, "options=")
+					return strings.Split(option, "|")
+				}
+			}
+		}
+	}
+	return nil
+}
+
 // GetBodyMembers returns all json fields
 func (t DefineStruct) GetBodyMembers() []Member {
 	var result []Member
diff --git a/tools/goctl/api/tsgen/util.go b/tools/goctl/api/tsgen/util.go
index 27c230d9..5d211717 100644
--- a/tools/goctl/api/tsgen/util.go
+++ b/tools/goctl/api/tsgen/util.go
@@ -19,7 +19,7 @@ const (
 
 func writeProperty(writer io.Writer, member spec.Member, indent int) error {
 	writeIndent(writer, indent)
-	ty, err := goTypeToTs(member.Type, false)
+	ty, err := genTsType(member)
 	if err != nil {
 		return err
 	}
@@ -52,6 +52,19 @@ func writeIndent(writer io.Writer, indent int) {
 	}
 }
 
+func genTsType(m spec.Member) (ty string, err error) {
+	ty, err = goTypeToTs(m.Type, false)
+	if enums := m.GetEnumOptions(); enums != nil {
+		if ty == "string" {
+			for i := range enums {
+				enums[i] = "'" + enums[i] + "'"
+			}
+		}
+		ty = strings.Join(enums, " | ")
+	}
+	return
+}
+
 func goTypeToTs(tp spec.Type, fromPacket bool) (string, error) {
 	switch v := tp.(type) {
 	case spec.DefineStruct:
diff --git a/tools/goctl/kube/kube.go b/tools/goctl/kube/kube.go
index dc701be8..03ba2028 100644
--- a/tools/goctl/kube/kube.go
+++ b/tools/goctl/kube/kube.go
@@ -49,7 +49,7 @@ type Deployment struct {
 	ImagePullPolicy string
 }
 
-// DeploymentCommand is used to generate the kubernetes deployment yaml files.
+// deploymentCommand is used to generate the kubernetes deployment yaml files.
 func deploymentCommand(_ *cobra.Command, _ []string) error {
 	nodePort := varIntNodePort
 	home := varStringHome
diff --git a/tools/goctl/upgrade/upgrade.go b/tools/goctl/upgrade/upgrade.go
index 1f373cfb..7d96bf0f 100644
--- a/tools/goctl/upgrade/upgrade.go
+++ b/tools/goctl/upgrade/upgrade.go
@@ -8,7 +8,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
 )
 
-// Upgrade gets the latest goctl by
+// upgrade gets the latest goctl by
 // go install github.com/zeromicro/go-zero/tools/goctl@latest
 func upgrade(_ *cobra.Command, _ []string) error {
 	cmd := `GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
diff --git a/zrpc/internal/clientinterceptors/durationinterceptor.go b/zrpc/internal/clientinterceptors/durationinterceptor.go
index 1abf0002..3f65a8bf 100644
--- a/zrpc/internal/clientinterceptors/durationinterceptor.go
+++ b/zrpc/internal/clientinterceptors/durationinterceptor.go
@@ -22,7 +22,7 @@ func DurationInterceptor(ctx context.Context, method string, req, reply interfac
 	start := timex.Now()
 	err := invoker(ctx, method, req, reply, cc, opts...)
 	if err != nil {
-		logx.WithContext(ctx).WithDuration(timex.Since(start)).Infof("fail - %s - %v - %s",
+		logx.WithContext(ctx).WithDuration(timex.Since(start)).Errorf("fail - %s - %v - %s",
 			serverName, req, err.Error())
 	} else {
 		elapsed := timex.Since(start)
diff --git a/zrpc/internal/rpclogger.go b/zrpc/internal/rpclogger.go
index e7b057bb..e3c3148c 100644
--- a/zrpc/internal/rpclogger.go
+++ b/zrpc/internal/rpclogger.go
@@ -1,8 +1,6 @@
 package internal
 
 import (
-	"sync"
-
 	"github.com/zeromicro/go-zero/core/logx"
 	"google.golang.org/grpc/grpclog"
 )
@@ -10,16 +8,11 @@ import (
 // because grpclog.errorLog is not exported, we need to define our own.
 const errorLevel = 2
 
-var once sync.Once
-
 // A Logger is a rpc logger.
 type Logger struct{}
 
-// InitLogger initializes the rpc logger.
-func InitLogger() {
-	once.Do(func() {
-		grpclog.SetLoggerV2(new(Logger))
-	})
+func init() {
+	grpclog.SetLoggerV2(new(Logger))
 }
 
 // Error logs the given args into error log.
diff --git a/zrpc/internal/rpcserver.go b/zrpc/internal/rpcserver.go
index 36408776..cf9c8ce8 100644
--- a/zrpc/internal/rpcserver.go
+++ b/zrpc/internal/rpcserver.go
@@ -25,10 +25,6 @@ type (
 	}
 )
 
-func init() {
-	InitLogger()
-}
-
 // NewRpcServer returns a Server.
 func NewRpcServer(address string, opts ...ServerOption) Server {
 	var options rpcServerOptions


Test patch:
diff --git a/rest/internal/encoding/parser_test.go b/rest/internal/encoding/parser_test.go
index b04b3f72..2708098c 100644
--- a/rest/internal/encoding/parser_test.go
+++ b/rest/internal/encoding/parser_test.go
@@ -48,4 +48,4 @@ func TestParseHeadersArrayInt(t *testing.T) {
 	r.Header.Add("foo", "2")
 	assert.Nil(t, ParseHeaders(r.Header, &val))
 	assert.Equal(t, []int{1, 2}, val.Foo)
-}
\ No newline at end of file
+}
diff --git a/tools/goctl/api/spec/example_test.go b/tools/goctl/api/spec/example_test.go
new file mode 100644
index 00000000..eaa1651d
--- /dev/null
+++ b/tools/goctl/api/spec/example_test.go
@@ -0,0 +1,15 @@
+package spec_test
+
+import (
+	"fmt"
+	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
+)
+
+func ExampleMember_GetEnumOptions() {
+	member := spec.Member{
+		Tag: `json:"foo,options=foo|bar|options|123"`,
+	}
+	fmt.Println(member.GetEnumOptions())
+	// Output:
+	// [foo bar options 123]
+}
diff --git a/tools/goctl/api/tsgen/util_test.go b/tools/goctl/api/tsgen/util_test.go
new file mode 100644
index 00000000..de11800b
--- /dev/null
+++ b/tools/goctl/api/tsgen/util_test.go
@@ -0,0 +1,38 @@
+package tsgen
+
+import (
+	"github.com/stretchr/testify/assert"
+	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
+	"testing"
+)
+
+func TestGenTsType(t *testing.T) {
+	member := spec.Member{
+		Name:     "foo",
+		Type:     spec.PrimitiveType{RawName: "string"},
+		Tag:      `json:"foo,options=foo|bar|options|123"`,
+		Comment:  "",
+		Docs:     nil,
+		IsInline: false,
+	}
+	ty, err := genTsType(member)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, `'foo' | 'bar' | 'options' | '123'`, ty)
+
+	member.IsInline = true
+	ty, err = genTsType(member)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, `'foo' | 'bar' | 'options' | '123'`, ty)
+
+	member.Type = spec.PrimitiveType{RawName: "int"}
+	member.Tag = `json:"foo,options=1|3|4|123"`
+	ty, err = genTsType(member)
+	if err != nil {
+		t.Fatal(err)
+	}
+	assert.Equal(t, `1 | 3 | 4 | 123`, ty)
+}