feat: add generic i18n and validator
parent
7dad55d2d5
commit
b67467c0b4
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"go.goroot": "/opt/gvm/gos/go1.20",
|
"go.goroot": "/opt/go/sdk/go",
|
||||||
"go.gopath": "/opt/gvm/pkgsets/go1.20/global"
|
"go.gopath": "/root/.go"
|
||||||
}
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
package apis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ggicci/httpin"
|
||||||
|
httpin_integration "github.com/ggicci/httpin/integration"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
|
||||||
|
"git.ifooth.com/common/pkg/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UnaryFunc Unary or ClientStreaming handle function
|
||||||
|
type UnaryFunc[In, Out any] func(context.Context, *In) (*Out, error)
|
||||||
|
|
||||||
|
// StreamingServer server or bidi streaming server
|
||||||
|
type StreamingServer interface {
|
||||||
|
http.ResponseWriter
|
||||||
|
Context() context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamFunc ServerStreaming or BidiStreaming handle function
|
||||||
|
type StreamFunc[In any] func(*In, StreamingServer) error
|
||||||
|
|
||||||
|
// Handle Composable HTTP Handlers using generics
|
||||||
|
func Handle[In any, Out any](fn UnaryFunc[In, Out]) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handleName := getHandleName(fn)
|
||||||
|
|
||||||
|
f := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.Header.Set("Content-Type", "application/json")
|
||||||
|
st := time.Now()
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
collectHandleMetrics(handleName, r.Method, st, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
in, err := decodeReq[In](r)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("handle decode request failed", "err", err)
|
||||||
|
_ = render.Render(w, r, APIError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置语言
|
||||||
|
ctx := i18n.SetLang(r.Context(), r.Header.Get("Accept-Language"))
|
||||||
|
ctx = context.WithValue(ctx, reqCtxKey, r)
|
||||||
|
|
||||||
|
out, err := fn(ctx, in)
|
||||||
|
if err != nil {
|
||||||
|
_ = render.Render(w, r, APIError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = render.Render(w, r, APIOK(out))
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
type streamingServer struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
*http.ResponseController
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context return svr's context
|
||||||
|
func (s *streamingServer) Context() context.Context {
|
||||||
|
return s.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stream Composable HTTP Handlers using generics
|
||||||
|
func Stream[In any](fn StreamFunc[In]) func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handleName := getHandleName(fn)
|
||||||
|
|
||||||
|
f := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
st := time.Now()
|
||||||
|
var err error
|
||||||
|
defer func() {
|
||||||
|
collectHandleMetrics(handleName, r.Method, st, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
in, err := decodeReq[In](r)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("handle decode request failed ", "err", err)
|
||||||
|
_ = render.Render(w, r, APIError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置语言
|
||||||
|
ctx := i18n.SetLang(r.Context(), r.Header.Get("Accept-Language"))
|
||||||
|
ctx = context.WithValue(ctx, reqCtxKey, r)
|
||||||
|
|
||||||
|
svr := &streamingServer{
|
||||||
|
ResponseWriter: w,
|
||||||
|
ResponseController: http.NewResponseController(w),
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fn(in, svr)
|
||||||
|
if err != nil {
|
||||||
|
_ = render.Render(w, r, APIError(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeReq ...
|
||||||
|
func decodeReq[T any](r *http.Request) (*T, error) {
|
||||||
|
in := new(T)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// http.Request 直接返回
|
||||||
|
if _, ok := any(in).(*http.Request); ok {
|
||||||
|
return any(r).(*T), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空值不需要反序列化
|
||||||
|
if _, ok := any(in).(*EmptyReq); ok {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err = httpin.Decode[T](r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get/Delete 请求, 请求参数从url中获取
|
||||||
|
if r.Method == http.MethodGet || r.Method == http.MethodDelete {
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post 请求等, 从body中获取
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = json.Unmarshal(body, in); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal json body: %s", err)
|
||||||
|
}
|
||||||
|
return in, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyReq 空的请求
|
||||||
|
type EmptyReq struct{}
|
||||||
|
|
||||||
|
// EmptyResp 空的返回
|
||||||
|
type EmptyResp struct{}
|
||||||
|
|
||||||
|
// PaginationReq 分页接口通用请求
|
||||||
|
type PaginationReq struct {
|
||||||
|
Offset int `json:"offset" in:"query=offset" validate:"gte=0"`
|
||||||
|
Limit int `json:"limit" in:"query=limit" validate:"gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginationResp 分页接口通用返回
|
||||||
|
type PaginationResp[T any] struct {
|
||||||
|
Count int64 `json:"count"`
|
||||||
|
Items []*T `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
httpin_integration.UseGochiURLParam("path", chi.URLParam)
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package apis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewMockRequest creates a new mock request.
|
||||||
|
func NewMockRequest(t *testing.T, method string, body io.ReadCloser) *http.Request {
|
||||||
|
req, err := http.NewRequest(method, "/vm/xxx?name=alice", body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return req
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDecodeReq tests the decodeReq function.
|
||||||
|
func TestDecodeReq(t *testing.T) {
|
||||||
|
// Define a test struct
|
||||||
|
type TestStruct struct {
|
||||||
|
Field string `json:"field"`
|
||||||
|
EnvID string `json:"env_id" in:"path=env_id" validate:"required"`
|
||||||
|
Name string `json:"name" in:"query=name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test case 1: GET request with no body
|
||||||
|
t.Run("GET request with no body", func(t *testing.T) {
|
||||||
|
req := NewMockRequest(t, http.MethodGet, nil)
|
||||||
|
result, err := decodeReq[TestStruct](req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "alice", result.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test case 2: POST request with valid JSON body
|
||||||
|
t.Run("POST request with valid JSON body", func(t *testing.T) {
|
||||||
|
jsonBody := `{"field": "value", "env_id": "test", "name": "alice1"}`
|
||||||
|
body := io.NopCloser(bytes.NewBufferString(jsonBody))
|
||||||
|
req := NewMockRequest(t, http.MethodPost, body)
|
||||||
|
result, err := decodeReq[TestStruct](req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "value", result.Field)
|
||||||
|
assert.Equal(t, "test", result.EnvID)
|
||||||
|
assert.Equal(t, "alice1", result.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test case 3: POST request with invalid JSON body
|
||||||
|
t.Run("POST request with invalid JSON body", func(t *testing.T) {
|
||||||
|
jsonBody := `{"field": "value"` // Invalid JSON
|
||||||
|
body := io.NopCloser(bytes.NewBufferString(jsonBody))
|
||||||
|
req := NewMockRequest(t, http.MethodPost, body)
|
||||||
|
result, err := decodeReq[TestStruct](req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test case 4: Invalid request header
|
||||||
|
t.Run("invalid request header", func(t *testing.T) {
|
||||||
|
jsonBody := `{"field": "value", "env_id": "test", "name": "alice1"}`
|
||||||
|
body := io.NopCloser(bytes.NewBufferString(jsonBody))
|
||||||
|
req := NewMockRequest(t, http.MethodPost, body)
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
result, err := decodeReq[TestStruct](req)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Nil(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test case 5: EmptyReq type check
|
||||||
|
t.Run("EmptyReq type check", func(t *testing.T) {
|
||||||
|
req := NewMockRequest(t, http.MethodPost, nil)
|
||||||
|
result, err := decodeReq[EmptyReq](req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, result)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test case 5: EmptyReq type check
|
||||||
|
t.Run("HttpRequest type check", func(t *testing.T) {
|
||||||
|
req := &http.Request{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
}
|
||||||
|
result, err := decodeReq[http.Request](req)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, req.Method, result.Method)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecodeReq(b *testing.B) {
|
||||||
|
r := &http.Request{
|
||||||
|
Method: http.MethodPost,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
result, err := decodeReq[http.Request](r)
|
||||||
|
if err != nil {
|
||||||
|
b.Error(err)
|
||||||
|
}
|
||||||
|
if result.Method != http.MethodPost {
|
||||||
|
b.Error("invalid result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package apis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
requestCounter = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "http_requests_total",
|
||||||
|
Help: "Number of get requests.",
|
||||||
|
},
|
||||||
|
[]string{"handler", "method", "code"},
|
||||||
|
)
|
||||||
|
responseTimeDuration = prometheus.NewHistogramVec(
|
||||||
|
prometheus.HistogramOpts{
|
||||||
|
Name: "http_request_duration_seconds",
|
||||||
|
Help: "Histogram of response time for HTTP requests.",
|
||||||
|
Buckets: []float64{0.1, 0.2, 0.5, 1, 2, 5, 10, 30, 60},
|
||||||
|
},
|
||||||
|
[]string{"handler", "method", "code"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// getHandleName 获取FuncHandle/StreamHandle函数名
|
||||||
|
func getHandleName(fn any) string {
|
||||||
|
fullName := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
|
||||||
|
if fullName == "" {
|
||||||
|
panic("get func name is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(fullName, ".")
|
||||||
|
lastPart := parts[len(parts)-1]
|
||||||
|
name := strings.TrimSuffix(lastPart, "-fm")
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
// collectHandleMetrics api指标数据
|
||||||
|
func collectHandleMetrics(funcName, method string, st time.Time, err error) {
|
||||||
|
code := 200
|
||||||
|
if err != nil {
|
||||||
|
code = APIError(err).(*APIResponse).HTTPStatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
codeStr := strconv.Itoa(code)
|
||||||
|
requestCounter.WithLabelValues(funcName, method, codeStr).Inc()
|
||||||
|
duration := time.Since(st).Seconds()
|
||||||
|
responseTimeDuration.WithLabelValues(funcName, method, codeStr).Observe(duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
prometheus.MustRegister(requestCounter)
|
||||||
|
prometheus.MustRegister(responseTimeDuration)
|
||||||
|
}
|
|
@ -13,6 +13,14 @@ import (
|
||||||
"git.ifooth.com/common/pkg/components"
|
"git.ifooth.com/common/pkg/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type contextKey struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
reqCtxKey = &contextKey{"HTTPRequest"}
|
||||||
|
)
|
||||||
|
|
||||||
// RequestIdGenerator request_id
|
// RequestIdGenerator request_id
|
||||||
func RequestIdGenerator() string {
|
func RequestIdGenerator() string {
|
||||||
uid := uuid.New().String()
|
uid := uuid.New().String()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package rest
|
package apis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -36,8 +36,8 @@ func (h RestHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result 返回的标准结构
|
// APIResponse 返回的标准结构
|
||||||
type Response struct {
|
type APIResponse struct {
|
||||||
Err error `json:"-"` // low-level runtime error
|
Err error `json:"-"` // low-level runtime error
|
||||||
HTTPStatusCode int `json:"-"` // http response status code
|
HTTPStatusCode int `json:"-"` // http response status code
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
|
@ -47,7 +47,7 @@ type Response struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render chi Render 实现
|
// Render chi Render 实现
|
||||||
func (res *Response) Render(w http.ResponseWriter, r *http.Request) error {
|
func (res *APIResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||||
statusCode := res.HTTPStatusCode
|
statusCode := res.HTTPStatusCode
|
||||||
if statusCode == 0 {
|
if statusCode == 0 {
|
||||||
statusCode = http.StatusOK
|
statusCode = http.StatusOK
|
||||||
|
@ -61,7 +61,7 @@ func (res *Response) Render(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
// UnauthorizedErrRender 未登入返回
|
// UnauthorizedErrRender 未登入返回
|
||||||
func AbortWithUnauthorizedError(err error) render.Renderer {
|
func AbortWithUnauthorizedError(err error) render.Renderer {
|
||||||
return &Response{
|
return &APIResponse{
|
||||||
Code: 1401,
|
Code: 1401,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
HTTPStatusCode: http.StatusUnauthorized,
|
HTTPStatusCode: http.StatusUnauthorized,
|
||||||
|
@ -70,7 +70,17 @@ func AbortWithUnauthorizedError(err error) render.Renderer {
|
||||||
|
|
||||||
// AbortWithBadRequestError 错误
|
// AbortWithBadRequestError 错误
|
||||||
func AbortWithBadRequestError(err error) render.Renderer {
|
func AbortWithBadRequestError(err error) render.Renderer {
|
||||||
return &Response{
|
return &APIResponse{
|
||||||
|
Code: 1400,
|
||||||
|
Message: err.Error(),
|
||||||
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// AbortWithBadRequestError 错误
|
||||||
|
func APIError(err error) render.Renderer {
|
||||||
|
return &APIResponse{
|
||||||
Code: 1400,
|
Code: 1400,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
HTTPStatusCode: http.StatusBadRequest,
|
HTTPStatusCode: http.StatusBadRequest,
|
||||||
|
@ -80,7 +90,7 @@ func AbortWithBadRequestError(err error) render.Renderer {
|
||||||
|
|
||||||
// AbortWithWithForbiddenError 没有权限
|
// AbortWithWithForbiddenError 没有权限
|
||||||
func AbortWithWithForbiddenError(err error) render.Renderer {
|
func AbortWithWithForbiddenError(err error) render.Renderer {
|
||||||
return &Response{
|
return &APIResponse{
|
||||||
Code: 1403,
|
Code: 1403,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
HTTPStatusCode: http.StatusForbidden,
|
HTTPStatusCode: http.StatusForbidden,
|
||||||
|
@ -88,6 +98,6 @@ func AbortWithWithForbiddenError(err error) render.Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// OKRender 正常返回
|
// OKRender 正常返回
|
||||||
func OKRender(data interface{}) render.Renderer {
|
func APIOK(data interface{}) render.Renderer {
|
||||||
return &Response{Data: data}
|
return &APIResponse{Data: data}
|
||||||
}
|
}
|
25
go.mod
25
go.mod
|
@ -1,18 +1,24 @@
|
||||||
module git.ifooth.com/common/pkg
|
module git.ifooth.com/common/pkg
|
||||||
|
|
||||||
go 1.22.0
|
go 1.21
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/felixge/fgprof v0.9.3
|
github.com/felixge/fgprof v0.9.3
|
||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/ggicci/httpin v0.19.0
|
||||||
|
github.com/go-chi/chi/v5 v5.0.11
|
||||||
github.com/go-chi/render v1.0.2
|
github.com/go-chi/render v1.0.2
|
||||||
|
github.com/go-playground/locales v0.14.1
|
||||||
|
github.com/go-playground/universal-translator v0.18.1
|
||||||
|
github.com/go-playground/validator/v10 v10.23.0
|
||||||
github.com/go-resty/resty/v2 v2.7.0
|
github.com/go-resty/resty/v2 v2.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.14.0
|
github.com/prometheus/client_golang v1.14.0
|
||||||
github.com/redis/go-redis/v9 v9.0.3
|
github.com/redis/go-redis/v9 v9.0.3
|
||||||
|
github.com/samber/lo v1.47.0
|
||||||
|
github.com/stretchr/testify v1.9.0
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0
|
||||||
go.opentelemetry.io/otel v1.27.0
|
go.opentelemetry.io/otel v1.27.0
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
|
||||||
|
@ -27,23 +33,36 @@ require (
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/ggicci/owl v0.8.2 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.1 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
|
||||||
|
github.com/labstack/echo/v4 v4.12.0 // indirect
|
||||||
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.42.0 // indirect
|
github.com/prometheus/common v0.42.0 // indirect
|
||||||
github.com/prometheus/procfs v0.9.0 // indirect
|
github.com/prometheus/procfs v0.9.0 // indirect
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
go.opentelemetry.io/otel/metric v1.27.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
go.opentelemetry.io/otel/trace v1.27.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
|
||||||
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
golang.org/x/net v0.25.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.16.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
|
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
55
go.sum
55
go.sum
|
@ -24,8 +24,14 @@ github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/ggicci/httpin v0.19.0 h1:p0B3SWLVgg770VirYiHB14M5wdRx3zR8mCTzM/TkTQ8=
|
||||||
|
github.com/ggicci/httpin v0.19.0/go.mod h1:hzsQHcbqLabmGOycf7WNw6AAzcVbsMeoOp46bWAbIWc=
|
||||||
|
github.com/ggicci/owl v0.8.2 h1:og+lhqpzSMPDdEB+NJfzoAJARP7qCG3f8uUC3xvGukA=
|
||||||
|
github.com/ggicci/owl v0.8.2/go.mod h1:PHRD57u41vFN5UtFz2SF79yTVoM3HlWpjMiE+ZU2dj4=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA=
|
||||||
|
github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
github.com/go-chi/render v1.0.2 h1:4ER/udB0+fMWB2Jlf15RV3F4A2FDuYi/9f+lFttR/Lg=
|
||||||
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
@ -34,6 +40,14 @@ github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||||
|
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
@ -46,9 +60,28 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8
|
||||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=
|
||||||
|
github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo=
|
||||||
|
github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||||
|
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||||
|
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||||
|
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
@ -67,12 +100,20 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf
|
||||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||||
github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k=
|
github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k=
|
||||||
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
|
||||||
|
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/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||||
|
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
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.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 h1:9l89oX4ba9kHbBol3Xin3leYJ+252h0zszDtBwyKe2A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0/go.mod h1:XLZfZboOJWHNKUv7eH0inh0E9VV6eWDFB/9yJyTLPp0=
|
||||||
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
|
||||||
|
@ -91,6 +132,8 @@ go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IO
|
||||||
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
@ -98,12 +141,14 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
|
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU=
|
||||||
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
|
google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak=
|
||||||
|
@ -112,6 +157,8 @@ google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLp
|
||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Package i18n is used to localize for different languages
|
||||||
|
package i18n
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type acceptLangKey struct{}
|
||||||
|
|
||||||
|
// SetLang 设定语言
|
||||||
|
func SetLang(ctx context.Context, val string) context.Context {
|
||||||
|
return context.WithValue(ctx, acceptLangKey{}, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLang 获取语言
|
||||||
|
func GetLang(ctx context.Context) (string, bool) {
|
||||||
|
value, ok := ctx.Value(acceptLangKey{}).(string)
|
||||||
|
return value, ok
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Package validator use for valiate struct
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/locales/en"
|
||||||
|
"github.com/go-playground/locales/zh"
|
||||||
|
ut "github.com/go-playground/universal-translator"
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
en_translations "github.com/go-playground/validator/v10/translations/en"
|
||||||
|
zh_translations "github.com/go-playground/validator/v10/translations/zh"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
|
||||||
|
"git.ifooth.com/common/pkg/i18n"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validate *validator.Validate
|
||||||
|
uni *ut.UniversalTranslator
|
||||||
|
defaultTrans ut.Translator
|
||||||
|
zhTrans ut.Translator
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidationError 校验错误
|
||||||
|
type ValidationError struct {
|
||||||
|
ctx context.Context
|
||||||
|
rawErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validator 实现了 Validate 接口自定义调用
|
||||||
|
type Validator interface {
|
||||||
|
Validate() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ValidationError) getTranslator() ut.Translator {
|
||||||
|
acceptLang, ok := i18n.GetLang(e.ctx)
|
||||||
|
if ok && acceptLang == "zh" {
|
||||||
|
return zhTrans
|
||||||
|
}
|
||||||
|
return defaultTrans
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error error iface
|
||||||
|
func (e *ValidationError) Error() string {
|
||||||
|
if _, ok := e.rawErr.(*validator.InvalidValidationError); ok {
|
||||||
|
return e.rawErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := e.rawErr.(validator.ValidationErrors)
|
||||||
|
// 只返回单个错误
|
||||||
|
for _, ve := range errs {
|
||||||
|
return ve.Translate(e.getTranslator())
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.rawErr.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struct 通过 validate tag 校验结构体, Validate 校验需要传入指针类型
|
||||||
|
func Struct(ctx context.Context, s any) error {
|
||||||
|
err := validate.StructCtx(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return &ValidationError{ctx: ctx, rawErr: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现了 Validate 接口自定义调用
|
||||||
|
if v, ok := s.(Validator); ok {
|
||||||
|
return v.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tagNameFunc 优先从 json tag 获取名称
|
||||||
|
func tagNameFunc(fld reflect.StructField) string {
|
||||||
|
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]
|
||||||
|
if name == "-" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
validate = validator.New(validator.WithRequiredStructEnabled())
|
||||||
|
validate.RegisterTagNameFunc(tagNameFunc)
|
||||||
|
|
||||||
|
// 默认使用英文
|
||||||
|
en := en.New()
|
||||||
|
zh := zh.New()
|
||||||
|
uni = ut.New(en, en, zh)
|
||||||
|
defaultTrans, _ = uni.GetTranslator("en")
|
||||||
|
lo.Must0(en_translations.RegisterDefaultTranslations(validate, defaultTrans))
|
||||||
|
|
||||||
|
zhTrans, _ = uni.GetTranslator("zh")
|
||||||
|
lo.Must0(zh_translations.RegisterDefaultTranslations(validate, zhTrans))
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testStruct struct {
|
||||||
|
EnvID string `json:"uid" validate:"required"`
|
||||||
|
Force bool `json:"-" validate:"required"`
|
||||||
|
Operator string `validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct2 struct {
|
||||||
|
EnvID string `json:"uid" validate:"required"`
|
||||||
|
Force bool `json:"-"`
|
||||||
|
Operator string `validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStruct3 struct {
|
||||||
|
Name string `json:"name" validate:"required,gt=1"`
|
||||||
|
Count int `json:"count" validate:"required,gt=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testStruct2) Validate() error {
|
||||||
|
return fmt.Errorf("hit validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidate(t *testing.T) {
|
||||||
|
|
||||||
|
d := testStruct{}
|
||||||
|
err := Struct(context.Background(), d)
|
||||||
|
assert.Equal(t, err.Error(), "uid is a required field")
|
||||||
|
|
||||||
|
t2 := &testStruct2{
|
||||||
|
EnvID: "abc",
|
||||||
|
Force: false,
|
||||||
|
Operator: "ab",
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Struct(context.Background(), t2)
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.Equal(t, err.Error(), "hit validate")
|
||||||
|
}
|
||||||
|
|
||||||
|
t3 := testStruct3{
|
||||||
|
Name: "dd_dabc",
|
||||||
|
Count: 1,
|
||||||
|
}
|
||||||
|
err = Struct(context.Background(), t3)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
name := "testValidate"
|
||||||
|
err = Struct(context.Background(), name)
|
||||||
|
assert.Equal(t, err.Error(), "validator: (nil string)")
|
||||||
|
// assert.Equal(t, err.Error(), "Force is required")
|
||||||
|
// assert.Equal(t, err.Error(), "Operator is required")
|
||||||
|
}
|
Loading…
Reference in New Issue