Repository: zeromicro/go-zero
Pull request #2125 (2022-07-11 13:46:22)

feat:Add `Routes` method for server
feat https://github.com/zeromicro/go-zero/issues/2096


Patch:
diff --git a/core/errorx/wrap.go b/core/errorx/wrap.go
new file mode 100644
index 00000000..f4c7da66
--- /dev/null
+++ b/core/errorx/wrap.go
@@ -0,0 +1,21 @@
+package errorx
+
+import "fmt"
+
+// Wrap returns an error that wraps err with given message.
+func Wrap(err error, message string) error {
+	if err == nil {
+		return nil
+	}
+
+	return fmt.Errorf("%s: %w", message, err)
+}
+
+// Wrapf returns an error that wraps err with given format and args.
+func Wrapf(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+
+	return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err)
+}
diff --git a/core/logx/tracelogger.go b/core/logx/contextlogger.go
similarity index 56%
rename from core/logx/tracelogger.go
rename to core/logx/contextlogger.go
index 896bf21d..2aec407c 100644
--- a/core/logx/tracelogger.go
+++ b/core/logx/contextlogger.go
@@ -11,65 +11,65 @@ import (
 
 // WithContext sets ctx to log, for keeping tracing information.
 func WithContext(ctx context.Context) Logger {
-	return &traceLogger{
+	return &contextLogger{
 		ctx: ctx,
 	}
 }
 
-type traceLogger struct {
+type contextLogger struct {
 	logEntry
 	ctx context.Context
 }
 
-func (l *traceLogger) Error(v ...interface{}) {
+func (l *contextLogger) Error(v ...interface{}) {
 	l.err(fmt.Sprint(v...))
 }
 
-func (l *traceLogger) Errorf(format string, v ...interface{}) {
+func (l *contextLogger) Errorf(format string, v ...interface{}) {
 	l.err(fmt.Sprintf(format, v...))
 }
 
-func (l *traceLogger) Errorv(v interface{}) {
+func (l *contextLogger) Errorv(v interface{}) {
 	l.err(fmt.Sprint(v))
 }
 
-func (l *traceLogger) Errorw(msg string, fields ...LogField) {
+func (l *contextLogger) Errorw(msg string, fields ...LogField) {
 	l.err(msg, fields...)
 }
 
-func (l *traceLogger) Info(v ...interface{}) {
+func (l *contextLogger) Info(v ...interface{}) {
 	l.info(fmt.Sprint(v...))
 }
 
-func (l *traceLogger) Infof(format string, v ...interface{}) {
+func (l *contextLogger) Infof(format string, v ...interface{}) {
 	l.info(fmt.Sprintf(format, v...))
 }
 
-func (l *traceLogger) Infov(v interface{}) {
+func (l *contextLogger) Infov(v interface{}) {
 	l.info(v)
 }
 
-func (l *traceLogger) Infow(msg string, fields ...LogField) {
+func (l *contextLogger) Infow(msg string, fields ...LogField) {
 	l.info(msg, fields...)
 }
 
-func (l *traceLogger) Slow(v ...interface{}) {
+func (l *contextLogger) Slow(v ...interface{}) {
 	l.slow(fmt.Sprint(v...))
 }
 
-func (l *traceLogger) Slowf(format string, v ...interface{}) {
+func (l *contextLogger) Slowf(format string, v ...interface{}) {
 	l.slow(fmt.Sprintf(format, v...))
 }
 
-func (l *traceLogger) Slowv(v interface{}) {
+func (l *contextLogger) Slowv(v interface{}) {
 	l.slow(v)
 }
 
-func (l *traceLogger) Sloww(msg string, fields ...LogField) {
+func (l *contextLogger) Sloww(msg string, fields ...LogField) {
 	l.slow(msg, fields...)
 }
 
-func (l *traceLogger) WithContext(ctx context.Context) Logger {
+func (l *contextLogger) WithContext(ctx context.Context) Logger {
 	if ctx == nil {
 		return l
 	}
@@ -78,40 +78,49 @@ func (l *traceLogger) WithContext(ctx context.Context) Logger {
 	return l
 }
 
-func (l *traceLogger) WithDuration(duration time.Duration) Logger {
+func (l *contextLogger) WithDuration(duration time.Duration) Logger {
 	l.Duration = timex.ReprOfDuration(duration)
 	return l
 }
 
-func (l *traceLogger) buildFields(fields ...LogField) []LogField {
+func (l *contextLogger) buildFields(fields ...LogField) []LogField {
 	if len(l.Duration) > 0 {
 		fields = append(fields, Field(durationKey, l.Duration))
 	}
+
 	traceID := traceIdFromContext(l.ctx)
 	if len(traceID) > 0 {
 		fields = append(fields, Field(traceKey, traceID))
 	}
+
 	spanID := spanIdFromContext(l.ctx)
 	if len(spanID) > 0 {
 		fields = append(fields, Field(spanKey, spanID))
 	}
 
+	val := l.ctx.Value(fieldsContextKey)
+	if val != nil {
+		if arr, ok := val.([]LogField); ok {
+			fields = append(fields, arr...)
+		}
+	}
+
 	return fields
 }
 
-func (l *traceLogger) err(v interface{}, fields ...LogField) {
+func (l *contextLogger) err(v interface{}, fields ...LogField) {
 	if shallLog(ErrorLevel) {
 		getWriter().Error(v, l.buildFields(fields...)...)
 	}
 }
 
-func (l *traceLogger) info(v interface{}, fields ...LogField) {
+func (l *contextLogger) info(v interface{}, fields ...LogField) {
 	if shallLog(InfoLevel) {
 		getWriter().Info(v, l.buildFields(fields...)...)
 	}
 }
 
-func (l *traceLogger) slow(v interface{}, fields ...LogField) {
+func (l *contextLogger) slow(v interface{}, fields ...LogField) {
 	if shallLog(ErrorLevel) {
 		getWriter().Slow(v, l.buildFields(fields...)...)
 	}
diff --git a/core/logx/durationlogger.go b/core/logx/durationlogger.go
index bf51d342..a9c3e500 100644
--- a/core/logx/durationlogger.go
+++ b/core/logx/durationlogger.go
@@ -66,7 +66,7 @@ func (l *durationLogger) Sloww(msg string, fields ...LogField) {
 }
 
 func (l *durationLogger) WithContext(ctx context.Context) Logger {
-	return &traceLogger{
+	return &contextLogger{
 		ctx: ctx,
 		logEntry: logEntry{
 			Duration: l.Duration,
diff --git a/core/logx/fields.go b/core/logx/fields.go
new file mode 100644
index 00000000..91958720
--- /dev/null
+++ b/core/logx/fields.go
@@ -0,0 +1,18 @@
+package logx
+
+import "context"
+
+var fieldsContextKey contextKey
+
+type contextKey struct{}
+
+// WithFields returns a new context with the given fields.
+func WithFields(ctx context.Context, fields ...LogField) context.Context {
+	if val := ctx.Value(fieldsContextKey); val != nil {
+		if arr, ok := val.([]LogField); ok {
+			return context.WithValue(ctx, fieldsContextKey, append(arr, fields...))
+		}
+	}
+
+	return context.WithValue(ctx, fieldsContextKey, fields)
+}
diff --git a/gateway/config.go b/gateway/config.go
new file mode 100644
index 00000000..133ebbb7
--- /dev/null
+++ b/gateway/config.go
@@ -0,0 +1,7 @@
+package gateway
+
+import "github.com/zeromicro/go-zero/rest"
+
+type GatewayConf struct {
+	rest.RestConf
+}
diff --git a/gateway/server.go b/gateway/server.go
new file mode 100644
index 00000000..8cdb1f96
--- /dev/null
+++ b/gateway/server.go
@@ -0,0 +1,6 @@
+package gateway
+
+type Server struct {
+}
+
+func MustNewServer() {}
diff --git a/rest/server.go b/rest/server.go
index b0debcac..fe1464ce 100644
--- a/rest/server.go
+++ b/rest/server.go
@@ -79,6 +79,15 @@ func (s *Server) PrintRoutes() {
 	s.ngin.print()
 }
 
+// Routes returns the Http routers which are registered in the engine
+func (s *Server) Routes() []Route {
+	routers := make([]Route, len(s.ngin.routes))
+	for _, r := range s.ngin.routes {
+		routers = append(routers, r.routes...)
+	}
+	return routers
+}
+
 // Start starts the Server.
 // Graceful shutdown is enabled by default.
 // Use proc.SetTimeToForceQuit to customize the graceful shutdown period.
diff --git a/tools/goctl/model/sql/gen/insert.go b/tools/goctl/model/sql/gen/insert.go
index 1ab93069..ad9a54ba 100644
--- a/tools/goctl/model/sql/gen/insert.go
+++ b/tools/goctl/model/sql/gen/insert.go
@@ -31,7 +31,7 @@ func genInsert(table Table, withCache, postgreSql bool) (string, string, error)
 	var count int
 	for _, field := range table.Fields {
 		camel := util.SafeString(field.Name.ToCamel())
-		if camel == "CreateTime" || camel == "UpdateTime" {
+		if camel == "CreateTime" || camel == "UpdateTime" || camel == "CreateAt" || camel == "UpdateAt" {
 			continue
 		}
 
diff --git a/tools/goctl/model/sql/gen/update.go b/tools/goctl/model/sql/gen/update.go
index b6e1e17b..d209b577 100644
--- a/tools/goctl/model/sql/gen/update.go
+++ b/tools/goctl/model/sql/gen/update.go
@@ -21,7 +21,7 @@ func genUpdate(table Table, withCache, postgreSql bool) (
 	}
 	for _, field := range table.Fields {
 		camel := util.SafeString(field.Name.ToCamel())
-		if camel == "CreateTime" || camel == "UpdateTime" {
+		if camel == "CreateTime" || camel == "UpdateTime" || camel == "CreateAt" || camel == "UpdateAt" {
 			continue
 		}
 
diff --git a/tools/goctl/model/sql/template/vars.go b/tools/goctl/model/sql/template/vars.go
index e0758e77..da093d80 100644
--- a/tools/goctl/model/sql/template/vars.go
+++ b/tools/goctl/model/sql/template/vars.go
@@ -8,7 +8,7 @@ var Vars = fmt.Sprintf(
 var (
 	{{.lowerStartCamelObject}}FieldNames          = builder.RawFieldNames(&{{.upperStartCamelObject}}{}{{if .postgreSql}},true{{end}})
 	{{.lowerStartCamelObject}}Rows                = strings.Join({{.lowerStartCamelObject}}FieldNames, ",")
-	{{.lowerStartCamelObject}}RowsExpectAutoSet   = {{if .postgreSql}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "%screate_time%s", "%supdate_time%s", "%screate_t%s", "%supdate_at%s"), ","){{else}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "%screate_time%s", "%supdate_time%s", "%screate_at%s", "%supdate_at%s"), ","){{end}}
+	{{.lowerStartCamelObject}}RowsExpectAutoSet   = {{if .postgreSql}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "%screate_time%s", "%supdate_time%s", "%screate_at%s", "%supdate_at%s"), ","){{else}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, {{if .autoIncrement}}"{{.originalPrimaryKey}}",{{end}} "%screate_time%s", "%supdate_time%s", "%screate_at%s", "%supdate_at%s"), ","){{end}}
 	{{.lowerStartCamelObject}}RowsWithPlaceHolder = {{if .postgreSql}}builder.PostgreSqlJoin(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "%screate_time%s", "%supdate_time%s", "%screate_at%s", "%supdate_at%s")){{else}}strings.Join(stringx.Remove({{.lowerStartCamelObject}}FieldNames, "{{.originalPrimaryKey}}", "%screate_time%s", "%supdate_time%s", "%screate_at%s", "%supdate_at%s"), "=?,") + "=?"{{end}}
 
 	{{if .withCache}}{{.cacheKeys}}{{end}}


Test patch:
diff --git a/core/errorx/wrap_test.go b/core/errorx/wrap_test.go
new file mode 100644
index 00000000..088bc378
--- /dev/null
+++ b/core/errorx/wrap_test.go
@@ -0,0 +1,18 @@
+package errorx
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestWrap(t *testing.T) {
+	assert.Nil(t, Wrap(nil, "test"))
+	assert.Equal(t, "foo: bar", Wrap(errors.New("bar"), "foo").Error())
+}
+
+func TestWrapf(t *testing.T) {
+	assert.Nil(t, Wrapf(nil, "%s", "test"))
+	assert.Equal(t, "foo bar: quz", Wrapf(errors.New("quz"), "foo %s", "bar").Error())
+}
diff --git a/core/logx/tracelogger_test.go b/core/logx/contextlogger_test.go
similarity index 92%
rename from core/logx/tracelogger_test.go
rename to core/logx/contextlogger_test.go
index fc5964ea..a621bdfd 100644
--- a/core/logx/tracelogger_test.go
+++ b/core/logx/contextlogger_test.go
@@ -192,6 +192,25 @@ func TestTraceWithoutContext(t *testing.T) {
 	validate(t, w.String(), false, false)
 }
 
+func TestLogWithFields(t *testing.T) {
+	w := new(mockWriter)
+	old := writer.Swap(w)
+	writer.lock.RLock()
+	defer func() {
+		writer.lock.RUnlock()
+		writer.Store(old)
+	}()
+
+	ctx := WithFields(context.Background(), Field("foo", "bar"))
+	l := WithContext(ctx)
+	SetLevel(InfoLevel)
+	l.Info(testlog)
+
+	var val mockValue
+	assert.Nil(t, json.Unmarshal([]byte(w.String()), &val))
+	assert.Equal(t, "bar", val.Foo)
+}
+
 func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
 	var val mockValue
 	dec := json.NewDecoder(strings.NewReader(body))
@@ -217,4 +236,5 @@ func validate(t *testing.T, body string, expectedTrace, expectedSpan bool) {
 type mockValue struct {
 	Trace string `json:"trace"`
 	Span  string `json:"span"`
+	Foo   string `json:"foo"`
 }
diff --git a/core/logx/fields_test.go b/core/logx/fields_test.go
new file mode 100644
index 00000000..c447526f
--- /dev/null
+++ b/core/logx/fields_test.go
@@ -0,0 +1,35 @@
+package logx
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestWithFields(t *testing.T) {
+	ctx := WithFields(context.Background(), Field("a", 1), Field("b", 2))
+	vals := ctx.Value(fieldsContextKey)
+	assert.NotNil(t, vals)
+	fields, ok := vals.([]LogField)
+	assert.True(t, ok)
+	assert.EqualValues(t, []LogField{Field("a", 1), Field("b", 2)}, fields)
+}
+
+func TestWithFieldsAppend(t *testing.T) {
+	var dummyKey struct{}
+	ctx := context.WithValue(context.Background(), dummyKey, "dummy")
+	ctx = WithFields(ctx, Field("a", 1), Field("b", 2))
+	ctx = WithFields(ctx, Field("c", 3), Field("d", 4))
+	vals := ctx.Value(fieldsContextKey)
+	assert.NotNil(t, vals)
+	fields, ok := vals.([]LogField)
+	assert.True(t, ok)
+	assert.Equal(t, "dummy", ctx.Value(dummyKey))
+	assert.EqualValues(t, []LogField{
+		Field("a", 1),
+		Field("b", 2),
+		Field("c", 3),
+		Field("d", 4),
+	}, fields)
+}
diff --git a/rest/server_test.go b/rest/server_test.go
index 0f4af3f5..d9cc4011 100644
--- a/rest/server_test.go
+++ b/rest/server_test.go
@@ -425,6 +425,56 @@ Port: 54321
 	assert.Equal(t, expect, out)
 }
 
+func TestServer_Routes(t *testing.T) {
+	const (
+		configYaml = `
+Name: foo
+Port: 54321
+`
+		expect = `GET /foo GET /bar GET /foo/:bar GET /foo/:bar/baz`
+	)
+
+	var cnf RestConf
+	assert.Nil(t, conf.LoadFromYamlBytes([]byte(configYaml), &cnf))
+
+	svr, err := NewServer(cnf)
+	assert.Nil(t, err)
+
+	svr.AddRoutes([]Route{
+		{
+			Method:  http.MethodGet,
+			Path:    "/foo",
+			Handler: http.NotFound,
+		},
+		{
+			Method:  http.MethodGet,
+			Path:    "/bar",
+			Handler: http.NotFound,
+		},
+		{
+			Method:  http.MethodGet,
+			Path:    "/foo/:bar",
+			Handler: http.NotFound,
+		},
+		{
+			Method:  http.MethodGet,
+			Path:    "/foo/:bar/baz",
+			Handler: http.NotFound,
+		},
+	})
+
+	routes := svr.Routes()
+	var buf strings.Builder
+	for i := 0; i < len(routes); i++ {
+		buf.WriteString(routes[i].Method)
+		buf.WriteString(" ")
+		buf.WriteString(routes[i].Path)
+		buf.WriteString(" ")
+	}
+
+	assert.Equal(t, expect, strings.Trim(buf.String(), " "))
+}
+
 func TestHandleError(t *testing.T) {
 	assert.NotPanics(t, func() {
 		handleError(nil)