diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml
index 0d2cbd1e0..f3cb3a75c 100644
--- a/.github/workflows/lint-test.yml
+++ b/.github/workflows/lint-test.yml
@@ -12,15 +12,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
-
+ - uses: "actions/checkout@v4"
- name: Set up Go
- uses: actions/setup-go@v2
+ uses: actions/setup-go@v5
with:
- go-version: 1.22
+ go-version-file: 'go.mod'
- name: golangci-lint
- uses: golangci/golangci-lint-action@v2
+ uses: golangci/golangci-lint-action@v8
with:
skip-go-installation: true
args: --timeout 3m --verbose
@@ -30,12 +29,10 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v2
-
- - name: Set up Go
- uses: actions/setup-go@v2
+ uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
with:
- go-version: 1.22
+ go-version-file: 'go.mod'
- name: Collect dependencies
run: |
@@ -46,7 +43,7 @@ jobs:
run: gotestsum --junitfile unit-tests.xml -- -gcflags=-l ./...
- name: Test report
- uses: dorny/test-reporter@v1
+ uses: dorny/test-reporter@v2
if: success() || failure()
with:
name: Test report
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index 986be8ffb..000000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Release
-on:
- push:
- tags: ["v*.*.*"]
-
-jobs:
- release:
- name: Release on GitHub
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
- - name: Set up Go
- uses: actions/setup-go@v5
- with:
- cache-dependency-path: "**/*.sum"
- - name: Run GoReleaser
- uses: goreleaser/goreleaser-action@v5
- with:
- version: latest
- args: release --clean
- env:
- GITHUB_TOKEN: ${{ secrets.TOKEN }}
diff --git a/.gitignore b/.gitignore
index 82d9ddbdd..a2111e00c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,4 +26,4 @@ pkg/example/services/logs
logs
bin
/example/bin
-.proto
\ No newline at end of file
+.proto
diff --git a/.golangci.yaml b/.golangci.yaml
index 133d78670..91afde0d5 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -1,30 +1,44 @@
+version: "2"
+issues:
+ fix: true
linters:
- fast: true
- disable-all: true
+ default: none
+ enable:
+ - govet
+ exclusions:
+ presets:
+ - comments
+ - common-false-positives
+ - legacy
+ - std-error-handling
+ paths:
+ - internal/examples
+ - vendor
+ - third_party$
+ - builtin$
+ - examples$
+formatters:
enable:
-# - bodyclose # checks whether HTTP response body is closed successfully
- - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
- - gosimple # Linter for Go source code that specializes in simplifying a code
- - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
-# - ineffassign # Detects when assignments to existing variables are not used
-# - dogsled # Checks assignments with too many blank identifiers (e.g. x, , , _, := f())
-# - goconst # Finds repeated strings that could be replaced by a constant
- - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
-# - goprintffuncname # Checks that printf-like functions are named with f at the end
-# - nolintlint # Reports ill-formed or insufficient nolint directives
-# - rowserrcheck # checks whether Err of rows is checked successfully
- - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks
-# - unconvert # Remove unnecessary type conversions
-# - unparam # Reports unused function parameters
-# - unused # Checks Go code for unused constants, variables, functions and types
+ - goimports
+ - gofmt
+ - gofumpt
+ settings:
+ gofumpt:
+ extra-rules: true
+ goimports:
+ local-prefixes:
+ - github.com/pubgo/lava/v2
+ gofmt:
+ simplify: false
+ rewrite-rules:
+ - pattern: 'interface{}'
+ replacement: 'any'
+ - pattern: 'a[b:len(a)]'
+ replacement: 'a[b:]'
-issues:
- exclude-dirs:
- - internal/example
- - cmds
- - vendor
- - pkg/proto
- exclude-generated: strict
- exclude-dirs-use-default: true
- exclude-case-sensitive: false
- exclude-use-default: true
+ exclusions:
+ paths:
+ - internal/examples
+ - vendor
+ - examples$
+ - proto
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
deleted file mode 100644
index 27a6a68d0..000000000
--- a/.goreleaser.yaml
+++ /dev/null
@@ -1,26 +0,0 @@
-builds:
- - main: ./cmds/protoc-gen-lava/main.go
- id: protoc-gen-lava
- binary: protoc-gen-lava
- goos:
- - linux
- - darwin
- - windows
- - main: ./main.go
- id: lava
- binary: lava
- env:
- - CGO_ENABLED=0
- goos:
- - linux
- - darwin
- - windows
- ldflags:
- - -X 'github.com/pubgo/lava/version.BuildTime={{ .Date }}'
- - -X 'github.com/pubgo/lava/version.CommitID={{ .Commit }}'
- - -X 'github.com/pubgo/lava/version.Version={{ .Version }}'
-archives:
- - name_template: "{{ .Binary }}-{{ .Tag }}-{{ .Os }}-{{ .Arch }}"
- format: binary
- replacements:
- amd64: x86_64
diff --git a/.version b/.version
new file mode 100644
index 000000000..6eaf89433
--- /dev/null
+++ b/.version
@@ -0,0 +1 @@
+v2.0.0
\ No newline at end of file
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 14a6e8783..000000000
--- a/Makefile
+++ /dev/null
@@ -1,50 +0,0 @@
-WORKDIR=`pwd`
-Project=lava
-Base=github.com/pubgo/funk
-Version=$(shell git tag --sort=committerdate | tail -n 1)
-BuildTime=$(shell date "+%F %T")
-CommitID=$(shell git rev-parse --short=8 HEAD)
-GOPATH=$(shell go env GOPATH )
-
-LDFLAGS=-ldflags " \
--X '${Base}/version.buildTime=${BuildTime}' \
--X '${Base}/version.commitID=${CommitID}' \
--X '${Base}/version.version=${Version:-"v0.0.1-dev"}' \
--X '${Base}/version.project=${Project}' \
-"
-
-.PHONY: test
-test:
- @go test -short -race -v ./... -cover
-
-.PHONY: cover
-cover:
- gocov test -tags "kcp quic" ./... | gocov-html > cover.html
- open cover.html
-
-.PHONY: vet
-vet:
- @go vet ./...
-
-.PHONY: generate
-generate:
- @go generate ./...
-
-.PHONY: deps
-deps:
- # https://github.com/protocolbuffers/protobuf
- @go install -v github.com/tinylib/msgp
- @go install -v github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2
-
-.PHONY: protobuf
-protobuf:
- protobuild vendor
- protobuild gen
-
-.PHONY: lint
-lint:
- golangci-lint --version
- golangci-lint run --timeout 3m --verbose ./...
-
-install-protoc:
- go install -v ./cmds/protoc-gen-lava
diff --git a/README.md b/README.md
index 4d899216e..068d275da 100644
--- a/README.md
+++ b/README.md
@@ -25,3 +25,12 @@
11. 统一protobuf定义grpc和http服务, 便于生成swagger和sdk, 方便第三方调用
12. 专注于业务开发,把额外的组件抽象成一个独立的服务
+
+
+## install
+
+```shell
+brew install go-task/tap/go-task
+brew install go-task
+eval "$(task --completion zsh)"
+```
diff --git a/buildtasks/bench.go b/buildtasks/bench.go
deleted file mode 100644
index ec3993e02..000000000
--- a/buildtasks/bench.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package buildtasks
-
-import "fmt"
-
-type BenchMode int
-
-const (
- BenchModeWazero BenchMode = iota
- BenchModeCGO
- BenchModeDefault
-)
-
-func BenchArgs(pkg string, count int, mode BenchMode, libName string) []string {
- args := []string{"test", "-bench=.", "-run=^$", "-v", "-timeout=60m"}
- if count > 0 {
- args = append(args, fmt.Sprintf("-count=%d", count))
- }
- switch mode {
- case BenchModeCGO:
- args = append(args, fmt.Sprintf("-tags=%s_cgo", libName))
- case BenchModeDefault:
- args = append(args, fmt.Sprintf("-tags=%s_bench_default", libName))
- }
- args = append(args, pkg)
-
- return args
-}
diff --git a/buildtasks/lint.go b/buildtasks/lint.go
deleted file mode 100644
index f08864404..000000000
--- a/buildtasks/lint.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package buildtasks
-
-import (
- "github.com/goyek/goyek/v2"
- "github.com/goyek/x/cmd"
-)
-
-var GoLint = goyek.Define(goyek.Task{
- Name: "go-lint",
- Usage: "golangci-lint run --fix",
- Action: func(a *goyek.A) {
- if !cmd.Exec(a, "go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest") {
- return
- }
- cmd.Exec(a, "golangci-lint run --fix")
- },
-})
diff --git a/buildtasks/util.go b/buildtasks/util.go
deleted file mode 100644
index e519e3d46..000000000
--- a/buildtasks/util.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package buildtasks
-
-import (
- "os/exec"
-
- "github.com/goyek/goyek/v2"
- "github.com/goyek/x/cmd"
-)
-
-// check if docker is installed and running
-func checkDocker(a *goyek.A) bool {
- if !hasBinary("docker") {
- return true
- }
-
- return cmd.Exec(a, "docker ps")
-}
-
-// check if a binary exists
-func hasBinary(binaryName string) bool {
- _, err := exec.LookPath(binaryName)
- return err == nil
-}
diff --git a/clients/grpcc/aaa.go b/clients/grpcc/aaa.go
index 9be9e315f..f353996e1 100644
--- a/clients/grpcc/aaa.go
+++ b/clients/grpcc/aaa.go
@@ -3,13 +3,14 @@ package grpcc
import (
"context"
- "github.com/pubgo/lava/clients/grpcc/grpcc_config"
"google.golang.org/grpc"
+
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig"
)
const Name = "grpcc"
-type Config = grpcc_config.Cfg
+type Config = grpccconfig.Cfg
// Client grpc client interface
type Client interface {
diff --git a/clients/grpcc/client.go b/clients/grpcc/client.go
index 5e548aef5..cb078b41d 100644
--- a/clients/grpcc/client.go
+++ b/clients/grpcc/client.go
@@ -5,18 +5,19 @@ import (
"fmt"
"sync"
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/funk/vars"
- "github.com/pubgo/lava/clients/grpcc/grpcc_config"
- "github.com/pubgo/lava/core/metrics"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/funk/v2/config"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/vars"
"google.golang.org/grpc"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/resolver"
+
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig"
+ "github.com/pubgo/lava/v2/core/metrics"
+ "github.com/pubgo/lava/v2/lava"
)
type Params struct {
@@ -25,8 +26,8 @@ type Params struct {
Resolvers []resolver.Builder
}
-func New(cfg *grpcc_config.Cfg, p Params, middlewares ...lava.Middleware) Client {
- cfg = config.MergeR(grpcc_config.DefaultCfg(), cfg).Unwrap()
+func New(cfg *grpccconfig.Cfg, p Params, middlewares ...lava.Middleware) Client {
+ cfg = config.MergeR(grpccconfig.DefaultCfg(), cfg).Unwrap()
cfg.Resolvers = p.Resolvers
c := &clientImpl{
@@ -35,57 +36,64 @@ func New(cfg *grpcc_config.Cfg, p Params, middlewares ...lava.Middleware) Client
middlewares: middlewares,
}
- vars.RegisterValue(fmt.Sprintf("%s-grpc-client-config", cfg.Service.Name), cfg)
+ vars.Register(fmt.Sprintf("%s-grpc-client-config", cfg.Service.Name), func() any { return cfg })
return c
}
type clientImpl struct {
log log.Logger
- cfg *grpcc_config.Cfg
+ cfg *grpccconfig.Cfg
mu sync.Mutex
conn grpc.ClientConnInterface
middlewares []lava.Middleware
}
-func (t *clientImpl) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) (err error) {
+func (t *clientImpl) Invoke(ctx context.Context, method string, args, reply any, opts ...grpc.CallOption) (err error) {
defer recovery.Err(&err, func(err error) error {
- return errors.WrapTag(err, errors.T("method", method), errors.T("args", args))
+ return errors.WrapTags(err, errors.Tags{"method": method, "args": args})
})
- conn := t.Get()
- if conn.IsErr() {
- return errors.Wrapf(conn.Err(), "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method)
- }
+ conn := t.Get().
+ MapErr(func(err error) error {
+ return errors.Wrapf(err, "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method)
+ }).
+ CallIfOK(func(val grpc.ClientConnInterface) error {
+ return val.Invoke(ctx, method, args, reply, opts...)
+ })
- return conn.Unwrap().Invoke(ctx, method, args, reply, opts...)
+ return conn.Err()
}
func (t *clientImpl) Healthy(ctx context.Context) error {
- conn := t.Get()
- if conn.IsErr() {
- return errors.Wrapf(conn.Err(), "failed to get grpc client, service=%s, method=healthy", t.cfg.Service)
- }
-
- _, err := grpc_health_v1.NewHealthClient(conn.Unwrap()).Check(ctx, &grpc_health_v1.HealthCheckRequest{})
- return errors.Wrapf(err, "service %s heath check failed", t.cfg.Service)
+ return t.Get().
+ MapErr(func(err error) error {
+ return errors.Wrapf(err, "failed to get grpc client, service=%s, method=healthy", t.cfg.Service)
+ }).
+ CallIfOK(func(val grpc.ClientConnInterface) error {
+ _, err := grpc_health_v1.NewHealthClient(val).Check(ctx, &grpc_health_v1.HealthCheckRequest{})
+ return errors.Wrapf(err, "service %s heath check failed", t.cfg.Service)
+ }).
+ Err()
}
func (t *clientImpl) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) {
- conn := t.Get()
- if conn.IsErr() {
- return nil, errors.Wrapf(conn.Err(), "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method)
- }
-
- c, err1 := conn.Unwrap().NewStream(ctx, desc, method, opts...)
- return c, errors.Wrap(err1, method)
+ conn := t.Get().MapErr(func(err error) error {
+ return errors.Wrapf(err, "failed to get grpc client, service=%s, method=%s", t.cfg.Service, method)
+ })
+ return result.FlatMapTo(conn, func(val grpc.ClientConnInterface) (r result.Result[grpc.ClientStream]) {
+ return result.Wrap(val.NewStream(ctx, desc, method, opts...)).
+ MapErr(func(err error) error {
+ return errors.Wrapf(err, "service %s:%s new stream failed", t.cfg.Service, method)
+ })
+ }).UnwrapErr()
}
// Get new grpc client
func (t *clientImpl) Get() (r result.Result[grpc.ClientConnInterface]) {
- defer recovery.Result(&r)
+ defer result.Recovery(&r)
if t.conn != nil {
- return r.WithVal(t.conn)
+ return r.WithValue(t.conn)
}
t.mu.Lock()
@@ -93,14 +101,14 @@ func (t *clientImpl) Get() (r result.Result[grpc.ClientConnInterface]) {
// 双检, 避免多次创建
if t.conn != nil {
- return r.WithVal(t.conn)
+ return r.WithValue(t.conn)
}
- conn, err := createConn(t.cfg, t.log, t.middlewares)
- if err != nil {
- return r.WithErr(err)
+ conn := createConn(t.cfg, t.log, t.middlewares).UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
}
t.conn = conn
- return r.WithVal(t.conn)
+ return r.WithValue(t.conn)
}
diff --git a/clients/grpcc/grpcc_config/config.go b/clients/grpcc/grpccconfig/config.go
similarity index 94%
rename from clients/grpcc/grpcc_config/config.go
rename to clients/grpcc/grpccconfig/config.go
index f2e32a263..56e89f8a9 100644
--- a/clients/grpcc/grpcc_config/config.go
+++ b/clients/grpcc/grpccconfig/config.go
@@ -1,11 +1,12 @@
-package grpcc_config
+package grpccconfig
import (
"time"
- "github.com/pubgo/lava/clients/grpcc/grpcc_resolver"
"google.golang.org/grpc"
"google.golang.org/grpc/resolver"
+
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccresolver"
)
const (
@@ -45,7 +46,7 @@ type ServiceCfg struct {
func DefaultCfg() *Cfg {
cfg := &Cfg{
Service: &ServiceCfg{
- Scheme: grpcc_resolver.DirectScheme,
+ Scheme: grpccresolver.DirectScheme,
},
Client: &GrpcClientCfg{
Insecure: true,
diff --git a/clients/grpcc/grpcc_config/grpc.go b/clients/grpcc/grpccconfig/grpc.go
similarity index 99%
rename from clients/grpcc/grpcc_config/grpc.go
rename to clients/grpcc/grpccconfig/grpc.go
index fb8869c3b..3fe2145ff 100644
--- a/clients/grpcc/grpcc_config/grpc.go
+++ b/clients/grpcc/grpccconfig/grpc.go
@@ -1,4 +1,4 @@
-package grpcc_config
+package grpccconfig
import (
"time"
diff --git a/clients/grpcc/grpcc_lb/p2c/lc.go b/clients/grpcc/grpcclb/p2c/lc.go
similarity index 86%
rename from clients/grpcc/grpcc_lb/p2c/lc.go
rename to clients/grpcc/grpcclb/p2c/lc.go
index 87ff75c91..f57d546b0 100644
--- a/clients/grpcc/grpcc_lb/p2c/lc.go
+++ b/clients/grpcc/grpcclb/p2c/lc.go
@@ -10,7 +10,7 @@ import (
)
type node struct {
- item interface{}
+ item any
loadCount int64
}
@@ -28,11 +28,11 @@ func newP2cAgl() *loadAggregate {
}
}
-func (la *loadAggregate) Add(n interface{}) {
+func (la *loadAggregate) Add(n any) {
la.items = append(la.items, &node{item: n})
}
-func (la *loadAggregate) Next(info balancer.PickInfo) (interface{}, func(info balancer.DoneInfo)) {
+func (la *loadAggregate) Next(info balancer.PickInfo) (any, func(info balancer.DoneInfo)) {
var election, alternative *node
switch len(la.items) {
case 0:
diff --git a/clients/grpcc/grpcc_lb/p2c/p2c.go b/clients/grpcc/grpcclb/p2c/p2c.go
similarity index 92%
rename from clients/grpcc/grpcc_lb/p2c/p2c.go
rename to clients/grpcc/grpcclb/p2c/p2c.go
index 46906894b..a503d74ca 100644
--- a/clients/grpcc/grpcc_lb/p2c/p2c.go
+++ b/clients/grpcc/grpcclb/p2c/p2c.go
@@ -1,7 +1,7 @@
package p2c
import (
- "github.com/pubgo/funk/errors"
+ "github.com/pubgo/funk/v2/errors"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
)
@@ -11,7 +11,7 @@ var _ base.PickerBuilder = (*p2cBalancer)(nil)
type p2cBalancer struct{}
func (p2c *p2cBalancer) Build(info base.PickerBuildInfo) balancer.Picker {
- if info.ReadySCs == nil || len(info.ReadySCs) == 0 {
+ if len(info.ReadySCs) == 0 {
return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
}
diff --git a/clients/grpcc/grpcc_lb/p2c/register.go b/clients/grpcc/grpcclb/p2c/register.go
similarity index 100%
rename from clients/grpcc/grpcc_lb/p2c/register.go
rename to clients/grpcc/grpcclb/p2c/register.go
diff --git a/clients/grpcc/grpcc_resolver/directbuilder.go b/clients/grpcc/grpccresolver/directbuilder.go
similarity index 91%
rename from clients/grpcc/grpcc_resolver/directbuilder.go
rename to clients/grpcc/grpccresolver/directbuilder.go
index b89b30712..29d8cdbf3 100644
--- a/clients/grpcc/grpcc_resolver/directbuilder.go
+++ b/clients/grpcc/grpccresolver/directbuilder.go
@@ -1,12 +1,12 @@
-package grpcc_resolver
+package grpccresolver
import (
"fmt"
"strings"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
"google.golang.org/grpc/resolver"
)
diff --git a/clients/grpcc/grpcc_resolver/discoverybuilder.go b/clients/grpcc/grpccresolver/discoverybuilder.go
similarity index 74%
rename from clients/grpcc/grpcc_resolver/discoverybuilder.go
rename to clients/grpcc/grpccresolver/discoverybuilder.go
index af59b1e2c..71517d704 100644
--- a/clients/grpcc/grpcc_resolver/discoverybuilder.go
+++ b/clients/grpcc/grpccresolver/discoverybuilder.go
@@ -1,23 +1,23 @@
-package grpcc_resolver
+package grpccresolver
import (
"context"
"strings"
"sync"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/async"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/pretty"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/try"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/async"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/pretty"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/try"
"google.golang.org/grpc/resolver"
- "github.com/pubgo/lava/core/discovery"
- "github.com/pubgo/lava/core/service"
- "github.com/pubgo/lava/internal/logutil"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
+ "github.com/pubgo/lava/v2/core/discovery"
+ "github.com/pubgo/lava/v2/core/service"
+ "github.com/pubgo/lava/v2/internal/logutil"
+ "github.com/pubgo/lava/v2/pkg/proto/lavapbv1"
)
func NewDiscoveryBuilder(disco discovery.Discovery) resolver.Builder {
@@ -71,7 +71,7 @@ func (d *discoveryBuilder) updateService(services ...*service.Service) {
// 获取服务地址
func (d *discoveryBuilder) getAddrList(name string) []resolver.Address {
var addrList []resolver.Address
- d.services.Range(func(_, value interface{}) bool {
+ d.services.Range(func(_, value any) bool {
addr := *value.(*resolver.Address)
if addr.ServerName == name {
addrList = append(addrList, *value.(*resolver.Address))
@@ -96,9 +96,14 @@ func (d *discoveryBuilder) Build(target resolver.Target, cc resolver.ClientConn,
// target.Endpoint是服务的名字, 是项目启动的时候注册中心中注册的项目名字
// GetService根据服务名字获取注册中心该项目所有服务
- services := d.disco.GetService(context.Background(), srv).Unwrap(func(err error) error {
- return errors.Wrapf(err, "failed to GetService, srv=%s", srv)
- })
+ services, gErr := d.disco.GetService(context.Background(), srv).
+ MapErr(func(err error) error {
+ return errors.Wrapf(err, "failed to GetService, srv=%s", srv)
+ }).
+ UnwrapErr()
+ if gErr != nil {
+ return nil, gErr
+ }
// 启动后,更新服务地址
d.updateService(services...)
@@ -109,9 +114,13 @@ func (d *discoveryBuilder) Build(target resolver.Target, cc resolver.ClientConn,
logs.Info().Msgf("discovery builder UpdateState, address=%v", address)
assert.MustF(cc.UpdateState(newState(address)), "update resolver address: %v", address)
- w := d.disco.Watch(context.Background(), srv).Unwrap(func(err error) error {
- return errors.Wrapf(err, "target.Endpoint: %s", srv)
- })
+ w, gErr := d.disco.Watch(context.Background(), srv).
+ MapErr(func(err error) error {
+ return errors.Wrapf(err, "target.Endpoint: %s", srv)
+ }).UnwrapErr()
+ if gErr != nil {
+ return nil, gErr
+ }
return &baseResolver{
serviceName: srv,
@@ -122,17 +131,17 @@ func (d *discoveryBuilder) Build(target resolver.Target, cc resolver.ClientConn,
for {
select {
case <-ctx.Done():
- return
+ return gErr
default:
res := w.Next()
if res.IsErr() {
- if errors.Is(res.Err(), discovery.ErrWatcherStopped) {
- return
+ if errors.Is(res.GetErr(), discovery.ErrWatcherStopped) {
+ return gErr
}
- d.log.Err(res.Err(), ctx).Msg("failed to get service watcher event")
+ d.log.Err(res.GetErr(), ctx).Msg("failed to get service watcher event")
- if errors.Is(res.Err(), discovery.ErrTimeout) {
+ if errors.Is(res.GetErr(), discovery.ErrTimeout) {
continue
}
diff --git a/clients/grpcc/grpcc_resolver/endpoint.go b/clients/grpcc/grpccresolver/endpoint.go
similarity index 99%
rename from clients/grpcc/grpcc_resolver/endpoint.go
rename to clients/grpcc/grpccresolver/endpoint.go
index b4ff8013d..6c0ad583f 100644
--- a/clients/grpcc/grpcc_resolver/endpoint.go
+++ b/clients/grpcc/grpccresolver/endpoint.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package grpcc_resolver
+package grpccresolver
import (
"fmt"
diff --git a/clients/grpcc/grpcc_resolver/register.go b/clients/grpcc/grpccresolver/register.go
similarity index 90%
rename from clients/grpcc/grpcc_resolver/register.go
rename to clients/grpcc/grpccresolver/register.go
index 446f39fb1..f392973b2 100644
--- a/clients/grpcc/grpcc_resolver/register.go
+++ b/clients/grpcc/grpccresolver/register.go
@@ -1,4 +1,4 @@
-package grpcc_resolver
+package grpccresolver
import "google.golang.org/grpc/resolver"
diff --git a/clients/grpcc/grpcc_resolver/resolver.go b/clients/grpcc/grpccresolver/resolver.go
similarity index 92%
rename from clients/grpcc/grpcc_resolver/resolver.go
rename to clients/grpcc/grpccresolver/resolver.go
index 502f2189d..4b454dd61 100644
--- a/clients/grpcc/grpcc_resolver/resolver.go
+++ b/clients/grpcc/grpccresolver/resolver.go
@@ -1,9 +1,9 @@
-package grpcc_resolver
+package grpccresolver
import (
"context"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
"google.golang.org/grpc/resolver"
)
diff --git a/clients/grpcc/grpcc_resolver/util.go b/clients/grpcc/grpccresolver/util.go
similarity index 98%
rename from clients/grpcc/grpcc_resolver/util.go
rename to clients/grpcc/grpccresolver/util.go
index ba90e66ec..a2d640277 100644
--- a/clients/grpcc/grpcc_resolver/util.go
+++ b/clients/grpcc/grpccresolver/util.go
@@ -1,4 +1,4 @@
-package grpcc_resolver
+package grpccresolver
import (
"fmt"
diff --git a/clients/grpcc/middleware.go b/clients/grpcc/middleware.go
index aa3140552..a2520b37e 100644
--- a/clients/grpcc/middleware.go
+++ b/clients/grpcc/middleware.go
@@ -5,19 +5,20 @@ import (
"strings"
"time"
- "github.com/pubgo/funk/convert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/strutil"
- "github.com/pubgo/lava/clients/grpcc/grpcc_config"
- "github.com/pubgo/lava/core/lavacontexts"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/grpcutil"
- "github.com/pubgo/lava/pkg/httputil"
+ "github.com/pubgo/funk/v2/convert"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/strutil"
"github.com/rs/xid"
"github.com/valyala/fasthttp"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
+
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig"
+ "github.com/pubgo/lava/v2/core/lavacontexts"
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/grpcutil"
+ "github.com/pubgo/lava/v2/pkg/httputil"
)
func md2Head(md metadata.MD, header interface{ Add(key, value string) }) {
@@ -29,9 +30,9 @@ func md2Head(md metadata.MD, header interface{ Add(key, value string) }) {
}
func head2md(header *lava.RequestHeader, md metadata.MD) {
- header.VisitAll(func(key, value []byte) {
+ for key, value := range header.All() {
md.Append(convert.BtoS(key), convert.BtoS(value))
- })
+ }
}
func unaryInterceptor(middlewares []lava.Middleware) grpc.UnaryClientInterceptor {
@@ -54,7 +55,7 @@ func unaryInterceptor(middlewares []lava.Middleware) grpc.UnaryClientInterceptor
}
unaryWrapper = lava.Chain(middlewares...).Middleware(unaryWrapper)
- return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
+ return func(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = make(metadata.MD)
@@ -66,7 +67,7 @@ func unaryInterceptor(middlewares []lava.Middleware) grpc.UnaryClientInterceptor
}, func() string {
return grpcutil.HeaderGet(md, "x-content-type")
}, func() string {
- return grpcc_config.DefaultContentType
+ return grpccconfig.DefaultContentType
})
delete(md, "x-content-type")
@@ -145,7 +146,7 @@ func streamInterceptor(middlewares []lava.Middleware) grpc.StreamClientIntercept
}, func() string {
return grpcutil.HeaderGet(md, "x-content-type")
}, func() string {
- return grpcc_config.DefaultContentType
+ return grpccconfig.DefaultContentType
})
delete(md, "x-content-type")
diff --git a/clients/grpcc/request.go b/clients/grpcc/request.go
index 8b73d31da..632091f38 100644
--- a/clients/grpcc/request.go
+++ b/clients/grpcc/request.go
@@ -3,19 +3,19 @@ package grpcc
import (
"google.golang.org/grpc"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/lava"
)
var _ lava.Request = (*request)(nil)
type request struct {
resp grpc.ClientStream
- reply interface{}
+ reply any
ct string
opts []grpc.CallOption
method string
service string
- req interface{}
+ req any
cc *grpc.ClientConn
invoker grpc.UnaryInvoker
streamer grpc.Streamer
@@ -30,5 +30,5 @@ func (r *request) Service() string { return r.service }
func (r *request) Endpoint() string { return r.method }
func (r *request) ContentType() string { return r.ct }
func (r *request) Header() *lava.RequestHeader { return r.header }
-func (r *request) Payload() interface{} { return r.req }
+func (r *request) Payload() any { return r.req }
func (r *request) Stream() bool { return r.desc != nil }
diff --git a/clients/grpcc/response.go b/clients/grpcc/response.go
index fe30c2d3b..755b2ff90 100644
--- a/clients/grpcc/response.go
+++ b/clients/grpcc/response.go
@@ -3,7 +3,7 @@ package grpcc
import (
"google.golang.org/grpc"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/lava"
)
var _ lava.Response = (*response)(nil)
@@ -11,9 +11,9 @@ var _ lava.Response = (*response)(nil)
type response struct {
header *lava.ResponseHeader
stream grpc.ClientStream
- resp interface{}
+ resp any
}
func (r *response) Stream() bool { return r.stream != nil }
func (r *response) Header() *lava.ResponseHeader { return r.header }
-func (r *response) Payload() interface{} { return r.resp }
+func (r *response) Payload() any { return r.resp }
diff --git a/clients/grpcc/util.go b/clients/grpcc/util.go
index 1db160b3c..e93ec4b18 100644
--- a/clients/grpcc/util.go
+++ b/clients/grpcc/util.go
@@ -3,64 +3,65 @@ package grpcc
import (
"fmt"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/lava/clients/grpcc/grpcc_config"
- "github.com/pubgo/lava/clients/grpcc/grpcc_resolver"
- "github.com/pubgo/lava/core/logging/logkey"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/result"
"github.com/rs/zerolog"
"google.golang.org/grpc"
+
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig"
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccresolver"
+ "github.com/pubgo/lava/v2/core/logging/logkey"
+ "github.com/pubgo/lava/v2/lava"
)
-func buildTarget(cfg *grpcc_config.ServiceCfg) string {
+func buildTarget(cfg *grpccconfig.ServiceCfg) string {
addr := cfg.Addr
- scheme := grpcc_resolver.DirectScheme
+ scheme := grpccresolver.DirectScheme
if cfg.Scheme != "" {
scheme = cfg.Scheme
}
switch scheme {
- case grpcc_resolver.DiscoveryScheme:
- return grpcc_resolver.BuildDiscoveryTarget(addr)
- case grpcc_resolver.DirectScheme:
- return grpcc_resolver.BuildDirectTarget(cfg.Name, addr)
- case grpcc_resolver.K8sScheme, grpcc_resolver.DnsScheme:
+ case grpccresolver.DiscoveryScheme:
+ return grpccresolver.BuildDiscoveryTarget(addr)
+ case grpccresolver.DirectScheme:
+ return grpccresolver.BuildDirectTarget(cfg.Name, addr)
+ case grpccresolver.K8sScheme, grpccresolver.DnsScheme:
return fmt.Sprintf("dns:///%s", addr)
default:
return addr
}
}
-func createConn(cfg *grpcc_config.Cfg, log log.Logger, mm []lava.Middleware) (_ grpc.ClientConnInterface, gErr error) {
+func createConn(cfg *grpccconfig.Cfg, log log.Logger, mm []lava.Middleware) (r result.Result[grpc.ClientConnInterface]) {
addr := buildTarget(cfg.Service)
- var logMsg = func(e *zerolog.Event) {
+ logMsg := func(e *zerolog.Event) {
e.Any(logkey.Service, cfg.Service)
e.Any("config", cfg.Client)
e.Str("addr", addr)
}
defer func() {
- if gErr == nil {
+ if r.IsOK() {
log.Info().
Func(logMsg).Msg("succeed to create grpc client")
} else {
- log.Err(gErr).
+ log.Err(r.GetErr()).
Func(logMsg).Msg("failed to create grpc client")
}
}()
- opts := append(
- cfg.Client.ToOpts(),
- grpc.WithResolvers(cfg.Resolvers...),
- grpc.WithChainUnaryInterceptor(unaryInterceptor(mm)),
- grpc.WithChainStreamInterceptor(streamInterceptor(mm)),
- )
+ opts := cfg.Client.ToOpts()
+ opts = append(opts, grpc.WithResolvers(cfg.Resolvers...))
+ opts = append(opts, grpc.WithChainUnaryInterceptor(unaryInterceptor(mm)))
+ opts = append(opts, grpc.WithChainStreamInterceptor(streamInterceptor(mm)))
+
conn, err := grpc.NewClient(addr, opts...)
if err != nil {
- return nil, errors.Wrapf(err, "grpc dial failed, target=>%s", addr)
+ return r.WithErr(errors.Wrapf(err, "failed to dial grpc server, target=%s", addr))
}
- return conn, nil
+ return r.WithValue(conn)
}
diff --git a/clients/resty/aaa.go b/clients/resty/aaa.go
index 1256367f6..0cc70cf58 100644
--- a/clients/resty/aaa.go
+++ b/clients/resty/aaa.go
@@ -4,7 +4,7 @@ import (
"context"
"time"
- "github.com/pubgo/funk/result"
+ "github.com/pubgo/funk/v2/result"
"github.com/valyala/fasthttp"
)
diff --git a/clients/resty/client.go b/clients/resty/client.go
index c9d5d01f9..6d0eb03c7 100644
--- a/clients/resty/client.go
+++ b/clients/resty/client.go
@@ -5,20 +5,19 @@ import (
"net/url"
"sync"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/funk/retry"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/config"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/retry"
"github.com/valyala/fasthttp"
- "github.com/pubgo/lava/core/metrics"
- "github.com/pubgo/lava/internal/middlewares/middleware_accesslog"
- "github.com/pubgo/lava/internal/middlewares/middleware_metric"
- "github.com/pubgo/lava/internal/middlewares/middleware_recovery"
- "github.com/pubgo/lava/internal/middlewares/middleware_service_info"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/core/metrics"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_accesslog"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_metric"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_recovery"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_serviceinfo"
+ "github.com/pubgo/lava/v2/lava"
)
type Params struct {
@@ -29,9 +28,9 @@ type Params struct {
func New(cfg *Config, p Params, mm ...lava.Middleware) *Client {
cfg = config.MergeR(DefaultCfg(), cfg).Unwrap()
middlewares := lava.Middlewares{
- middleware_service_info.New(),
+ middleware_serviceinfo.New(),
middleware_metric.New(p.Metric),
- middleware_accesslog.New(p.Log.WithFields(log.Map{"service": cfg.ServiceName})),
+ middleware_accesslog.New(p.Log.WithFields(log.Fields{"service": cfg.ServiceName})),
middleware_recovery.New(),
}
middlewares = append(middlewares, mm...)
@@ -45,8 +44,10 @@ func New(cfg *Config, p Params, mm ...lava.Middleware) *Client {
backoff = retry.WithMaxRetries(cfg.DefaultRetryCount, backoff)
}
+ handler := do(cfg)
+ handler = lava.Chain(middlewares...).Middleware(handler)
return &Client{
- do: lava.Chain(middlewares...).Middleware(do(cfg)),
+ do: handler,
log: p.Log,
cfg: cfg,
baseUrl: assert.Must1(url.Parse(cfg.BaseUrl)),
@@ -67,20 +68,17 @@ type Client struct {
}
func (c *Client) Do(ctx context.Context, req *Request) (r result.Result[*fasthttp.Response]) {
- defer recovery.Result(&r)
+ defer result.Recovery(&r)
- reqErr := doRequest(c, req)
- if reqErr.IsErr() {
- return r.WithErr(reqErr.Err())
+ if doRequest(c, req).ValueTo(&req.req).Throw(&r) {
+ return r
}
- req.req = reqErr.Unwrap()
-
request := &requestImpl{service: c.cfg.ServiceName, req: req}
resp, err := c.do(ctx, request)
if err != nil {
return r.WithErr(err)
}
- return r.WithVal(resp.(*responseImpl).resp)
+ return r.WithValue(resp.(*responseImpl).resp)
}
diff --git a/clients/resty/config.go b/clients/resty/config.go
index 2b818cef7..4a9a7e63f 100644
--- a/clients/resty/config.go
+++ b/clients/resty/config.go
@@ -5,7 +5,7 @@ import (
"net"
"time"
- "github.com/pubgo/funk/version"
+ "github.com/pubgo/funk/v2/buildinfo/version"
"github.com/valyala/fasthttp"
"github.com/valyala/fasthttp/fasthttpproxy"
"golang.org/x/net/http/httpproxy"
diff --git a/clients/resty/jar.go b/clients/resty/jar.go
index c3d6c421a..abd62c9bb 100644
--- a/clients/resty/jar.go
+++ b/clients/resty/jar.go
@@ -8,10 +8,10 @@ import (
"time"
"github.com/goccy/go-json"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
"github.com/valyala/fasthttp"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/lava"
)
func NewJar(log log.Logger) *Jar {
@@ -41,14 +41,14 @@ func (j *Jar) Middleware(next lava.HandlerFunc) lava.HandlerFunc {
return nil, err
}
- rsp.Header().VisitAllCookie(func(key, value []byte) {
+ for _, value := range rsp.Header().All() {
acquireCookie := fasthttp.AcquireCookie()
if err := acquireCookie.ParseBytes(value); err != nil {
j.log.Err(err, ctx).Msg("failed to parse cookie")
} else {
j.cookies[string(acquireCookie.Key())] = acquireCookie
}
- })
+ }
return rsp, err
}
diff --git a/clients/resty/middleware.go b/clients/resty/middleware.go
index 69fefa30d..52ed25ed9 100644
--- a/clients/resty/middleware.go
+++ b/clients/resty/middleware.go
@@ -4,10 +4,10 @@ import (
"fmt"
"github.com/gofiber/utils"
- "github.com/pubgo/funk/convert"
+ "github.com/pubgo/funk/v2/convert"
"github.com/valyala/fasthttp"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/lava"
)
var _ lava.Request = (*requestImpl)(nil)
@@ -27,7 +27,7 @@ func (r *requestImpl) Service() string { return r.service }
func (r *requestImpl) Endpoint() string { return utils.UnsafeString(r.req.req.URI().Path()) }
func (r *requestImpl) ContentType() string { return convert.B2S(r.req.req.Header.ContentType()) }
func (r *requestImpl) Header() *lava.RequestHeader { return &r.req.req.Header }
-func (r *requestImpl) Payload() interface{} { return r.req.req.Body() }
+func (r *requestImpl) Payload() any { return r.req.req.Body() }
func (r *requestImpl) Stream() bool { return false }
var _ lava.Response = (*responseImpl)(nil)
@@ -37,5 +37,5 @@ type responseImpl struct {
}
func (r *responseImpl) Header() *lava.ResponseHeader { return &r.resp.Header }
-func (r *responseImpl) Payload() interface{} { return r.resp.Body() }
+func (r *responseImpl) Payload() any { return r.resp.Body() }
func (r *responseImpl) Stream() bool { return false }
diff --git a/clients/resty/request.go b/clients/resty/request.go
index 07ab27b6f..d0dd32412 100644
--- a/clients/resty/request.go
+++ b/clients/resty/request.go
@@ -5,13 +5,13 @@ import (
"net/url"
"regexp"
- "github.com/pubgo/funk/retry"
+ "github.com/pubgo/funk/v2/retry"
"github.com/valyala/fasthttp"
)
var regParam = regexp.MustCompile(`{.+}`)
-type RequestConfig struct {
+type RequestSpec struct {
Header map[string]string
Path string
Method string
@@ -20,14 +20,21 @@ type RequestConfig struct {
EnableAuth bool
}
-func NewRequest(cfg *RequestConfig) *Request {
- r := &Request{cfg: cfg}
+func (r RequestSpec) CreateRequest() *Request { return NewRequest(&r) }
+
+func NewRequest(cfg *RequestSpec) *Request {
+ r := &Request{
+ cfg: cfg,
+ header: make(http.Header),
+ query: make(url.Values),
+ params: make(map[string]any),
+ }
return r
}
type Request struct {
req *fasthttp.Request
- cfg *RequestConfig
+ cfg *RequestSpec
header http.Header
query url.Values
params map[string]any
diff --git a/clients/resty/util.go b/clients/resty/util.go
index 5c098666e..1d1eb2ae2 100644
--- a/clients/resty/util.go
+++ b/clients/resty/util.go
@@ -12,16 +12,15 @@ import (
"strings"
"github.com/goccy/go-json"
- "github.com/pubgo/funk/convert"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/funk/retry"
- "github.com/valyala/bytebufferpool"
+ "github.com/pubgo/funk/v2/convert"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/retry"
"github.com/valyala/fasthttp"
"github.com/valyala/fasttemplate"
"golang.org/x/net/http/httpguts"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/httputil"
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/httputil"
)
func do(cfg *Config) lava.HandlerFunc {
@@ -58,16 +57,16 @@ func do(cfg *Config) lava.HandlerFunc {
}
}
-func getBodyReader(rawBody interface{}) ([]byte, error) {
+func getBodyReader(rawBody any) (r result.Result[[]byte]) {
switch body := rawBody.(type) {
case nil:
- return nil, nil
+ return r
case *bytes.Buffer:
- return body.Bytes(), nil
+ return r.WithValue(body.Bytes())
case []byte:
- return body, nil
+ return r.WithValue(body)
case string:
- return convert.StoB(body), nil
+ return r.WithValue(convert.StoB(body))
// We prioritize *bytes.Reader here because we don't really want to
// deal with it seeking so want it to match here instead of the
@@ -75,46 +74,39 @@ func getBodyReader(rawBody interface{}) ([]byte, error) {
case *bytes.Reader:
buf, err := io.ReadAll(body)
if err != nil {
- return nil, err
+ return r.WithErr(err)
}
- return buf, nil
+ return r.WithValue(buf)
// Compat case
case io.ReadSeeker:
_, err := body.Seek(0, 0)
if err != nil {
- return nil, err
+ return r.WithErr(err)
}
buf, err := io.ReadAll(body)
if err != nil {
- return nil, err
+ return r.WithErr(err)
}
- return buf, nil
+ return r.WithValue(buf)
case url.Values:
- return convert.StoB(body.Encode()), nil
+ return r.WithValue(convert.StoB(body.Encode()))
// Read all in so we can reset
case io.Reader:
buf, err := io.ReadAll(body)
if err != nil {
- return nil, err
+ return r.WithErr(err)
}
- return buf, nil
+ return r.WithValue(buf)
case json.Marshaler:
- return body.MarshalJSON()
+ return result.Wrap(body.MarshalJSON())
default:
- bb := bytebufferpool.Get()
- defer bytebufferpool.Put(bb)
-
- if err := json.NewEncoder(bb).Encode(rawBody); err != nil {
- return nil, err
- }
-
- return bb.Bytes(), nil
+ return result.Wrap(json.Marshal(rawBody))
}
}
@@ -134,36 +126,30 @@ func handleHeader(c *Client, req *Request) {
}
}
-func handlePath(c *Client, req *Request) (path string, err error) {
+func handlePath(c *Client, req *Request) (r result.Result[string]) {
reqConf := req.cfg
reqUrl := c.baseUrl.JoinPath(reqConf.Path)
req.operation = reqUrl.Path
- path = reqUrl.Path
- if v, ok := c.pathTemplates.Load(reqUrl.Path); ok {
- if v != nil {
- path, err = pathTemplateRun(v.(*fasttemplate.Template), req.params)
- if err != nil {
- return
- }
- }
+ if v, ok := c.pathTemplates.Load(reqUrl.Path); ok && v != nil {
+ return result.Wrap(pathTemplateRun(v.(*fasttemplate.Template), req.params))
} else {
if regParam.MatchString(reqUrl.Path) {
pathTemplate, err := fasttemplate.NewTemplate(reqUrl.Path, "{", "}")
if err != nil {
- return "", err
+ return r.WithErr(err)
}
c.pathTemplates.Store(reqUrl.Path, pathTemplate)
} else {
- c.pathTemplates.Store(reqUrl.Path, nil)
+ return r.WithValue(reqUrl.Path)
}
}
- return
+ return r
}
-func handleContentType(c *Client, req *Request) (string, error) {
+func handleContentType(c *Client, req *Request) (r result.Result[string]) {
defaultConf := c.cfg
reqConf := req.cfg
@@ -181,27 +167,23 @@ func handleContentType(c *Client, req *Request) (string, error) {
}
if contentType == "" {
- return "", errors.New("context-type header is empty")
+ return r.WithErr(errors.New("content-type header is empty"))
}
- return contentType, nil
+ return r.WithValue(contentType)
}
// doRequest data:[bytes|string|map|struct]
func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) {
r := fasthttp.AcquireRequest()
- ct, err := handleContentType(c, req)
- if err != nil {
- return rsp.WithErr(err)
+ if handleContentType(c, req).
+ IfOK(func(val string) {
+ r.Header.Set(httputil.HeaderContentType, val)
+ }).
+ Throw(&rsp) {
+ return rsp
}
- r.Header.Set(httputil.HeaderContentType, ct)
-
- path, err := handlePath(c, req)
- if err != nil {
- return rsp.WithErr(err)
- }
- r.SetRequestURI(path)
mth := req.cfg.Method
if mth == "" {
@@ -210,11 +192,13 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) {
r.Header.SetMethod(mth)
- bodyRaw, err := getBodyReader(req.body)
- if err != nil {
- return rsp.WithErr(err)
+ if getBodyReader(req.body).
+ IfOK(func(val []byte) {
+ r.SetBodyRaw(val)
+ }).
+ Throw(&rsp) {
+ return rsp
}
- r.SetBodyRaw(bodyRaw)
handleHeader(c, req)
@@ -227,11 +211,11 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) {
// enable auth
if c.cfg.EnableAuth || req.cfg.EnableAuth {
if c.cfg.BasicToken != "" {
- r.Header.Set("Authentication", "Basic "+c.cfg.BasicToken)
+ r.Header.Set(httputil.HeaderAuthorization, "Basic "+c.cfg.BasicToken)
}
if c.cfg.JwtToken != "" {
- r.Header.Set("Authentication", "Bearer "+c.cfg.JwtToken)
+ r.Header.Set(httputil.HeaderAuthorization, "Bearer "+c.cfg.JwtToken)
}
}
@@ -239,7 +223,14 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) {
defer fasthttp.ReleaseURI(uri)
uri.SetScheme(c.baseUrl.Scheme)
uri.SetHost(c.baseUrl.Host)
- uri.SetPath(path)
+ if handlePath(c, req).
+ IfOK(func(val string) {
+ uri.SetPath(val)
+ }).
+ Throw(&rsp) {
+ return rsp
+ }
+
if req.query != nil {
uri.SetQueryString(req.query.Encode())
}
@@ -255,7 +246,7 @@ func doRequest(c *Client, req *Request) (rsp result.Result[*fasthttp.Request]) {
}
}
- return rsp.WithVal(r)
+ return rsp.WithValue(r)
}
func filterFlags(content string) string {
diff --git a/cmds/app/cmd.go b/cmds/app/cmd.go
deleted file mode 100644
index 6ff12195b..000000000
--- a/cmds/app/cmd.go
+++ /dev/null
@@ -1,100 +0,0 @@
-package app
-
-import (
- "os"
- "sort"
-
- "github.com/pubgo/dix"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/version"
- cli "github.com/urfave/cli/v3"
-
- "github.com/pubgo/lava/clients/grpcc/grpcc_resolver"
- "github.com/pubgo/lava/cmds/depcmd"
- "github.com/pubgo/lava/cmds/grpcservercmd"
- "github.com/pubgo/lava/cmds/healthcmd"
- "github.com/pubgo/lava/cmds/httpservercmd"
- "github.com/pubgo/lava/cmds/versioncmd"
- "github.com/pubgo/lava/core/discovery"
- "github.com/pubgo/lava/core/flags"
- "github.com/pubgo/lava/core/lifecycle"
- "github.com/pubgo/lava/core/logging"
- "github.com/pubgo/lava/core/metrics"
- "github.com/pubgo/lava/core/scheduler"
- "github.com/pubgo/lava/core/signal"
- "github.com/pubgo/lava/internal/middlewares/middleware_accesslog"
- "github.com/pubgo/lava/internal/middlewares/middleware_metric"
- "github.com/pubgo/lava/pkg/cmdutil"
-
- _ "github.com/pubgo/lava/core/debug/debug"
- // debug
- _ "github.com/pubgo/lava/core/debug/pprof"
- _ "github.com/pubgo/lava/core/debug/process"
- _ "github.com/pubgo/lava/core/debug/statsviz"
- _ "github.com/pubgo/lava/core/debug/trace"
- _ "github.com/pubgo/lava/core/debug/vars"
- _ "github.com/pubgo/lava/core/debug/version"
-
- // metric
- _ "github.com/pubgo/lava/core/metrics/drivers/prometheus"
-
- // encoding
- _ "github.com/pubgo/lava/core/encoding/protobuf"
- _ "github.com/pubgo/lava/core/encoding/protojson"
-
- // logging
- _ "github.com/pubgo/lava/core/logging/logext/grpclog"
- _ "github.com/pubgo/lava/core/logging/logext/stdlog"
-
- _ "go.uber.org/automaxprocs"
-)
-
-var defaultProviders = []any{
- grpcc_resolver.NewDirectBuilder,
- grpcc_resolver.NewDiscoveryBuilder,
- discovery.NewNoopDiscovery,
-
- middleware_accesslog.New,
- middleware_metric.New,
- logging.New,
- metrics.New,
-
- lifecycle.New,
- scheduler.New,
-}
-
-func NewBuilder(opts ...dix.Option) *dix.Dix {
- di := dix.New(append(opts, dix.WithValuesNull())...)
- for _, p := range defaultProviders {
- di.Provide(p)
- }
- return di
-}
-
-func Run(di *dix.Dix) {
- defer recovery.Exit()
-
- di.Provide(versioncmd.New)
- di.Provide(healthcmd.New)
- di.Provide(depcmd.New)
- di.Provide(grpcservercmd.New)
- di.Provide(httpservercmd.New)
-
- di.Inject(func(cmd []*cli.Command) {
- app := &cli.Command{
- Name: version.Project(),
- Suggest: true,
- UseShortOptionHandling: true,
- Usage: cmdutil.UsageDesc("%s service", version.Project()),
- Version: version.Version(),
- Flags: flags.GetFlags(),
- Commands: cmd,
- ExtraInfo: running.GetSysInfo,
- }
-
- sort.Sort(cli.FlagsByName(app.Flags))
- assert.Must(app.Run(signal.Context(), os.Args))
- })
-}
diff --git a/cmds/depcmd/cmd.go b/cmds/depcmd/cmd.go
index bdc7a1557..ee88a40ba 100644
--- a/cmds/depcmd/cmd.go
+++ b/cmds/depcmd/cmd.go
@@ -7,21 +7,22 @@ import (
"runtime/debug"
"github.com/olekukonko/tablewriter"
- "github.com/pubgo/dix"
- "github.com/pubgo/funk/pretty"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/version"
+ "github.com/pubgo/dix/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/pretty"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/running"
cli "github.com/urfave/cli/v3"
- "github.com/pubgo/lava/pkg/cmdutil"
+ "github.com/pubgo/lava/v2/pkg/cliutil"
)
func New(di *dix.Dix) *cli.Command {
return &cli.Command{
Name: "dep",
Usage: "Print the dependency package information",
- Description: cmdutil.ExampleFmt(
+ Description: cliutil.ExampleDesc(
"lava dep",
"lava dep json",
"lava dep t"),
@@ -45,16 +46,13 @@ func New(di *dix.Dix) *cli.Command {
pretty.Println(running.GetSysInfo())
case "table", "tb", "t":
table := tablewriter.NewWriter(os.Stdout)
- table.SetHeader([]string{"path", "Version", "Replace"})
- table.Append([]string{info.Main.Path, version.Version(), replace(info.Main.Replace)})
+ table.Header([]string{"path", "Version", "Replace"})
+ assert.Must(table.Append([]string{info.Main.Path, version.Version(), replace(info.Main.Replace)}))
for _, dep := range info.Deps {
- table.Append([]string{dep.Path, dep.Version, replace(dep.Replace)})
+ assert.Must(table.Append([]string{dep.Path, dep.Version, replace(dep.Replace)}))
}
- table.Render()
- case "di":
- fmt.Println(di.Graph().Objects)
- fmt.Println(di.Graph().Providers)
+ assert.Must(table.Render())
}
return nil
},
diff --git a/cmds/fileservercmd/cmd.go b/cmds/fileservercmd/cmd.go
new file mode 100644
index 000000000..360b76f4a
--- /dev/null
+++ b/cmds/fileservercmd/cmd.go
@@ -0,0 +1,54 @@
+package fileservercmd
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/running"
+ cli "github.com/urfave/cli/v3"
+ "github.com/valyala/fasthttp"
+)
+
+func New() *cli.Command {
+ return &cli.Command{
+ Name: "fileserver",
+ Usage: "serve `pwd` via http at *:8080",
+ Flags: []cli.Flag{},
+ ArgsUsage: "[dir]",
+ Action: func(ctx context.Context, command *cli.Command) error {
+ defer recovery.Exit()
+
+ wd := result.Wrap(os.Getwd()).Unwrap()
+ if command.Args().Len() > 0 {
+ wd = command.Args().Get(0)
+ }
+
+ port := running.HttpPort()
+ log.Info().Msgf("file dir: %s", wd)
+ log.Info().Msgf("http://localhost:%v", port)
+
+ fs := &fasthttp.FS{
+ Root: wd,
+ IndexNames: []string{"index.html"},
+ GenerateIndexPages: true,
+ Compress: false,
+ AcceptByteRange: true,
+ // PathRewrite: fasthttp.NewVHostPathRewriter(0),
+ }
+
+ s := &fasthttp.Server{
+ Handler: fs.NewRequestHandler(),
+ Logger: log.NewStd(log.GetLogger("fileserver")),
+ }
+ go func() { assert.Must(s.ListenAndServe(fmt.Sprintf(":%v", port))) }()
+
+ <-ctx.Done()
+ return s.ShutdownWithContext(ctx)
+ },
+ }
+}
diff --git a/cmds/gencmd/main.go b/cmds/gencmd/main.go
index 0128deecc..29e6c27e4 100644
--- a/cmds/gencmd/main.go
+++ b/cmds/gencmd/main.go
@@ -6,7 +6,7 @@ import (
"github.com/urfave/cli/v3"
- "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/funk/v2/recovery"
)
func New() *cli.Command {
diff --git a/cmds/grpcservercmd/cmd.go b/cmds/grpcservercmd/cmd.go
index ca9a0373d..781d64d0b 100644
--- a/cmds/grpcservercmd/cmd.go
+++ b/cmds/grpcservercmd/cmd.go
@@ -2,23 +2,35 @@ package grpcservercmd
import (
"context"
- "github.com/pubgo/dix"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/pkg/cmdutil"
- "github.com/pubgo/lava/servers/grpcs"
+
+ "github.com/pubgo/dix/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
"github.com/urfave/cli/v3"
+
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/supervisor"
+ "github.com/pubgo/lava/v2/pkg/cliutil"
+ "github.com/pubgo/lava/v2/servers/grpcs"
)
func New(di *dix.Dix) *cli.Command {
return &cli.Command{
Name: "grpc",
- Usage: cmdutil.UsageDesc("%s grpc service", version.Project()),
+ Usage: cliutil.UsageDesc("grpc service %s(%s)", version.Project(), version.Version()),
Action: func(ctx context.Context, command *cli.Command) error {
- defer recovery.Exit()
- srv := dix.Inject(di, grpcs.New())
- srv.Run()
- return nil
+ di.Provide(grpcs.New)
+ params := dix.Inject(di, new(struct {
+ LC lifecycle.Getter
+ Services []supervisor.Service
+ }))
+
+ manager := supervisor.Default(params.LC)
+ for _, svc := range params.Services {
+ assert.Exit(manager.Add(svc))
+ }
+
+ return manager.Run(ctx)
},
}
}
diff --git a/cmds/healthcmd/cmd.go b/cmds/healthcmd/cmd.go
index 5e364d898..56ab2fa9f 100644
--- a/cmds/healthcmd/cmd.go
+++ b/cmds/healthcmd/cmd.go
@@ -7,20 +7,20 @@ import (
"net/http"
"os"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/version"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/recovery"
"github.com/urfave/cli/v3"
- "github.com/pubgo/lava/pkg/cmdutil"
- "github.com/pubgo/lava/pkg/netutil"
+ "github.com/pubgo/lava/v2/pkg/cliutil"
+ "github.com/pubgo/lava/v2/pkg/netutil"
)
func New() *cli.Command {
return &cli.Command{
Name: "health",
- Usage: cmdutil.UsageDesc("%s health check", version.Project()),
- Description: cmdutil.ExampleFmt(
+ Usage: cliutil.UsageDesc("%s health check", version.Project()),
+ Description: cliutil.ExampleDesc(
"lava health",
"lava health localhost:8080",
),
diff --git a/cmds/httpservercmd/cmd.go b/cmds/httpservercmd/cmd.go
index 896ba2d23..2d169f5a4 100644
--- a/cmds/httpservercmd/cmd.go
+++ b/cmds/httpservercmd/cmd.go
@@ -2,23 +2,35 @@ package httpservercmd
import (
"context"
- "github.com/pubgo/dix"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/pkg/cmdutil"
- "github.com/pubgo/lava/servers/https"
+
+ "github.com/pubgo/dix/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
"github.com/urfave/cli/v3"
+
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/supervisor"
+ "github.com/pubgo/lava/v2/pkg/cliutil"
+ "github.com/pubgo/lava/v2/servers/https"
)
func New(di *dix.Dix) *cli.Command {
return &cli.Command{
Name: "http",
- Usage: cmdutil.UsageDesc("%s http service", version.Project()),
+ Usage: cliutil.UsageDesc("%s http service", version.Project()),
Action: func(ctx context.Context, command *cli.Command) error {
- defer recovery.Exit()
- srv := dix.Inject(di, https.New())
- srv.Run()
- return nil
+ di.Provide(https.New)
+ params := dix.Inject(di, new(struct {
+ LC lifecycle.Getter
+ Services []supervisor.Service
+ }))
+
+ manager := supervisor.Default(params.LC)
+ for _, svc := range params.Services {
+ assert.Exit(manager.Add(svc))
+ }
+
+ return manager.Run(ctx)
},
}
}
diff --git a/cmds/lava/main.go b/cmds/lava/main.go
deleted file mode 100644
index 660cd81c5..000000000
--- a/cmds/lava/main.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package main
-
-func main() {
- // https://github.com/beego/bee
-}
diff --git a/cmds/protoc-gen-lava/internal/gen.go b/cmds/protoc-gen-lava/internal/gen.go
deleted file mode 100644
index 81fe1d0da..000000000
--- a/cmds/protoc-gen-lava/internal/gen.go
+++ /dev/null
@@ -1,105 +0,0 @@
-package internal
-
-import (
- "fmt"
- "reflect"
- "strings"
-
- "github.com/dave/jennifer/jen"
- "github.com/pubgo/lava/core/rpcmeta"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
- "google.golang.org/protobuf/compiler/protogen"
- "google.golang.org/protobuf/proto"
-)
-
-var rpcMetaPkg = reflect.TypeOf(rpcmeta.RpcMeta{}).PkgPath()
-
-type rpcMetaInfo struct {
- srv *protogen.Service
- mth *protogen.Method
- meta *lavapbv1.RpcMeta
-}
-
-// GenerateFile generates a .errors.pb.go file containing service definitions.
-func GenerateFile(gen *protogen.Plugin, file *protogen.File) (g *protogen.GeneratedFile) {
- filename := file.GeneratedFilenamePrefix + ".lava.pb.go"
- genFile := jen.NewFile(string(file.GoPackageName))
- genFile.HeaderComment("Code generated by protoc-gen-lava. DO NOT EDIT.")
- genFile.HeaderComment("versions:")
- genFile.HeaderComment(fmt.Sprintf(" - protoc-gen-lava %s", Version))
- genFile.HeaderComment(fmt.Sprintf(" - protoc %s", protocVersion(gen)))
- if file.Proto.GetOptions().GetDeprecated() {
- genFile.HeaderComment(fmt.Sprintf("%s is a deprecated file.", file.Desc.Path()))
- } else {
- genFile.HeaderComment(fmt.Sprintf("source: %s", file.Desc.Path()))
- }
-
- g = gen.NewGeneratedFile(filename, file.GoImportPath)
- g.Skip()
-
- if len(file.Services) == 0 {
- return g
- }
-
- var rpcMetas = make(map[string]*rpcMetaInfo)
- for _, srv := range file.Services {
- for _, m := range srv.Methods {
- rpcMeta, ok := proto.GetExtension(m.Desc.Options(), lavapbv1.E_Options).(*lavapbv1.RpcMeta)
- if !ok || rpcMeta == nil {
- continue
- }
-
- if rpcMeta.Name == "" {
- gen.Error(fmt.Errorf("rpc meta name is null, method=%s", m.GoName))
- return
- }
-
- if rpcMetas[rpcMeta.Name] != nil {
- gen.Error(fmt.Errorf("rpc meta name:%s already exists, method=%s", rpcMeta.Name, m.GoName))
- return
- }
-
- rpcMetas[rpcMeta.Name] = &rpcMetaInfo{srv: srv, mth: m, meta: rpcMeta}
- }
- }
-
- if len(rpcMetas) == 0 {
- return g
- }
-
- g.Unskip()
- for _, meta := range rpcMetas {
- srvInfo := meta.srv
- keyPrefix := strings.ReplaceAll(srvInfo.GoName, "InnerService", "")
- keyPrefix = strings.ReplaceAll(keyPrefix, "Inner", "") + "Service"
-
- genFile.Var().Op("_").Op("=").Qual(rpcMetaPkg, "Register").Call(
- jen.Op("&").Qual(rpcMetaPkg, "RpcMeta").Values(
- jen.Dict{
- jen.Id("Input"): jen.New(getPkg(file, meta.mth.Input.GoIdent)),
- jen.Id("Output"): jen.New(getPkg(file, meta.mth.Output.GoIdent)),
- jen.Id("Name"): jen.Lit(meta.meta.Name),
- jen.Id("Method"): jen.Lit(fmt.Sprintf("%s/%s", meta.srv.Desc.FullName(), meta.mth.GoName)),
- jen.Id("Tags"): jen.Map(jen.String()).String().Values(
- jen.DictFunc(func(dict jen.Dict) {
- for k, v := range meta.meta.Tags {
- dict[jen.Lit(k)] = jen.Lit(v)
- }
- }),
- ),
- },
- ))
- }
-
- g.P(genFile.GoString())
- return g
-}
-
-func getPkg(file *protogen.File, goIdent protogen.GoIdent) *jen.Statement {
- var pkgName = ""
- if file.GoImportPath != goIdent.GoImportPath {
- pkgName = string(goIdent.GoImportPath)
- }
-
- return jen.Qual(pkgName, goIdent.GoName)
-}
diff --git a/cmds/protoc-gen-lava/internal/version.go b/cmds/protoc-gen-lava/internal/version.go
deleted file mode 100644
index d71300e2f..000000000
--- a/cmds/protoc-gen-lava/internal/version.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package internal
-
-import (
- "fmt"
-
- "google.golang.org/protobuf/compiler/protogen"
-)
-
-const Version = "v0.0.3"
-
-func protocVersion(gen *protogen.Plugin) string {
- v := gen.Request.GetCompilerVersion()
- if v == nil {
- return "(unknown)"
- }
- var suffix string
- if s := v.GetSuffix(); s != "" {
- suffix = "-" + s
- }
- return fmt.Sprintf("v%d.%d.%d%s", v.GetMajor(), v.GetMinor(), v.GetPatch(), suffix)
-}
diff --git a/cmds/protoc-gen-lava/main.go b/cmds/protoc-gen-lava/main.go
deleted file mode 100644
index 964990e65..000000000
--- a/cmds/protoc-gen-lava/main.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package main
-
-import (
- "flag"
-
- "github.com/pubgo/lava/cmds/protoc-gen-lava/internal"
- "google.golang.org/protobuf/compiler/protogen"
- "google.golang.org/protobuf/types/pluginpb"
-)
-
-var _ = flag.String("version", internal.Version, "version")
-
-func main() {
- flag.Parse()
-
- protogen.Options{ParamFunc: flag.CommandLine.Set}.
- Run(func(plugin *protogen.Plugin) error {
- plugin.SupportedFeatures = uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
-
- for _, name := range plugin.Request.FileToGenerate {
- internal.GenerateFile(plugin, plugin.FilesByPath[name])
- }
-
- return nil
- })
-}
diff --git a/cmds/schedulercmd/cmd.go b/cmds/schedulercmd/cmd.go
new file mode 100644
index 000000000..b69664c1e
--- /dev/null
+++ b/cmds/schedulercmd/cmd.go
@@ -0,0 +1,43 @@
+package schedulercmd
+
+import (
+ "context"
+
+ "github.com/pubgo/dix/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/urfave/cli/v3"
+
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/scheduler"
+ "github.com/pubgo/lava/v2/core/scheduler/schedulerbuilder"
+ "github.com/pubgo/lava/v2/core/scheduler/schedulerdebug"
+ "github.com/pubgo/lava/v2/core/supervisor"
+ "github.com/pubgo/lava/v2/pkg/cliutil"
+ "github.com/pubgo/lava/v2/servers/https"
+)
+
+func New(di *dix.Dix) *cli.Command {
+ return &cli.Command{
+ Name: "scheduler",
+ Usage: cliutil.UsageDesc("crontab scheduler service %s(%s)", version.Project(), version.Version()),
+ Action: func(ctx context.Context, command *cli.Command) error {
+ di.Provide(schedulerbuilder.NewService)
+ di.Provide(https.New)
+ params := dix.Inject(di, new(struct {
+ LC lifecycle.Getter
+ Services []supervisor.Service
+ Manager scheduler.JobManager
+ }))
+
+ schedulerdebug.Init(params.Manager)
+
+ manager := supervisor.Default(params.LC)
+ for _, svc := range params.Services {
+ assert.Exit(manager.Add(svc))
+ }
+
+ return manager.Run(ctx)
+ },
+ }
+}
diff --git a/cmds/versioncmd/cmd.go b/cmds/versioncmd/cmd.go
index 92053ae70..c93fe6f8f 100644
--- a/cmds/versioncmd/cmd.go
+++ b/cmds/versioncmd/cmd.go
@@ -3,19 +3,20 @@ package versioncmd
import (
"context"
"fmt"
+
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/running"
"github.com/urfave/cli/v3"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/pkg/cmdutil"
+ "github.com/pubgo/lava/v2/pkg/cliutil"
)
func New() *cli.Command {
return &cli.Command{
Name: "version",
Aliases: []string{"v"},
- Usage: cmdutil.UsageDesc("%s version info", version.Project()),
+ Usage: cliutil.UsageDesc("%s version info", version.Project()),
Action: func(ctx context.Context, command *cli.Command) error {
defer recovery.Exit()
fmt.Println("project:", version.Project())
diff --git a/core/annotation/openapi.go b/core/annotation/openapi.go
deleted file mode 100644
index 1a39bc78a..000000000
--- a/core/annotation/openapi.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package annotation
-
-import "github.com/pubgo/lava/lava"
-
-var _ lava.Annotation = (*Openapi)(nil)
-
-type Openapi struct {
- ServiceName string `json:"service_name"`
-}
-
-func (s Openapi) Name() string {
- return "Openapi"
-}
diff --git a/core/debug/_docs.go b/core/debug/_.go
similarity index 56%
rename from core/debug/_docs.go
rename to core/debug/_.go
index c59e48f9f..927b1681d 100644
--- a/core/debug/_docs.go
+++ b/core/debug/_.go
@@ -1,3 +1,7 @@
package debug
// https://github.com/go-echarts/statsview
+
+import (
+ _ "github.com/fasthttp/router"
+)
diff --git a/core/debug/debug/debug.go b/core/debug/debug/debug.go
index ef7b07209..186bd77d0 100644
--- a/core/debug/debug/debug.go
+++ b/core/debug/debug/debug.go
@@ -8,7 +8,8 @@ import (
g "github.com/maragudk/gomponents"
c "github.com/maragudk/gomponents/components"
h "github.com/maragudk/gomponents/html"
- "github.com/pubgo/lava/core/debug"
+
+ "github.com/pubgo/lava/v2/core/debug"
)
func init() {
@@ -17,8 +18,9 @@ func init() {
func initDebug() {
debug.Get("/", func(ctx *fiber.Ctx) error {
- pathMap := make(map[string]interface{})
- stack := debug.App().Stack()
+ pathMap := make(map[string]any)
+
+ stack := ctx.App().Stack()
for m := range stack {
for r := range stack[m] {
route := stack[m][r]
diff --git a/core/debug/debug/mux.go b/core/debug/debug/mux.go
index 4fac20b15..6a185feb4 100644
--- a/core/debug/debug/mux.go
+++ b/core/debug/debug/mux.go
@@ -7,17 +7,19 @@ import (
"sync"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/strutil"
- "github.com/pubgo/lava/core/debug"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/config"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/pubgo/funk/v2/strutil"
+ "github.com/samber/lo"
"github.com/valyala/fasthttp"
"gopkg.in/yaml.v3"
+
+ "github.com/pubgo/lava/v2/core/debug"
)
var (
@@ -26,13 +28,13 @@ var (
)
func init() {
- log.Info().Str("password", running.InstanceID).Msg("debug password")
+ // log.Info().Str("password", passwd).Msg("debug password")
debug.App().Use(func(c *fiber.Ctx) (gErr error) {
defer recovery.Recovery(func(err error) {
- err = errors.WrapTag(err,
- errors.T("headers", c.GetReqHeaders()),
- errors.T("url", c.Request().URI().String()),
- )
+ err = errors.WrapTags(err, errors.Tags{
+ "headers": c.GetReqHeaders(),
+ "url": c.Request().URI().String(),
+ })
gErr = c.JSON(err)
})
@@ -53,8 +55,8 @@ func init() {
if host != "localhost" && host != "127.0.0.1" {
if token != passwd {
err := errors.New("token 不存在或者密码不对")
- if ret := result.Of(c.WriteString(err.Error())); ret.IsErr() {
- return errors.WrapCaller(ret.Err())
+ if result.ThrowErr(&gErr, lo.T2(c.WriteString(err.Error())).B) {
+ return gErr
}
if err := c.SendStatus(http.StatusInternalServerError); err != nil {
diff --git a/core/debug/gops/_.go b/core/debug/gops/_.go
new file mode 100644
index 000000000..16086fbe5
--- /dev/null
+++ b/core/debug/gops/_.go
@@ -0,0 +1,3 @@
+package gops
+
+//_ https://github.com/openziti/agent/blob/main/client.go
diff --git a/core/debug/gops/debug.go b/core/debug/gops/debug.go
new file mode 100644
index 000000000..40b97b515
--- /dev/null
+++ b/core/debug/gops/debug.go
@@ -0,0 +1,15 @@
+package gops
+
+import (
+ "github.com/google/gops/agent"
+ "github.com/pubgo/funk/v2/log"
+)
+
+func init() {
+ err := agent.Listen(agent.Options{})
+ if err != nil {
+ log.Err(err).Msg("failed to start gops agent")
+ } else {
+ log.Info().Msg("gops agent started ok")
+ }
+}
diff --git a/core/debug/healthy/debug.go b/core/debug/healthy/debug.go
index 8f5ca51d5..4b3b1ac4a 100644
--- a/core/debug/healthy/debug.go
+++ b/core/debug/healthy/debug.go
@@ -6,9 +6,10 @@ import (
jjson "github.com/goccy/go-json"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/funk/try"
- "github.com/pubgo/lava/core/debug"
- "github.com/pubgo/lava/core/healthy"
+ "github.com/pubgo/funk/v2/try"
+
+ "github.com/pubgo/lava/v2/core/debug"
+ "github.com/pubgo/lava/v2/core/healthy"
)
func init() {
diff --git a/core/debug/mux.go b/core/debug/mux.go
index 2cd3f6e7b..6860355b0 100644
--- a/core/debug/mux.go
+++ b/core/debug/mux.go
@@ -3,7 +3,6 @@ package debug
import (
"net/http"
- _ "github.com/fasthttp/router"
"github.com/gofiber/adaptor/v2"
"github.com/gofiber/fiber/v2"
)
@@ -16,11 +15,6 @@ type Config struct {
var app = fiber.New()
-func Handler(ctx *fiber.Ctx) error {
- app.Handler()(ctx.Context())
- return nil
-}
-
func App() *fiber.App { return app }
func WrapFunc(h http.HandlerFunc) fiber.Handler { return adaptor.HTTPHandlerFunc(h) }
func Wrap(h http.Handler) fiber.Handler { return adaptor.HTTPHandler(h) }
diff --git a/core/debug/panicparse/panicparse.go b/core/debug/panicparse/panicparse.go
new file mode 100644
index 000000000..6590848a3
--- /dev/null
+++ b/core/debug/panicparse/panicparse.go
@@ -0,0 +1,11 @@
+package panicparse
+
+import (
+ "github.com/maruel/panicparse/v2/stack/webstack"
+
+ "github.com/pubgo/lava/v2/core/debug"
+)
+
+func init() {
+ debug.Get("/panicparse", debug.WrapFunc(webstack.SnapshotHandler))
+}
diff --git a/core/debug/pprof/pprof.go b/core/debug/pprof/pprof.go
index 03c6b1e12..709ef18c0 100644
--- a/core/debug/pprof/pprof.go
+++ b/core/debug/pprof/pprof.go
@@ -7,7 +7,7 @@ import (
"github.com/felixge/fgprof"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/lava/core/debug"
+ "github.com/pubgo/lava/v2/core/debug"
)
func init() {
diff --git a/core/debug/process/process.go b/core/debug/process/process.go
index c282a716a..9c67a53a6 100644
--- a/core/debug/process/process.go
+++ b/core/debug/process/process.go
@@ -5,46 +5,48 @@ import (
"github.com/gofiber/fiber/v2"
ps "github.com/keybase/go-ps"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/result"
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/result"
- "github.com/pubgo/lava/core/debug"
+ "github.com/pubgo/lava/v2/core/debug"
)
func init() {
- debug.Get("/process", func(ctx *fiber.Ctx) error {
+ debug.Get("/process", func(ctx *fiber.Ctx) (gErr error) {
+ defer result.RecoveryErr(&gErr)
processes := assert.Must1(ps.Processes())
- processes1 := generic.Map(processes, func(i int) map[string]any {
- p := processes[i]
- ret := goVersion(result.Wrap(p.Path()))
- if ret.IsErr() {
- return nil
+ processes1 := funk.Map(processes, func(p ps.Process) map[string]any {
+ path, err := p.Path()
+ if err != nil {
+ log.Err(err).Str("path", p.Executable()).Msg("process path error")
}
return map[string]any{
"pid": p.Pid(),
"ppid": p.PPid(),
"exec": p.Executable(),
- "path": result.Wrap(p.Path()),
- "go_version": ret.Unwrap(),
+ "path": path,
+ "go_version": goVersion(path),
}
})
- processes1 = generic.Filter(processes1, func(m map[string]any) bool { return m != nil })
+ processes1 = funk.Filter(processes1, func(m map[string]any) bool { return m != nil })
return ctx.JSON(processes1)
})
}
-func goVersion(path result.Result[string]) result.Result[string] {
- if path.IsErr() {
- return path
+func goVersion(path string) string {
+ if path == "" {
+ return ""
}
- info, err := buildinfo.ReadFile(path.Unwrap())
+ info, err := buildinfo.ReadFile(path)
if err != nil {
- return result.Wrap("", err)
+ log.Err(err).CallerSkipFrame(1).Str("path", path).Msg("goVersion error")
+ return ""
}
- return result.OK(info.GoVersion)
+ return info.GoVersion
}
diff --git a/core/debug/statsviz/main.go b/core/debug/statsviz/main.go
index 05dbf315e..3935bdeb5 100644
--- a/core/debug/statsviz/main.go
+++ b/core/debug/statsviz/main.go
@@ -5,26 +5,36 @@ import (
"github.com/arl/statsviz"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/lava/core/debug"
- "github.com/pubgo/lava/pkg/httputil"
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/assert"
+
+ "github.com/pubgo/lava/v2/core/debug"
+ "github.com/pubgo/lava/v2/pkg/httputil"
)
// github.com/go-echarts/statsview
func init() {
- srv := assert.Exit1(statsviz.NewServer(statsviz.Root("/statsviz")))
+ srv := assert.Exit1(statsviz.NewServer(statsviz.Root("/debug/statsviz")))
debug.Route("/statsviz", func(router fiber.Router) {
router.Use(func(ctx *fiber.Ctx) error {
path := string(ctx.Request().URI().Path())
- pathList := strings.Split(path, "/")
- if strings.Trim(pathList[len(pathList)-1], "/") == "ws" {
+ lastPath := strings.TrimSpace(funk.Last(strings.Split(strings.Trim(path, "/"), "/")))
+
+ if lastPath == "ws" {
return httputil.HTTPHandler(srv.Ws())(ctx)
}
+ if lastPath == "statsviz" {
+ return httputil.HTTPHandler(srv.Index())(ctx)
+ }
+
return ctx.Next()
})
+ router.Get("", func(ctx *fiber.Ctx) error {
+ return httputil.HTTPHandler(srv.Index())(ctx)
+ })
router.Get("/", func(ctx *fiber.Ctx) error {
return httputil.HTTPHandler(srv.Index())(ctx)
})
diff --git a/core/debug/trace/trace.go b/core/debug/trace/trace.go
index e94a6cd27..8c02555f0 100644
--- a/core/debug/trace/trace.go
+++ b/core/debug/trace/trace.go
@@ -4,7 +4,7 @@ import (
"github.com/gofiber/adaptor/v2"
"golang.org/x/net/trace"
- "github.com/pubgo/lava/core/debug"
+ "github.com/pubgo/lava/v2/core/debug"
)
func init() {
diff --git a/core/debug/vars/debug.go b/core/debug/vars/debug.go
index 6fa6320b2..b7666e33a 100644
--- a/core/debug/vars/debug.go
+++ b/core/debug/vars/debug.go
@@ -8,9 +8,9 @@ import (
g "github.com/maragudk/gomponents"
c "github.com/maragudk/gomponents/components"
h "github.com/maragudk/gomponents/html"
- "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/funk/v2/recovery"
- "github.com/pubgo/lava/core/debug"
+ "github.com/pubgo/lava/v2/core/debug"
)
func init() {
diff --git a/core/debug/version/version.go b/core/debug/version/version.go
index e8c7ce5ab..b27d96e61 100644
--- a/core/debug/version/version.go
+++ b/core/debug/version/version.go
@@ -7,16 +7,16 @@ import (
json "github.com/goccy/go-json"
"github.com/gofiber/adaptor/v2"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/running"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/running"
- "github.com/pubgo/lava/core/debug"
+ "github.com/pubgo/lava/v2/core/debug"
)
func init() {
- debug.Get("/env", adaptor.HTTPHandlerFunc(envHandle))
- debug.Get("/version", adaptor.HTTPHandlerFunc(versionHandle))
- debug.Get("/dep", adaptor.HTTPHandlerFunc(depHandle))
+ debug.Get("env", adaptor.HTTPHandlerFunc(envHandle))
+ debug.Get("version", adaptor.HTTPHandlerFunc(versionHandle))
+ debug.Get("dep", adaptor.HTTPHandlerFunc(depHandle))
}
func envHandle(writer http.ResponseWriter, request *http.Request) {
diff --git a/core/debug/zpags/debug.go b/core/debug/zpags/debug.go
new file mode 100644
index 000000000..fc1a98b16
--- /dev/null
+++ b/core/debug/zpags/debug.go
@@ -0,0 +1,12 @@
+package zpags
+
+import (
+ "github.com/gofiber/adaptor/v2"
+ "go.opentelemetry.io/contrib/zpages"
+
+ "github.com/pubgo/lava/v2/core/debug"
+)
+
+func init() {
+ debug.Get("/z", adaptor.HTTPHandler(zpages.NewTracezHandler(zpages.NewSpanProcessor())))
+}
diff --git a/core/discovery/aaa.go b/core/discovery/aaa.go
index 50099e1df..7e70280e1 100644
--- a/core/discovery/aaa.go
+++ b/core/discovery/aaa.go
@@ -4,9 +4,10 @@ import (
"context"
"time"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/lava/core/service"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
+ "github.com/pubgo/funk/v2/result"
+
+ "github.com/pubgo/lava/v2/core/service"
+ "github.com/pubgo/lava/v2/pkg/proto/lavapbv1"
)
type (
diff --git a/core/discovery/config.go b/core/discovery/config.go
index 4d5efc5fa..3bcd56ffd 100644
--- a/core/discovery/config.go
+++ b/core/discovery/config.go
@@ -4,9 +4,9 @@ import (
"fmt"
"time"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/errors"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/config"
+ "github.com/pubgo/funk/v2/errors"
)
// https://github.com/go-eagle/eagle/blob/master/pkg/registry/registry.go
diff --git a/core/discovery/noop.go b/core/discovery/noop.go
index 209b56fb1..a0dbf8c71 100644
--- a/core/discovery/noop.go
+++ b/core/discovery/noop.go
@@ -3,9 +3,9 @@ package discovery
import (
"context"
- "github.com/pubgo/funk/result"
+ "github.com/pubgo/funk/v2/result"
- "github.com/pubgo/lava/core/service"
+ "github.com/pubgo/lava/v2/core/service"
)
func NewNoopDiscovery() Discovery {
@@ -19,8 +19,8 @@ var (
type noopDiscovery struct{}
-func (n *noopDiscovery) Next() result.Result[*Result] {
- return result.Err[*Result](ErrWatcherStopped)
+func (n *noopDiscovery) Next() (r result.Result[*Result]) {
+ return r.WithErr(ErrWatcherStopped)
}
func (n *noopDiscovery) Stop() error { return nil }
diff --git a/core/encoding/aaa.go b/core/encoding/aaa.go
index a5594946d..0e5d4f106 100644
--- a/core/encoding/aaa.go
+++ b/core/encoding/aaa.go
@@ -5,8 +5,8 @@ var Name = "encoding"
// Codec defines the interface.
type Codec interface {
Name() string
- Encode(v interface{}) ([]byte, error)
- Decode(data []byte, v interface{}) error
- Marshal(v interface{}) ([]byte, error)
- Unmarshal(data []byte, v interface{}) error
+ Encode(v any) ([]byte, error)
+ Decode(data []byte, v any) error
+ Marshal(v any) ([]byte, error)
+ Unmarshal(data []byte, v any) error
}
diff --git a/core/encoding/bytes/bytes.go b/core/encoding/bytes/bytes.go
index a7bb2aefa..9bebe57f6 100644
--- a/core/encoding/bytes/bytes.go
+++ b/core/encoding/bytes/bytes.go
@@ -3,7 +3,7 @@ package bytes
import (
"fmt"
- "github.com/pubgo/lava/core/encoding"
+ "github.com/pubgo/lava/v2/core/encoding"
)
func init() {
@@ -18,7 +18,7 @@ func (c *Codec) Name() string {
return "bytes"
}
-func (c *Codec) Encode(v interface{}) ([]byte, error) {
+func (c *Codec) Encode(v any) ([]byte, error) {
switch ve := v.(type) {
case *[]byte:
return *ve, nil
@@ -28,7 +28,7 @@ func (c *Codec) Encode(v interface{}) ([]byte, error) {
return nil, nil
}
-func (c *Codec) Decode(data []byte, ve interface{}) error {
+func (c *Codec) Decode(data []byte, ve any) error {
switch ve := ve.(type) {
case *[]byte:
*ve = data
@@ -36,7 +36,7 @@ func (c *Codec) Decode(data []byte, ve interface{}) error {
return nil
}
-func (c *Codec) Marshal(v interface{}) ([]byte, error) {
+func (c *Codec) Marshal(v any) ([]byte, error) {
switch ve := v.(type) {
case *[]byte:
return *ve, nil
@@ -46,7 +46,7 @@ func (c *Codec) Marshal(v interface{}) ([]byte, error) {
return nil, nil
}
-func (c *Codec) Unmarshal(data []byte, ve interface{}) error {
+func (c *Codec) Unmarshal(data []byte, ve any) error {
fmt.Println(string(data))
switch ve := ve.(type) {
case *[]byte:
diff --git a/core/encoding/encoding.go b/core/encoding/encoding.go
index e855d3a55..df797e063 100644
--- a/core/encoding/encoding.go
+++ b/core/encoding/encoding.go
@@ -1,9 +1,9 @@
package encoding
import (
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/typex"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/typex"
)
var data typex.Map[Codec]
diff --git a/core/encoding/json/codec.go b/core/encoding/json/codec.go
index 7854340fd..18156579d 100644
--- a/core/encoding/json/codec.go
+++ b/core/encoding/json/codec.go
@@ -2,7 +2,8 @@ package json
import (
json "github.com/json-iterator/go"
- "github.com/pubgo/lava/core/encoding"
+
+ "github.com/pubgo/lava/v2/core/encoding"
)
var (
@@ -21,8 +22,8 @@ func init() {
// jsonCodec uses json marshaler and unmarshaler.
type jsonCodec struct{}
-func (c *jsonCodec) Marshal(v interface{}) ([]byte, error) { return std.Marshal(v) }
-func (c *jsonCodec) Unmarshal(data []byte, v interface{}) error { return std.Unmarshal(data, v) }
-func (c *jsonCodec) Name() string { return Name }
-func (c *jsonCodec) Encode(i interface{}) ([]byte, error) { return std.Marshal(i) }
-func (c *jsonCodec) Decode(data []byte, v interface{}) error { return std.Unmarshal(data, v) }
+func (c *jsonCodec) Marshal(v any) ([]byte, error) { return std.Marshal(v) }
+func (c *jsonCodec) Unmarshal(data []byte, v any) error { return std.Unmarshal(data, v) }
+func (c *jsonCodec) Name() string { return Name }
+func (c *jsonCodec) Encode(i any) ([]byte, error) { return std.Marshal(i) }
+func (c *jsonCodec) Decode(data []byte, v any) error { return std.Unmarshal(data, v) }
diff --git a/core/encoding/msgpack/codec.go b/core/encoding/msgpack/codec.go
index 9b2fd4ab2..52118d617 100644
--- a/core/encoding/msgpack/codec.go
+++ b/core/encoding/msgpack/codec.go
@@ -1,8 +1,9 @@
package msgpack
import (
- "github.com/pubgo/lava/core/encoding"
msgpack "github.com/vmihailenco/msgpack/v5"
+
+ "github.com/pubgo/lava/v2/core/encoding"
)
var Name = "msgpack"
@@ -14,8 +15,8 @@ func init() {
// msgpackCodec uses messagepack marshaler and unmarshaler.
type msgpackCodec struct{}
-func (c msgpackCodec) Marshal(v interface{}) ([]byte, error) { return msgpack.Marshal(v) }
-func (c msgpackCodec) Unmarshal(data []byte, v interface{}) error { return msgpack.Unmarshal(data, v) }
-func (c msgpackCodec) Name() string { return Name }
-func (c msgpackCodec) Encode(i interface{}) ([]byte, error) { return msgpack.Marshal(i) }
-func (c msgpackCodec) Decode(data []byte, i interface{}) error { return msgpack.Unmarshal(data, i) }
+func (c msgpackCodec) Marshal(v any) ([]byte, error) { return msgpack.Marshal(v) }
+func (c msgpackCodec) Unmarshal(data []byte, v any) error { return msgpack.Unmarshal(data, v) }
+func (c msgpackCodec) Name() string { return Name }
+func (c msgpackCodec) Encode(i any) ([]byte, error) { return msgpack.Marshal(i) }
+func (c msgpackCodec) Decode(data []byte, i any) error { return msgpack.Unmarshal(data, i) }
diff --git a/core/encoding/protobuf/codec.go b/core/encoding/protobuf/codec.go
index 3193a1f45..85291f33e 100644
--- a/core/encoding/protobuf/codec.go
+++ b/core/encoding/protobuf/codec.go
@@ -3,10 +3,9 @@ package protobuf
import (
"fmt"
- "github.com/pubgo/lava/core/encoding"
+ "github.com/pubgo/lava/v2/core/encoding"
"google.golang.org/protobuf/proto"
- pb "google.golang.org/protobuf/proto"
)
var Name = "proto"
@@ -23,8 +22,8 @@ func (c protobufCodec) Marshal(v interface{}) ([]byte, error) {
return proto.Marshal(m)
}
- if m, ok := v.(pb.Message); ok {
- return pb.Marshal(m)
+ if m, ok := v.(proto.Message); ok {
+ return proto.Marshal(m)
}
return nil, fmt.Errorf("%T is not a proto.Marshaler", v)
@@ -35,8 +34,8 @@ func (c protobufCodec) Unmarshal(data []byte, v interface{}) error {
return proto.Unmarshal(data, m)
}
- if m, ok := v.(pb.Message); ok {
- return pb.Unmarshal(data, m)
+ if m, ok := v.(proto.Message); ok {
+ return proto.Unmarshal(data, m)
}
return fmt.Errorf("%T is not a proto.Unmarshaler", v)
@@ -52,8 +51,8 @@ func (c protobufCodec) Encode(i interface{}) ([]byte, error) {
return proto.Marshal(m)
}
- if m, ok := i.(pb.Message); ok {
- return pb.Marshal(m)
+ if m, ok := i.(proto.Message); ok {
+ return proto.Marshal(m)
}
return nil, fmt.Errorf("%T is not a proto.Marshaler", i)
@@ -65,8 +64,8 @@ func (c protobufCodec) Decode(data []byte, i interface{}) error {
return proto.Unmarshal(data, m)
}
- if m, ok := i.(pb.Message); ok {
- return pb.Unmarshal(data, m)
+ if m, ok := i.(proto.Message); ok {
+ return proto.Unmarshal(data, m)
}
return fmt.Errorf("%T is not a proto.Unmarshaler", i)
diff --git a/core/encoding/protojson/codec.go b/core/encoding/protojson/codec.go
index 5560332f5..3be84fd62 100644
--- a/core/encoding/protojson/codec.go
+++ b/core/encoding/protojson/codec.go
@@ -4,7 +4,7 @@ import (
"bytes"
"encoding/json"
- "github.com/pubgo/lava/core/encoding"
+ "github.com/pubgo/lava/v2/core/encoding"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
diff --git a/core/encoding/vars.go b/core/encoding/vars.go
index 9b2731e27..2df8bc83c 100644
--- a/core/encoding/vars.go
+++ b/core/encoding/vars.go
@@ -1,8 +1,8 @@
package encoding
-import "github.com/pubgo/funk/vars"
+import "github.com/pubgo/funk/v2/vars"
func init() {
- vars.Register(Name, func() interface{} { return Keys() })
- vars.Register(Name+"-mapping", func() interface{} { return cdcMapping })
+ vars.Register(Name, func() any { return Keys() })
+ vars.Register(Name+"-mapping", func() any { return cdcMapping })
}
diff --git a/core/flags/default.go b/core/flags/default.go
index 293e55442..cf61cb446 100644
--- a/core/flags/default.go
+++ b/core/flags/default.go
@@ -1,64 +1,13 @@
package flags
import (
- "context"
-
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/env"
- "github.com/pubgo/funk/running"
- "github.com/urfave/cli/v3"
+ "github.com/pubgo/funk/v2/running"
)
func init() {
- const httpPort = "server_http_port"
- const grpcPort = "server_grpc_port"
- const conf = "config_path"
- env.GetIntVal(&running.HttpPort, httpPort)
- env.GetIntVal(&running.GrpcPort, grpcPort)
-
- Register(&cli.IntFlag{
- Name: "http-port",
- Usage: "service http port",
- Persistent: true,
- Value: int64(running.HttpPort),
- Sources: cli.EnvVars(env.Key(httpPort)),
- Action: func(ctx context.Context, command *cli.Command, i int64) error {
- running.HttpPort = int(i)
- return nil
- },
- })
-
- Register(&cli.IntFlag{
- Name: "grpc-port",
- Usage: "service grpc port",
- Persistent: true,
- Value: int64(running.GrpcPort),
- Sources: cli.EnvVars(env.Key("server_grpc_port")),
- Action: func(ctx context.Context, command *cli.Command, i int64) error {
- running.GrpcPort = int(i)
- return nil
- },
- })
-
- Register(&cli.BoolFlag{
- Name: "debug",
- Usage: "enable debug mode",
- Persistent: true,
- Value: running.IsDebug,
- Destination: &running.IsDebug,
- Sources: cli.EnvVars(env.Key("debug"), env.Key("enable_debug")),
- })
-
- Register(&cli.StringFlag{
- Name: "config",
- Aliases: []string{"c"},
- Usage: "config path",
- Value: config.GetConfigPath(),
- Persistent: true,
- Sources: cli.EnvVars(env.Key(conf)),
- Action: func(ctx context.Context, command *cli.Command, s string) error {
- config.SetConfigPath(s)
- return nil
- },
- })
+ Register(&running.DebugFlag)
+ Register(&running.EnvFlag)
+ Register(&running.ConfFlag)
+ Register(&running.GrpcPortFlag)
+ Register(&running.HttpPortFlag)
}
diff --git a/core/healthy/factory.go b/core/healthy/factory.go
index 4f71ae910..102463ccb 100644
--- a/core/healthy/factory.go
+++ b/core/healthy/factory.go
@@ -1,8 +1,8 @@
package healthy
import (
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/typex"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/typex"
)
const Name = "healthy"
@@ -19,11 +19,11 @@ func Get(name string) Handler {
}
func List() (names []string) {
- healthList.Range(func(name, _ interface{}) bool {
+ healthList.Range(func(name, _ any) bool {
names = append(names, name.(string))
return true
})
- return
+ return names
}
func Register(name string, r Handler) {
diff --git a/core/healthy/vars.go b/core/healthy/vars.go
index 46f7df1ce..57990a8f1 100644
--- a/core/healthy/vars.go
+++ b/core/healthy/vars.go
@@ -1,14 +1,14 @@
package healthy
import (
- "github.com/pubgo/funk/stack"
- "github.com/pubgo/funk/vars"
+ "github.com/pubgo/funk/v2/stack"
+ "github.com/pubgo/funk/v2/vars"
)
func init() {
- vars.Register(Name, func() interface{} {
+ vars.Register(Name, func() any {
data := make(map[string]any)
- healthList.Range(func(key, value interface{}) bool {
+ healthList.Range(func(key, value any) bool {
data[key.(string)] = stack.CallerWithFunc(value)
return true
})
diff --git a/core/lavabuilder/builder.go b/core/lavabuilder/builder.go
new file mode 100644
index 000000000..97cd3e7f0
--- /dev/null
+++ b/core/lavabuilder/builder.go
@@ -0,0 +1,104 @@
+package lavabuilder
+
+import (
+ "context"
+ "os"
+ "sort"
+
+ "github.com/pubgo/dix/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/features/featureflags"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/running"
+ cli "github.com/urfave/cli/v3"
+
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccresolver"
+ "github.com/pubgo/lava/v2/cmds/depcmd"
+ "github.com/pubgo/lava/v2/cmds/grpcservercmd"
+ "github.com/pubgo/lava/v2/cmds/healthcmd"
+ "github.com/pubgo/lava/v2/cmds/httpservercmd"
+ "github.com/pubgo/lava/v2/cmds/schedulercmd"
+ "github.com/pubgo/lava/v2/cmds/versioncmd"
+ "github.com/pubgo/lava/v2/core/discovery"
+ "github.com/pubgo/lava/v2/core/flags"
+ "github.com/pubgo/lava/v2/core/lifecycle/lifecyclebuilder"
+ "github.com/pubgo/lava/v2/core/logging/logbuilder"
+ "github.com/pubgo/lava/v2/core/metrics/metricbuilder"
+ "github.com/pubgo/lava/v2/core/signals"
+ "github.com/pubgo/lava/v2/pkg/cliutil"
+
+ _ "github.com/pubgo/lava/v2/core/debug/debug"
+ //_ "github.com/pubgo/lava/v2/core/debug/gops"
+ _ "github.com/pubgo/lava/v2/core/debug/pprof"
+ _ "github.com/pubgo/lava/v2/core/debug/process"
+ _ "github.com/pubgo/lava/v2/core/debug/statsviz"
+ _ "github.com/pubgo/lava/v2/core/debug/trace"
+ _ "github.com/pubgo/lava/v2/core/debug/vars"
+ _ "github.com/pubgo/lava/v2/core/debug/version"
+
+ // metric
+ _ "github.com/pubgo/lava/v2/core/metrics/drivers/prometheus"
+
+ // encoding
+ _ "github.com/pubgo/lava/v2/core/encoding/protobuf"
+ _ "github.com/pubgo/lava/v2/core/encoding/protojson"
+
+ // logging
+ _ "github.com/pubgo/lava/v2/core/logging/logext/grpclog"
+ _ "github.com/pubgo/lava/v2/core/logging/logext/slog"
+ _ "github.com/pubgo/lava/v2/core/logging/logext/stdlog"
+
+ _ "go.uber.org/automaxprocs"
+)
+
+var defaultProviders = []any{
+ grpccresolver.NewDirectBuilder,
+ grpccresolver.NewDiscoveryBuilder,
+ discovery.NewNoopDiscovery,
+
+ logbuilder.New,
+ metricbuilder.New,
+
+ lifecyclebuilder.New,
+}
+
+func New(opts ...dix.Option) *dix.Dix {
+ di := dix.New(append(opts, dix.WithValuesNull())...)
+ for _, p := range defaultProviders {
+ dix.Provide(di, p)
+ }
+ return di
+}
+
+func Run(di *dix.Dix) {
+ defer recovery.Exit(func(err error) error {
+ if errors.Is(err, context.Canceled) {
+ return nil
+ }
+ return err
+ })
+
+ dix.Provide(di, versioncmd.New)
+ dix.Provide(di, healthcmd.New)
+ dix.Provide(di, depcmd.New)
+ dix.Provide(di, grpcservercmd.New)
+ dix.Provide(di, httpservercmd.New)
+ dix.Provide(di, schedulercmd.New)
+ dix.Inject(di, func(commands []*cli.Command) {
+ app := &cli.Command{
+ Name: version.Project(),
+ Suggest: true,
+ UseShortOptionHandling: true,
+ Usage: cliutil.UsageDesc("%s service", version.Project()),
+ Version: version.Version(),
+ Flags: append(flags.GetFlags(), featureflags.GetFlags()...),
+ Commands: commands,
+ ExtraInfo: running.GetSysInfo,
+ }
+
+ sort.Sort(cli.FlagsByName(app.Flags))
+ assert.Must(app.Run(signals.Context(), os.Args))
+ })
+}
diff --git a/core/lavacontexts/context.go b/core/lavacontexts/context.go
index 2a5b443d8..709d01c5f 100644
--- a/core/lavacontexts/context.go
+++ b/core/lavacontexts/context.go
@@ -3,9 +3,10 @@ package lavacontexts
import (
"context"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
"github.com/rs/xid"
+
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/proto/lavapbv1"
)
type ctxKey string
diff --git a/core/lifecycle/builder.go b/core/lifecycle/builder.go
deleted file mode 100644
index 48f289f21..000000000
--- a/core/lifecycle/builder.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package lifecycle
-
-type Provider struct {
- Setter Lifecycle
- Getter Getter
-}
-
-func New(handlers []Handler) Provider {
- lc := new(lifecycleImpl)
- for i := range handlers {
- handlers[i](lc)
- }
-
- return Provider{
- Setter: lc,
- Getter: lc,
- }
-}
diff --git a/core/lifecycle/lifecycle.go b/core/lifecycle/lifecycle.go
index 6b43248a7..8a71eb766 100644
--- a/core/lifecycle/lifecycle.go
+++ b/core/lifecycle/lifecycle.go
@@ -1,16 +1,32 @@
package lifecycle
+import "context"
+
+func WrapNoError(fn func(context.Context)) ExecFunc {
+ return func(ctx context.Context) error { fn(ctx); return nil }
+}
+
+func WrapNoCtx(fn func() error) ExecFunc {
+ return func(ctx context.Context) error { return fn() }
+}
+
+func WrapNoCtxErr(fn func()) ExecFunc {
+ return func(ctx context.Context) error { fn(); return nil }
+}
+
+type ExecFunc = func(context.Context) error
+
type Executor struct {
- Handler func()
+ Exec ExecFunc
}
type Handler func(lc Lifecycle)
type Lifecycle interface {
- AfterStop(f func())
- BeforeStop(f func())
- AfterStart(f func())
- BeforeStart(f func())
+ AfterStop(f ExecFunc)
+ BeforeStop(f ExecFunc)
+ AfterStart(f ExecFunc)
+ BeforeStart(f ExecFunc)
}
type Getter interface {
@@ -19,35 +35,3 @@ type Getter interface {
GetAfterStarts() []Executor
GetBeforeStarts() []Executor
}
-
-var (
- _ Lifecycle = (*lifecycleImpl)(nil)
- _ Getter = (*lifecycleImpl)(nil)
-)
-
-type lifecycleImpl struct {
- beforeStarts []Executor
- afterStarts []Executor
- beforeStops []Executor
- afterStops []Executor
-}
-
-func (t *lifecycleImpl) GetAfterStops() []Executor { return t.afterStops }
-func (t *lifecycleImpl) GetBeforeStops() []Executor { return t.beforeStops }
-func (t *lifecycleImpl) GetAfterStarts() []Executor { return t.afterStarts }
-func (t *lifecycleImpl) GetBeforeStarts() []Executor { return t.beforeStarts }
-func (t *lifecycleImpl) BeforeStart(f func()) {
- t.beforeStarts = append(t.beforeStarts, Executor{Handler: f})
-}
-
-func (t *lifecycleImpl) BeforeStop(f func()) {
- t.beforeStops = append([]Executor{{Handler: f}}, t.beforeStops...)
-}
-
-func (t *lifecycleImpl) AfterStart(f func()) {
- t.afterStarts = append(t.afterStarts, Executor{Handler: f})
-}
-
-func (t *lifecycleImpl) AfterStop(f func()) {
- t.afterStops = append([]Executor{{Handler: f}}, t.afterStops...)
-}
diff --git a/core/lifecycle/lifecyclebuilder/builder.go b/core/lifecycle/lifecyclebuilder/builder.go
new file mode 100644
index 000000000..2c75cbe53
--- /dev/null
+++ b/core/lifecycle/lifecyclebuilder/builder.go
@@ -0,0 +1,52 @@
+package lifecyclebuilder
+
+import "github.com/pubgo/lava/v2/core/lifecycle"
+
+type Provider struct {
+ Setter lifecycle.Lifecycle
+ Getter lifecycle.Getter
+}
+
+func New(handlers []lifecycle.Handler) Provider {
+ lc := new(lifecycleImpl)
+ for i := range handlers {
+ handlers[i](lc)
+ }
+
+ return Provider{
+ Setter: lc,
+ Getter: lc,
+ }
+}
+
+var (
+ _ lifecycle.Lifecycle = (*lifecycleImpl)(nil)
+ _ lifecycle.Getter = (*lifecycleImpl)(nil)
+)
+
+type lifecycleImpl struct {
+ beforeStarts []lifecycle.Executor
+ afterStarts []lifecycle.Executor
+ beforeStops []lifecycle.Executor
+ afterStops []lifecycle.Executor
+}
+
+func (t *lifecycleImpl) GetAfterStops() []lifecycle.Executor { return t.afterStops }
+func (t *lifecycleImpl) GetBeforeStops() []lifecycle.Executor { return t.beforeStops }
+func (t *lifecycleImpl) GetAfterStarts() []lifecycle.Executor { return t.afterStarts }
+func (t *lifecycleImpl) GetBeforeStarts() []lifecycle.Executor { return t.beforeStarts }
+func (t *lifecycleImpl) BeforeStart(f lifecycle.ExecFunc) {
+ t.beforeStarts = append(t.beforeStarts, lifecycle.Executor{Exec: f})
+}
+
+func (t *lifecycleImpl) BeforeStop(f lifecycle.ExecFunc) {
+ t.beforeStops = append([]lifecycle.Executor{{Exec: f}}, t.beforeStops...)
+}
+
+func (t *lifecycleImpl) AfterStart(f lifecycle.ExecFunc) {
+ t.afterStarts = append(t.afterStarts, lifecycle.Executor{Exec: f})
+}
+
+func (t *lifecycleImpl) AfterStop(f lifecycle.ExecFunc) {
+ t.afterStops = append([]lifecycle.Executor{{Exec: f}}, t.afterStops...)
+}
diff --git a/core/logging/builder.go b/core/logging/builder.go
deleted file mode 100644
index dbd3e5f4c..000000000
--- a/core/logging/builder.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package logging
-
-import (
- "io"
- "os"
- "time"
-
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/stack"
- "github.com/pubgo/lava/core/logging/logkey"
- "github.com/rs/zerolog"
-)
-
-func init() {
- zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
- return stack.Stack(pc).Short()
- }
-}
-
-// New logger
-func New(cfg *Config) log.Logger {
- defer recovery.Exit()
-
- level := zerolog.DebugLevel
- if cfg.Level != "" {
- level = result.Of(zerolog.ParseLevel(cfg.Level)).Expect("log level is invalid")
- }
- zerolog.SetGlobalLevel(level)
-
- logger := zerolog.New(&writer{os.Stdout}).Level(level).With().Timestamp().Caller().Logger()
- if !cfg.AsJson {
- logger = logger.Output(&writer{
- zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
- w.Out = os.Stdout
- w.TimeFormat = time.RFC3339
- }),
- })
- }
-
- // 全局log设置
- ee := logger.With().
- Str(logkey.Hostname, running.Hostname).
- Str(logkey.Project, running.Project).
- Str(logkey.Version, running.Version)
-
- if running.Namespace != "" {
- ee = ee.Str(logkey.Namespace, running.Namespace)
- }
-
- logger = ee.Logger()
- log.SetLogger(&logger)
-
- gl := log.New(&logger)
- for _, ext := range List() {
- ext(gl)
- }
- return gl
-}
-
-type writer struct {
- io.Writer
-}
-
-func (w writer) Write(p []byte) (n int, err error) {
- n, err = w.Writer.Write(p)
- if err != nil {
- log.Err(err).Str("raw_json", string(p)).Msg("failed to decode invalid json")
- return
- }
-
- return
-}
diff --git a/core/logging/config.go b/core/logging/config.go
index b5dd4a6c1..7ad69e4e8 100644
--- a/core/logging/config.go
+++ b/core/logging/config.go
@@ -8,4 +8,5 @@ type Config struct {
Level string `yaml:"level"`
AsJson bool `yaml:"as_json"`
DisableLoggers []string `yaml:"disable_loggers"`
+ Filters []string `yaml:"filters"`
}
diff --git a/core/logging/factory.go b/core/logging/factory.go
index b234d0bdd..134740f3d 100644
--- a/core/logging/factory.go
+++ b/core/logging/factory.go
@@ -1,9 +1,9 @@
package logging
import (
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
)
type Factory func(log log.Logger)
diff --git a/core/logging/logbuilder/builder.go b/core/logging/logbuilder/builder.go
new file mode 100644
index 000000000..1b428f453
--- /dev/null
+++ b/core/logging/logbuilder/builder.go
@@ -0,0 +1,115 @@
+package logbuilder
+
+import (
+ "context"
+ "io"
+ "os"
+ "strings"
+ "time"
+
+ "github.com/expr-lang/expr"
+ "github.com/expr-lang/expr/vm"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/log/logfields"
+ "github.com/pubgo/funk/v2/pretty"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/rs/zerolog"
+ "github.com/samber/lo"
+
+ "github.com/pubgo/lava/v2/core/logging"
+ "github.com/pubgo/lava/v2/core/logging/logkey"
+)
+
+var GlobalHook zerolog.Hook
+
+// New logger
+func New(cfg *logging.Config, hooks []zerolog.Hook) log.Logger {
+ defer recovery.Exit(func(err error) error {
+ pretty.Println(cfg)
+ return err
+ })
+
+ level := zerolog.DebugLevel
+ if cfg.Level != "" {
+ level = result.Wrap(zerolog.ParseLevel(cfg.Level)).Expect("log level is invalid")
+ }
+ zerolog.SetGlobalLevel(level)
+
+ logger := zerolog.New(&writer{os.Stdout}).Level(level).With().Timestamp().Caller().Logger()
+ if !cfg.AsJson {
+ logger = logger.Output(&writer{
+ zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
+ w.Out = os.Stdout
+ w.TimeFormat = time.RFC3339
+ }),
+ })
+ }
+
+ if GlobalHook != nil {
+ hooks = append(hooks, GlobalHook)
+ }
+ logger = logger.Hook(hooks...)
+
+ // 全局log设置
+ ee := logger.With().
+ Str(logkey.Hostname, running.Hostname).
+ Str(logkey.Project, running.Project()).
+ Str(logkey.Version, running.Version())
+
+ if running.Namespace != "" {
+ ee = ee.Str(logkey.Namespace, running.Namespace)
+ }
+
+ filters := lo.Filter(cfg.Filters, func(item string, index int) bool { return strings.TrimSpace(item) != "" })
+ if len(filters) > 0 {
+ expCode := strings.Join(filters, " && ")
+ log.Info().Str(logfields.Msg, "log filter expr").Msg(expCode)
+
+ exp := exprFilter(expCode)
+ log.SetEnableChecker(func(ctx context.Context, lvl log.Level, name, message string, fields log.Fields) bool {
+ envData := map[string]any{"level": lvl.String(), "msg": message, "name": name, "fields": fields}
+ output, err := expr.Run(exp, envData)
+ if err != nil {
+ log.Err(err).Str("expr", expCode).Msg("failed to run log filter expr")
+ }
+ return err == nil && output.(bool)
+ })
+ }
+
+ log.SetLogger(lo.ToPtr(ee.Logger()))
+
+ gl := log.GetLogger("ext")
+ for _, ext := range logging.List() {
+ ext(gl)
+ }
+
+ return log.GetLogger()
+}
+
+type writer struct {
+ io.Writer
+}
+
+func (w writer) Write(p []byte) (n int, err error) {
+ n, err = w.Writer.Write(p)
+ if err != nil {
+ log.Err(err).Str("raw_json", string(p)).Msg("failed to decode invalid json")
+ return n, err
+ }
+
+ return n, err
+}
+
+func exprFilter(code string) *vm.Program {
+ env := map[string]any{"level": "", "name": "", "msg": "", "fields": log.Fields{}}
+
+ program, err := expr.Compile(code, expr.Env(env))
+ if err != nil {
+ log.Err(err).Str("expr", code).Msg("failed to compile log filter expr")
+ assert.Exit(err, code)
+ }
+ return program
+}
diff --git a/core/logging/logext/gologr/log.go b/core/logging/logext/gologr/log.go
new file mode 100644
index 000000000..5f934166e
--- /dev/null
+++ b/core/logging/logext/gologr/log.go
@@ -0,0 +1,104 @@
+package gologr
+
+import (
+ "fmt"
+ "slices"
+
+ "github.com/go-logr/logr"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/rs/zerolog"
+)
+
+var (
+ RenderArgsHook = defaultRender
+ RenderValuesHook = defaultRender
+)
+
+// LogSink implements logr.LogSink and logr.CallDepthLogSink.
+type LogSink struct {
+ l log.Logger
+ keysAndValues []any
+}
+
+var (
+ _ logr.LogSink = &LogSink{}
+ _ logr.CallDepthLogSink = &LogSink{}
+)
+
+func NewSink(l log.Logger) *LogSink {
+ return &LogSink{l: l.WithName("logr")}
+}
+
+func (ls *LogSink) Init(ri logr.RuntimeInfo) {
+ ls.l = ls.l.WithCallerSkip(ri.CallDepth + 2)
+}
+
+func (ls *LogSink) Enabled(level int) bool { return true }
+
+func (ls *LogSink) Info(level int, msg string, keysAndValues ...any) {
+ ls.msg(ls.l.Info(), msg, keysAndValues)
+}
+
+func (ls *LogSink) Error(err error, msg string, keysAndValues ...any) {
+ ls.msg(ls.l.Err(err), msg, keysAndValues)
+}
+
+func (ls *LogSink) msg(e *zerolog.Event, msg string, keysAndValues []any) {
+ if e == nil {
+ return
+ }
+
+ if RenderArgsHook != nil {
+ keysAndValues = RenderArgsHook(keysAndValues)
+ }
+
+ e = e.Fields(keysAndValues)
+ e.Msg(msg)
+}
+
+func (ls *LogSink) copy() *LogSink {
+ return &LogSink{
+ l: ls.l,
+ keysAndValues: slices.Clone(ls.keysAndValues),
+ }
+}
+
+func (ls *LogSink) WithValues(keysAndValues ...any) logr.LogSink {
+ if len(keysAndValues) == 0 {
+ return ls
+ }
+
+ if RenderValuesHook != nil {
+ keysAndValues = RenderValuesHook(keysAndValues)
+ }
+
+ ll := ls.copy()
+ ll.keysAndValues = append(ll.keysAndValues, keysAndValues...)
+
+ return ll
+}
+
+func (ls *LogSink) WithName(name string) logr.LogSink {
+ ll := ls.copy()
+ ll.l = ll.l.WithName(name)
+ return ll
+}
+
+func (ls *LogSink) WithCallDepth(depth int) logr.LogSink {
+ ll := ls.copy()
+ ll.l = ll.l.WithCallerSkip(depth)
+ return ll
+}
+
+func defaultRender(keysAndValues []any) []any {
+ for i, n := 1, len(keysAndValues); i < n; i += 2 {
+ value := keysAndValues[i]
+ switch v := value.(type) {
+ case logr.Marshaler:
+ keysAndValues[i] = v.MarshalLog()
+ case fmt.Stringer:
+ keysAndValues[i] = v.String()
+ }
+ }
+ return keysAndValues
+}
diff --git a/core/logging/logext/gologr/log_test.go b/core/logging/logext/gologr/log_test.go
new file mode 100644
index 000000000..3e74cb26b
--- /dev/null
+++ b/core/logging/logext/gologr/log_test.go
@@ -0,0 +1,24 @@
+package gologr
+
+import (
+ "bytes"
+ "encoding/json"
+ "testing"
+
+ "github.com/go-logr/logr"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestName(t *testing.T) {
+ var buf bytes.Buffer
+ ll := logr.New(NewSink(log.Output(&buf)))
+ ll.Info("test", "hello", 123456)
+
+ data := make(map[string]any)
+ assert.NoError(t, json.Unmarshal(buf.Bytes(), &data))
+ assert.Equal(t, data["hello"], float64(123456))
+ assert.Equal(t, data["level"], "info")
+ assert.Equal(t, data["message"], "test")
+ assert.Contains(t, data["caller"], "otellogger/log_test.go")
+}
diff --git a/core/logging/logext/grpclog/log.go b/core/logging/logext/grpclog/log.go
index 99d2496c9..5ab1dece8 100644
--- a/core/logging/logext/grpclog/log.go
+++ b/core/logging/logext/grpclog/log.go
@@ -3,11 +3,11 @@ package grpclog
import (
"fmt"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
"github.com/rs/zerolog"
"google.golang.org/grpc/grpclog"
- "github.com/pubgo/lava/core/logging"
+ "github.com/pubgo/lava/v2/core/logging"
)
const (
@@ -25,23 +25,22 @@ var _grpcToZapLevel = map[int]zerolog.Level{
grpcLvlFatal: zerolog.FatalLevel,
}
-func grpcComponentName(args interface{}) func(e *zerolog.Event) {
+func grpcComponentName(args any) func(e *zerolog.Event) {
name := args.(string)
return func(e *zerolog.Event) {
e.Str("grpc-component", name[1:len(name)-1])
}
}
-var evt = log.NewEvent().Str("ext", "grpc")
-
func init() {
logging.Register("grpcLog", SetLogger)
}
func SetLogger(logger log.Logger) {
+ logger = logger.WithName("grpc").WithCallerSkip(2)
grpclog.SetLoggerV2(&loggerWrapper{
- log: logger.WithEvent(evt).WithCallerSkip(2),
- depthLog: logger.WithEvent(evt).WithCallerSkip(2),
+ log: logger,
+ depthLog: logger,
})
}
@@ -53,52 +52,52 @@ var (
type loggerWrapper struct {
log log.Logger
depthLog log.Logger
- printFilter func(args ...interface{}) bool
- printfFilter func(format string, args ...interface{}) bool
- printlnFilter func(args ...interface{}) bool
+ printFilter func(args ...any) bool
+ printfFilter func(format string, args ...any) bool
+ printlnFilter func(args ...any) bool
}
-func (l *loggerWrapper) InfoDepth(depth int, args ...interface{}) {
+func (l *loggerWrapper) InfoDepth(depth int, args ...any) {
l.depthLog.WithCallerSkip(depth).Info().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...))
}
-func (l *loggerWrapper) WarningDepth(depth int, args ...interface{}) {
+func (l *loggerWrapper) WarningDepth(depth int, args ...any) {
l.depthLog.WithCallerSkip(depth).Warn().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...))
}
-func (l *loggerWrapper) ErrorDepth(depth int, args ...interface{}) {
+func (l *loggerWrapper) ErrorDepth(depth int, args ...any) {
l.depthLog.WithCallerSkip(depth).Error().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...))
}
-func (l *loggerWrapper) FatalDepth(depth int, args ...interface{}) {
+func (l *loggerWrapper) FatalDepth(depth int, args ...any) {
l.depthLog.WithCallerSkip(depth).Fatal().Func(grpcComponentName(args[0])).Msg(fmt.Sprint(args[1:]...))
}
-func (l *loggerWrapper) SetPrintFilter(filter func(args ...interface{}) bool) {
+func (l *loggerWrapper) SetPrintFilter(filter func(args ...any) bool) {
l.printFilter = filter
}
-func (l *loggerWrapper) SetPrintfFilter(filter func(format string, args ...interface{}) bool) {
+func (l *loggerWrapper) SetPrintfFilter(filter func(format string, args ...any) bool) {
l.printfFilter = filter
}
-func (l *loggerWrapper) SetPrintlnFilter(filter func(args ...interface{}) bool) {
+func (l *loggerWrapper) SetPrintlnFilter(filter func(args ...any) bool) {
l.printlnFilter = filter
}
-func (l *loggerWrapper) filter(args ...interface{}) bool {
+func (l *loggerWrapper) filter(args ...any) bool {
return l.printFilter != nil && l.printFilter(args...)
}
-func (l *loggerWrapper) filterf(format string, args ...interface{}) bool {
+func (l *loggerWrapper) filterf(format string, args ...any) bool {
return l.printfFilter != nil && l.printfFilter(format, args...)
}
-func (l *loggerWrapper) filterln(args ...interface{}) bool {
+func (l *loggerWrapper) filterln(args ...any) bool {
return l.printlnFilter != nil && l.printlnFilter(args...)
}
-func (l *loggerWrapper) Info(args ...interface{}) {
+func (l *loggerWrapper) Info(args ...any) {
if l.filter(args) {
return
}
@@ -106,7 +105,7 @@ func (l *loggerWrapper) Info(args ...interface{}) {
l.log.Info().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Infoln(args ...interface{}) {
+func (l *loggerWrapper) Infoln(args ...any) {
if l.filterln(args) {
return
}
@@ -114,7 +113,7 @@ func (l *loggerWrapper) Infoln(args ...interface{}) {
l.log.Info().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Infof(format string, args ...interface{}) {
+func (l *loggerWrapper) Infof(format string, args ...any) {
if l.filterf(format, args...) {
return
}
@@ -122,7 +121,7 @@ func (l *loggerWrapper) Infof(format string, args ...interface{}) {
l.log.Info().Msg(fmt.Sprintf(format, args...))
}
-func (l *loggerWrapper) Warning(args ...interface{}) {
+func (l *loggerWrapper) Warning(args ...any) {
if l.filter(args...) {
return
}
@@ -130,7 +129,7 @@ func (l *loggerWrapper) Warning(args ...interface{}) {
l.log.Warn().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Warningln(args ...interface{}) {
+func (l *loggerWrapper) Warningln(args ...any) {
if l.filterln(args) {
return
}
@@ -138,7 +137,7 @@ func (l *loggerWrapper) Warningln(args ...interface{}) {
l.log.Warn().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Warningf(format string, args ...interface{}) {
+func (l *loggerWrapper) Warningf(format string, args ...any) {
if l.filterf(format, args...) {
return
}
@@ -146,7 +145,7 @@ func (l *loggerWrapper) Warningf(format string, args ...interface{}) {
l.log.Warn().Msg(fmt.Sprintf(format, args...))
}
-func (l *loggerWrapper) Error(args ...interface{}) {
+func (l *loggerWrapper) Error(args ...any) {
if l.filter(args...) {
return
}
@@ -154,7 +153,7 @@ func (l *loggerWrapper) Error(args ...interface{}) {
l.log.Error().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Errorln(args ...interface{}) {
+func (l *loggerWrapper) Errorln(args ...any) {
if l.filterln(args) {
return
}
@@ -162,7 +161,7 @@ func (l *loggerWrapper) Errorln(args ...interface{}) {
l.log.Error().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Errorf(format string, args ...interface{}) {
+func (l *loggerWrapper) Errorf(format string, args ...any) {
if l.filterf(format, args...) {
return
}
@@ -170,7 +169,7 @@ func (l *loggerWrapper) Errorf(format string, args ...interface{}) {
l.log.Error().Msg(fmt.Sprintf(format, args...))
}
-func (l *loggerWrapper) Fatal(args ...interface{}) {
+func (l *loggerWrapper) Fatal(args ...any) {
if l.filter(args...) {
return
}
@@ -178,7 +177,7 @@ func (l *loggerWrapper) Fatal(args ...interface{}) {
l.log.Fatal().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Fatalln(args ...interface{}) {
+func (l *loggerWrapper) Fatalln(args ...any) {
if l.filterln(args) {
return
}
@@ -186,7 +185,7 @@ func (l *loggerWrapper) Fatalln(args ...interface{}) {
l.log.Fatal().Msg(fmt.Sprint(args...))
}
-func (l *loggerWrapper) Fatalf(format string, args ...interface{}) {
+func (l *loggerWrapper) Fatalf(format string, args ...any) {
if l.filterf(format, args...) {
return
}
diff --git a/core/logging/logext/grpclog/log_test.go b/core/logging/logext/grpclog/log_test.go
index df4670874..968abc777 100644
--- a/core/logging/logext/grpclog/log_test.go
+++ b/core/logging/logext/grpclog/log_test.go
@@ -3,7 +3,7 @@ package grpclog
import (
"testing"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
"google.golang.org/grpc/grpclog"
)
diff --git a/core/logging/logext/slog/log.go b/core/logging/logext/slog/log.go
new file mode 100644
index 000000000..bf5241a34
--- /dev/null
+++ b/core/logging/logext/slog/log.go
@@ -0,0 +1,17 @@
+package slog
+
+import (
+ "log/slog"
+
+ "github.com/pubgo/funk/v2/log"
+
+ "github.com/pubgo/lava/v2/core/logging"
+)
+
+func init() {
+ logging.Register("slog", SetLogger)
+}
+
+func SetLogger(logger log.Logger) {
+ slog.SetDefault(slog.New(log.NewSlog(logger.WithName("slog"))))
+}
diff --git a/core/logging/logext/slog/z_log_test.go b/core/logging/logext/slog/z_log_test.go
new file mode 100644
index 000000000..9efff7191
--- /dev/null
+++ b/core/logging/logext/slog/z_log_test.go
@@ -0,0 +1,21 @@
+package slog
+
+import (
+ "log/slog"
+ "testing"
+
+ "github.com/pubgo/funk/v2/log"
+)
+
+func TestName(t *testing.T) {
+ SetLogger(log.GetLogger("testing"))
+
+ slog.Info("hello")
+ slog.With(slog.String("name", "abc")).Info("hello")
+ slog.With(slog.String("name", "abc")).Info("hello", "data-map", map[string]any{
+ "abc": 1,
+ "hello": map[string]any{
+ "world": 2,
+ },
+ })
+}
diff --git a/core/logging/logext/stdlog/log.go b/core/logging/logext/stdlog/log.go
index 3660ffef6..a5cf5cc89 100644
--- a/core/logging/logext/stdlog/log.go
+++ b/core/logging/logext/stdlog/log.go
@@ -5,12 +5,11 @@ import (
"io"
"log"
- "github.com/pubgo/funk/convert"
- logger "github.com/pubgo/funk/log"
- "github.com/pubgo/lava/core/logging"
-)
+ "github.com/pubgo/funk/v2/convert"
+ logger "github.com/pubgo/funk/v2/log"
-var evt = logger.NewEvent().Str("ext", "std")
+ "github.com/pubgo/lava/v2/core/logging"
+)
func init() {
logging.Register("stdLog", SetLogger)
@@ -21,7 +20,7 @@ func SetLogger(logger logger.Logger) {
stdLog := log.Default()
// 接管系统默认log
- *stdLog = *log.New(&std{l: logger.WithEvent(evt).WithCallerSkip(3)}, "", 0)
+ *stdLog = *log.New(&std{l: logger.WithName("std").WithCallerSkip(3)}, "", 0)
}
var _ io.Writer = (*std)(nil)
diff --git a/core/logging/logext/stdlog/log_test.go b/core/logging/logext/stdlog/log_test.go
index 330253707..70f1568e6 100644
--- a/core/logging/logext/stdlog/log_test.go
+++ b/core/logging/logext/stdlog/log_test.go
@@ -4,7 +4,7 @@ import (
stdLog "log"
"testing"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
)
func TestName(t *testing.T) {
diff --git a/core/metrics/aaa.go b/core/metrics/aaa.go
index e683f027f..a4cf0d68c 100644
--- a/core/metrics/aaa.go
+++ b/core/metrics/aaa.go
@@ -1,7 +1,7 @@
package metrics
import (
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
tally "github.com/uber-go/tally/v4"
)
diff --git a/core/metrics/config.go b/core/metrics/config.go
index 4b36fd3f7..1a38b0150 100644
--- a/core/metrics/config.go
+++ b/core/metrics/config.go
@@ -3,7 +3,7 @@ package metrics
import (
"time"
- "github.com/pubgo/funk/config"
+ "github.com/pubgo/funk/v2/config"
)
var Name = "metric"
diff --git a/core/metrics/drivers/prometheus/reporter.go b/core/metrics/drivers/prometheus/reporter.go
index d3ab55c8c..3ef668411 100644
--- a/core/metrics/drivers/prometheus/reporter.go
+++ b/core/metrics/drivers/prometheus/reporter.go
@@ -1,14 +1,13 @@
package prometheus
import (
- "github.com/prometheus/common/model"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/log"
tally "github.com/uber-go/tally/v4"
"github.com/uber-go/tally/v4/prometheus"
- "github.com/pubgo/lava/core/debug"
- "github.com/pubgo/lava/core/metrics"
+ "github.com/pubgo/lava/v2/core/debug"
+ "github.com/pubgo/lava/v2/core/metrics"
)
const (
@@ -27,8 +26,7 @@ func New(conf *metrics.Config, log log.Logger) *tally.ScopeOptions {
opts := tally.ScopeOptions{}
opts.Separator = prometheus.DefaultSeparator
- //opts.SanitizeOptions = &prometheus.DefaultSanitizerOpts
- model.NameValidationScheme = model.UTF8Validation
+ // opts.SanitizeOptions = &prometheus.DefaultSanitizerOpts
proCfg := &prometheus.Configuration{TimerType: "histogram"}
diff --git a/core/metrics/factory.go b/core/metrics/factory.go
index 0c23e15d3..3e303c6e9 100644
--- a/core/metrics/factory.go
+++ b/core/metrics/factory.go
@@ -1,8 +1,8 @@
package metrics
import (
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/recovery"
)
var factories = make(map[string]Factory)
diff --git a/core/metrics/metric.go b/core/metrics/metric.go
deleted file mode 100644
index 030cc942b..000000000
--- a/core/metrics/metric.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package metrics
-
-import (
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/merge"
- "github.com/pubgo/funk/version"
- lo "github.com/samber/lo"
- "github.com/uber-go/tally/v4"
-
- "github.com/pubgo/lava/core/lifecycle"
-)
-
-func New(m lifecycle.Lifecycle, cfg *Config, log log.Logger) Metric {
- cfg = merge.Struct(generic.Ptr(DefaultCfg()), cfg).Unwrap()
-
- log = log.WithName(Name)
-
- factory := Get(cfg.Driver)
- assert.If(factory == nil, "driver factory[%s] not found", cfg.Driver)
- opts := factory(cfg, log.WithName("driver"))
- if opts == nil {
- return tally.NoopScope
- }
-
- opts.Tags = Tags{"project": version.Project()}
-
- scope, closer := tally.NewRootScope(lo.FromPtr(opts), cfg.Interval)
- m.BeforeStop(func() { assert.Must(closer.Close()) })
-
- registerVars(scope)
- return scope
-}
diff --git a/core/metrics/metricbuilder/builder.go b/core/metrics/metricbuilder/builder.go
new file mode 100644
index 000000000..1a0d79dbb
--- /dev/null
+++ b/core/metrics/metricbuilder/builder.go
@@ -0,0 +1,36 @@
+package metricbuilder
+
+import (
+ "context"
+
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/merge"
+ lo "github.com/samber/lo"
+ "github.com/uber-go/tally/v4"
+
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/metrics"
+)
+
+func New(m lifecycle.Lifecycle, cfg *metrics.Config, log log.Logger) metrics.Metric {
+ cfg = merge.Struct(lo.ToPtr(metrics.DefaultCfg()), cfg).Unwrap()
+
+ log = log.WithName(metrics.Name)
+
+ factory := metrics.Get(cfg.Driver)
+ assert.If(factory == nil, "driver factory[%s] not found", cfg.Driver)
+ opts := factory(cfg, log.WithName("driver"))
+ if opts == nil {
+ return tally.NoopScope
+ }
+
+ opts.Tags = metrics.Tags{"project": version.Project()}
+
+ scope, closer := tally.NewRootScope(lo.FromPtr(opts), cfg.Interval)
+ m.BeforeStop(func(ctx context.Context) error { return closer.Close() })
+
+ registerVars(scope)
+ return scope
+}
diff --git a/core/metrics/metricbuilder/vars.go b/core/metrics/metricbuilder/vars.go
new file mode 100644
index 000000000..842472c5b
--- /dev/null
+++ b/core/metrics/metricbuilder/vars.go
@@ -0,0 +1,27 @@
+package metricbuilder
+
+import (
+ "github.com/pubgo/funk/v2/typex"
+ "github.com/pubgo/funk/v2/vars"
+ "github.com/uber-go/tally/v4"
+
+ "github.com/pubgo/lava/v2/core/metrics"
+)
+
+func registerVars(m metrics.Metric) {
+ vars.Register(vars.UniqueName(metrics.Name, "capabilities"), func() any {
+ c := m.Capabilities()
+ return typex.Ctx{
+ "reporting": c.Reporting(),
+ "tagging": c.Tagging(),
+ }
+ })
+
+ vars.Register(vars.UniqueName(metrics.Name, "snapshot"), func() any {
+ if c, ok := m.(tally.TestScope); ok {
+ // TODO 数据序列化处理
+ return c.Snapshot()
+ }
+ return nil
+ })
+}
diff --git a/core/metrics/vars.go b/core/metrics/vars.go
deleted file mode 100644
index 24d741b97..000000000
--- a/core/metrics/vars.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package metrics
-
-import (
- "github.com/pubgo/funk/typex"
- "github.com/pubgo/funk/vars"
- "github.com/uber-go/tally/v4"
-)
-
-func registerVars(m Metric) {
- vars.Register(Name+"_capabilities", func() interface{} {
- c := m.Capabilities()
- return typex.Ctx{
- "reporting": c.Reporting(),
- "tagging": c.Tagging(),
- }
- })
-
- vars.Register(Name+"_snapshot", func() interface{} {
- if c, ok := m.(tally.TestScope); ok {
- // TODO 数据序列化处理
- return c.Snapshot()
- }
- return nil
- })
-}
diff --git a/core/pidfile/builder.go b/core/pidfile/builder.go
deleted file mode 100644
index 1ca2f406d..000000000
--- a/core/pidfile/builder.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package pidfile
-
-import (
- "path/filepath"
-
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/pathutil"
-
- "github.com/pubgo/lava/core/lifecycle"
-)
-
-func New() lifecycle.Handler {
- return func(lc lifecycle.Lifecycle) {
- pidPath = filepath.Join(config.GetConfigDir(), Name)
-
- _ = pathutil.IsNotExistMkDir(pidPath)
-
- lc.AfterStart(func() { assert.Must(SavePid()) })
- }
-}
diff --git a/core/pidfile/pidfile.go b/core/pidfile/pidfile.go
index 8efb60336..592a45154 100644
--- a/core/pidfile/pidfile.go
+++ b/core/pidfile/pidfile.go
@@ -5,44 +5,57 @@ import (
"os"
"path/filepath"
"strconv"
- "syscall"
+ "sync"
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/funk/running"
+ "github.com/pubgo/funk/v2/config"
+ "github.com/pubgo/funk/v2/log/logfields"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/rs/zerolog"
)
-const Name = "pidfile"
-
-var pidPath = filepath.Join(config.GetConfigDir(), Name)
-
-const pidPerm os.FileMode = 0o666
-
-func GetPid() result.Result[int] {
- f := GetPidF()
- if f.IsErr() {
- return result.Err[int](f.Err())
+var getPidPath = sync.OnceValue(func() string {
+ return filepath.Join(config.GetConfigDir(), "."+running.Project()+".pid")
+})
+
+const pidPerm os.FileMode = 0o644
+
+func Get() (r result.Result[int]) {
+ pidPath := GetPath()
+ p := result.Wrap(os.ReadFile(pidPath)).
+ Validate(func(val []byte) error {
+ if len(val) == 0 {
+ return fmt.Errorf("pid file is empty")
+ }
+ return nil
+ }).
+ Log(func(e *zerolog.Event) {
+ e.Str("path", pidPath)
+ e.Str(logfields.Msg, "read pid file failed")
+ }).
+ UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
}
- p, err := os.ReadFile(f.Unwrap())
- if err != nil {
- return result.Wrap(0, err)
- }
-
- return result.Wrap(strconv.Atoi(string(p)))
+ return result.Wrap(strconv.Atoi(string(p))).
+ Log(func(e *zerolog.Event) {
+ e.Str("path", pidPath)
+ e.Str("pid", string(p))
+ e.Str(logfields.Msg, "convert pid to int failed")
+ })
}
-func GetPidF() result.Result[string] {
- filename := fmt.Sprintf("%s.pid", running.Project)
- return result.OK(filepath.Join(pidPath, filename))
-}
+func GetPath() string { return getPidPath() }
-func SavePid() error {
- f := GetPidF()
- if f.IsErr() {
- return f.Err()
- }
+func Save() (r result.Error) {
+ pidPath := GetPath()
+ pid := os.Getpid()
- pid := syscall.Getpid()
- return os.WriteFile(f.Unwrap(), []byte(strconv.Itoa(pid)), pidPerm)
+ return result.ErrOf(os.WriteFile(pidPath, []byte(strconv.Itoa(pid)), pidPerm)).
+ Log(func(e *zerolog.Event) {
+ e.Str("path", pidPath)
+ e.Int("pid", pid)
+ e.Str(logfields.Msg, "write pid file failed")
+ })
}
diff --git a/core/pidfile/pidfilebuilder/builder.go b/core/pidfile/pidfilebuilder/builder.go
new file mode 100644
index 000000000..b1edb3c38
--- /dev/null
+++ b/core/pidfile/pidfilebuilder/builder.go
@@ -0,0 +1,14 @@
+package pidfilebuilder
+
+import (
+ "context"
+
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/pidfile"
+)
+
+func New() lifecycle.Handler {
+ return func(lc lifecycle.Lifecycle) {
+ lc.AfterStart(func(ctx context.Context) error { return pidfile.Save().GetErr() })
+ }
+}
diff --git a/core/registry/aaa.go b/core/registry/aaa.go
index 3f8f3ba7d..4ffe2a995 100644
--- a/core/registry/aaa.go
+++ b/core/registry/aaa.go
@@ -4,7 +4,7 @@ package registry
import (
"context"
- "github.com/pubgo/lava/core/service"
+ "github.com/pubgo/lava/v2/core/service"
)
// Registry The registry provides an interface for service discovery
diff --git a/core/registry/builder.go b/core/registry/builder.go
index 6bb03b4b2..e7b8716ff 100644
--- a/core/registry/builder.go
+++ b/core/registry/builder.go
@@ -7,17 +7,17 @@ import (
"strings"
"time"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/async"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/version"
-
- "github.com/pubgo/lava/core/lifecycle"
- "github.com/pubgo/lava/core/service"
- "github.com/pubgo/lava/internal/logutil"
- "github.com/pubgo/lava/pkg/netutil"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/async"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/running"
+
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/service"
+ "github.com/pubgo/lava/v2/internal/logutil"
+ "github.com/pubgo/lava/v2/pkg/netutil"
)
func New(c *Config, lifecycle lifecycle.Lifecycle, regs map[string]Registry) {
@@ -31,14 +31,14 @@ func New(c *Config, lifecycle lifecycle.Lifecycle, regs map[string]Registry) {
return &errors.Err{
Msg: "registry driver is null",
Tags: errors.Tags{
- errors.T("driver", cfg.Driver),
- errors.T("regs", regs),
+ "driver": cfg.Driver,
+ "regs": regs,
},
}
})
// 服务注册
- lifecycle.AfterStart(func() {
+ lifecycle.AfterStart(func(ctx context.Context) error {
SetDefault(reg)
register(reg)
@@ -65,17 +65,19 @@ func New(c *Config, lifecycle lifecycle.Lifecycle, regs map[string]Registry) {
})
// 服务撤销
- lifecycle.BeforeStop(func() {
+ lifecycle.BeforeStop(func(ctx context.Context) error {
cancel()
deregister(reg)
+ return nil
})
+ return nil
})
}
func register(reg Registry) {
// parse address for host, port
var advt, host string
- port := running.GrpcPort
+ port := running.GrpcPort()
parts := strings.Split(advt, ":")
if len(parts) > 1 {
@@ -94,12 +96,12 @@ func register(reg Registry) {
Port: port,
Version: version.Version(),
Address: fmt.Sprintf("%s:%d", host, port),
- Id: running.Project + "-" + running.Hostname + "-" + running.InstanceID,
+ Id: running.Project() + "-" + running.Hostname + "-" + running.InstanceID,
Metadata: map[string]string{"registry": reg.String()},
}
s := &service.Service{
- Name: running.Project,
+ Name: running.Project(),
Nodes: []*service.Node{node},
}
@@ -108,18 +110,18 @@ func register(reg Registry) {
"register service node",
func() error {
err := reg.Register(context.Background(), s)
- return errors.WrapTag(err,
- errors.T("instance_id", node.Id),
- errors.T("service", running.Project),
- errors.T("registry", Default().String()),
- )
+ return errors.WrapTags(err, errors.Tags{
+ "instance_id": node.Id,
+ "service": running.Project,
+ "registry": Default().String(),
+ })
},
)
}
func deregister(reg Registry) {
var advt, host string
- port := running.GrpcPort
+ port := running.GrpcPort()
parts := strings.Split(advt, ":")
if len(parts) > 1 {
@@ -133,12 +135,12 @@ func deregister(reg Registry) {
node := &service.Node{
Port: port,
Address: fmt.Sprintf("%s:%d", host, port),
- Id: running.Project + "-" + running.Hostname + "-" + running.InstanceID,
+ Id: running.Project() + "-" + running.Hostname + "-" + running.InstanceID,
Metadata: make(map[string]string),
}
s := &service.Service{
- Name: running.Project,
+ Name: running.Project(),
Nodes: []*service.Node{node},
}
@@ -147,10 +149,10 @@ func deregister(reg Registry) {
"deregister service node",
func() error {
err := reg.Deregister(context.Background(), s)
- return errors.WrapTag(err,
- errors.T("id", node.Id),
- errors.T("name", running.Project),
- )
+ return errors.WrapTags(err, errors.Tags{
+ "id": node.Id,
+ "name": running.Project,
+ })
},
)
}
diff --git a/core/registry/config.go b/core/registry/config.go
index 69888992e..791b58696 100644
--- a/core/registry/config.go
+++ b/core/registry/config.go
@@ -4,8 +4,8 @@ import (
"fmt"
"time"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/errors"
)
// https://github.com/go-eagle/eagle/blob/master/pkg/registry/registry.go
@@ -33,9 +33,9 @@ const (
)
type Config struct {
- RegisterInterval time.Duration `yaml:"registerInterval"`
- Driver string `json:"driver" yaml:"driver"`
- DriverCfg map[string]interface{} `json:"driver_config" yaml:"driver_config"`
+ RegisterInterval time.Duration `yaml:"registerInterval"`
+ Driver string `json:"driver" yaml:"driver"`
+ DriverCfg map[string]any `json:"driver_config" yaml:"driver_config"`
}
func (cfg *Config) Check() *Config {
diff --git a/core/registry/drivers/mdns/mdns.go b/core/registry/drivers/mdns/mdns.go
index 2c4e927e8..cc5a13f71 100644
--- a/core/registry/drivers/mdns/mdns.go
+++ b/core/registry/drivers/mdns/mdns.go
@@ -5,15 +5,15 @@ import (
"context"
"github.com/grandcat/zeroconf"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/merge"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/typex"
-
- "github.com/pubgo/lava/core/registry"
- "github.com/pubgo/lava/core/service"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/merge"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/typex"
+
+ "github.com/pubgo/lava/v2/core/registry"
+ "github.com/pubgo/lava/v2/core/service"
)
const (
@@ -28,7 +28,7 @@ func New(conf *registry.Config, log log.Logger) registry.Registry {
}
var cfg Cfg
- merge.MapStruct(&cfg, conf.DriverCfg).Unwrap()
+ merge.MapStruct(&cfg, conf.DriverCfg).Must()
resolver, err := zeroconf.NewResolver()
assert.MustF(err, "Failed to initialize zeroconf resolver")
@@ -68,7 +68,7 @@ func (m *mdnsRegistry) Register(ctx context.Context, service *service.Service, o
// 已经存在
if m.services.Has(node.Id) {
- return
+ return gErr
}
server, err := zeroconf.Register(node.Id, service.Name, zeroconfDomain, node.GetPort(), []string{node.Id}, nil)
@@ -79,12 +79,14 @@ func (m *mdnsRegistry) Register(ctx context.Context, service *service.Service, o
optList[i](&opts)
}
+ _ = opts
+
m.services.Set(node.Id, &serverNode{
srv: server,
id: node.Id,
name: service.Name,
})
- return
+ return gErr
}
func (m *mdnsRegistry) Deregister(ctx context.Context, service *service.Service, opt ...registry.DeregOpt) (gErr error) {
@@ -98,11 +100,11 @@ func (m *mdnsRegistry) Deregister(ctx context.Context, service *service.Service,
node := service.Nodes[0]
val, ok := m.services.LoadAndDelete(node.Id)
if !ok || val == nil {
- return
+ return gErr
}
val.(*serverNode).srv.Shutdown()
- return
+ return gErr
}
func (m *mdnsRegistry) String() string { return Name }
diff --git a/core/registry/options.go b/core/registry/options.go
index f345ea505..464a3e7fb 100644
--- a/core/registry/options.go
+++ b/core/registry/options.go
@@ -5,7 +5,7 @@ import (
"crypto/tls"
"time"
- "github.com/pubgo/lava/core/service"
+ "github.com/pubgo/lava/v2/core/service"
)
func TTL(dur time.Duration) RegOpt {
diff --git a/core/responses/response.go b/core/responses/response.go
deleted file mode 100644
index cd5639f57..000000000
--- a/core/responses/response.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package responses
-
-import (
- "github.com/pubgo/funk/proto/errorpb"
-)
-
-type Response struct {
- Err *errorpb.ErrCode `json:"err"`
- Data any `json:"data"`
-}
diff --git a/core/responses/response_test.go b/core/responses/response_test.go
deleted file mode 100644
index d2f612c5c..000000000
--- a/core/responses/response_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package responses
-
-import (
- "encoding/json"
- "testing"
-
- "github.com/pubgo/funk/proto/testcodepb"
- "github.com/stretchr/testify/assert"
-)
-
-func TestResponse(t *testing.T) {
- rsp := &Response{
- Err: testcodepb.ErrCodeDbConn,
- Data: nil,
- }
- bytes, err1 := json.Marshal(rsp)
- assert.Nil(t, err1)
- t.Log(string(bytes))
-}
diff --git a/core/rpcmeta/registry.go b/core/rpcmeta/registry.go
index 8f849b175..918635f74 100644
--- a/core/rpcmeta/registry.go
+++ b/core/rpcmeta/registry.go
@@ -1,7 +1,7 @@
package rpcmeta
import (
- "github.com/pubgo/funk/assert"
+ "github.com/pubgo/funk/v2/assert"
)
var rpcMetas = make(map[string]*RpcMeta)
diff --git a/core/scheduler/aaa.go b/core/scheduler/aaa.go
new file mode 100644
index 000000000..da262b65f
--- /dev/null
+++ b/core/scheduler/aaa.go
@@ -0,0 +1,127 @@
+package scheduler
+
+import (
+ "context"
+ "time"
+
+ "github.com/pubgo/funk/v2/clone"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/stack"
+ "github.com/reugn/go-quartz/quartz"
+ "go.uber.org/atomic"
+)
+
+type JobExecutor interface {
+ Name() string
+ Exec(ctx context.Context, name string, metadata *JobMetadata) result.Result[[]byte]
+}
+
+var _ JobExecutor = (JobFunc)(nil)
+
+type JobFunc func(ctx context.Context, name string, metadata *JobMetadata) result.Result[[]byte]
+
+func (j JobFunc) Name() string {
+ return stack.CallerWithFunc(j).String()
+}
+
+func (j JobFunc) Exec(ctx context.Context, name string, metadata *JobMetadata) result.Result[[]byte] {
+ return j(ctx, name, metadata)
+}
+
+type JobRegister interface {
+ RegisterSchedulerJob(reg JobRegistry)
+}
+
+type JobRegistry interface {
+ Once(name string, delay time.Duration, fn JobFunc) result.Error
+ Every(name string, dur time.Duration, fn JobFunc) result.Error
+ Cron(name, expr string, fn JobFunc) result.Error
+}
+
+type JobManager interface {
+ CreateJob(spec JobSpec) result.Error
+ PatchJob(name string, config *JobConfig) result.Error
+ PauseJob(name string) result.Error
+ ResumeJob(name string) result.Error
+ DeleteJob(name string) result.Error
+ ReloadJob(name string) result.Error
+ ListJobs() []*Job
+ GetJob(name string) result.Result[*Job]
+}
+
+type JobSpec struct {
+ Name string
+ Config *JobConfig
+ Executor string
+ Once *OnceJob
+ Ticker *TickerJob
+ Cron *CronJob
+}
+
+type OnceJob struct {
+ Delay time.Duration
+}
+
+type TickerJob struct {
+ Dur time.Duration
+}
+
+type CronJob struct {
+ Expr string
+}
+
+type Job struct {
+ Spec *JobSpec
+ Metadata JobMetadata
+ Status Status
+
+ PreExecTime int64
+ ExecTime int64
+
+ Error error
+ Result []byte
+ Runs uint64
+}
+
+type jobTask struct {
+ spec *JobSpec
+ executor JobExecutor
+
+ trigger *triggerImpl
+ runs atomic.Uint64
+ jobKey *quartz.JobKey
+ status Status
+
+ result result.Result[[]byte]
+}
+
+func (job jobTask) ToJob() *Job {
+ return &Job{
+ Status: job.status,
+ PreExecTime: job.trigger.prev,
+ ExecTime: job.trigger.next,
+ Error: job.result.GetErr(),
+ Result: job.result.UnwrapOrEmpty(),
+ Runs: job.runs.Load(),
+ Spec: clone.Clone(job.spec),
+ }
+}
+
+type Status string
+
+const (
+ StatusRunning Status = "running"
+ StatusStop Status = "stop"
+)
+
+type JobMetadata struct {
+ Name string
+ Timeout time.Duration
+ MaxRetries int
+ RetryInterval time.Duration
+ Replace bool
+ Location string
+
+ NextExecTime int64
+ ExecTime int64
+}
diff --git a/core/scheduler/builder.go b/core/scheduler/builder.go
index e2e573290..ff3deb1ef 100644
--- a/core/scheduler/builder.go
+++ b/core/scheduler/builder.go
@@ -2,46 +2,80 @@ package scheduler
import (
"context"
- "fmt"
+ "log/slog"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/log/logfields"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/vars"
+ qlog "github.com/reugn/go-quartz/logger"
"github.com/reugn/go-quartz/quartz"
+ "github.com/rs/zerolog"
- "github.com/pubgo/lava/core/lifecycle"
- "github.com/pubgo/lava/core/metrics"
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/metrics"
+ "github.com/pubgo/lava/v2/core/supervisor"
)
const Name = "scheduler"
-func New(m lifecycle.Lifecycle, log log.Logger, opts []*Config, routers []CronRouter, metric metrics.Metric) *Scheduler {
- config := make(map[string]JobSetting)
- if len(opts) > 0 && opts[0] != nil {
- for _, setting := range opts[0].JobSettings {
- if _, ok := config[setting.Name]; ok {
- panic(fmt.Sprintf("schedule job(%s) exists", setting.Name))
- }
+type Params struct {
+ M lifecycle.Lifecycle
+ Log log.Logger
+ Configs []*Config
+ Routers []JobRegister
+ Metric metrics.Metric
+ Executors []JobExecutor
+}
- config[setting.Name] = setting
- }
- }
+type ResponseParams struct {
+ Service supervisor.Service
+ Manager JobManager
+}
+
+func New(m lifecycle.Lifecycle, logger log.Logger, metric metrics.Metric, configs []*Config, routers []JobRegister, executors []JobExecutor) (_ *Scheduler, gErr error) {
+ defer result.RecoveryErr(&gErr)
+
+ configMap := result.Wrap(createConfig(configs)).
+ UnwrapOrLog(func(e *zerolog.Event) {
+ e.Any("configs", configs)
+ e.Any(logfields.Msg, "failed to create config")
+ })
ctx, cancel := context.WithCancel(context.Background())
- quart := &Scheduler{
- metric: metric,
- config: config,
- scheduler: quartz.NewStdScheduler(),
- log: log.WithName(Name),
- ctx: ctx,
- cancel: cancel,
- jobs: make(map[string]JobFunc),
+
+ slogLogger := qlog.NewSlogLogger(ctx, slog.With(slog.String(logfields.Module, Name)))
+ scheduler := result.Wrap(quartz.NewStdScheduler(quartz.WithLogger(slogLogger), quartz.WithJobMetadata())).
+ UnwrapOrLog(func(e *zerolog.Event) {
+ e.Str(logfields.Msg, "failed to create scheduler")
+ })
+
+ jobExecutors := make(map[string]JobExecutor)
+ for _, executor := range executors {
+ regJobExecutor(jobExecutors, executor).
+ MustWithLog(func(e *zerolog.Event) {
+ e.Str(logfields.Msg, "failed to register job executor")
+ })
}
- quart.start()
- m.BeforeStop(quart.stop)
+ quart := &Scheduler{
+ metric: metric,
+ configMap: configMap,
+ scheduler: scheduler,
+ log: logger.WithName(Name),
+ ctx: ctx,
+ cancel: cancel,
+ jobExecutors: jobExecutors,
+ }
for _, r := range routers {
- r.Crontab(quart)
+ r.RegisterSchedulerJob(quart)
}
- return quart
+ m.AfterStart(func(ctx context.Context) error { return lifecycle.WrapNoCtxErr(quart.start)(ctx) })
+ m.BeforeStop(func(ctx context.Context) error { return lifecycle.WrapNoCtxErr(quart.stop)(ctx) })
+
+ vars.Register(vars.UniqueName(Name), func() any { return quart.ListJobs() })
+
+ return quart, nil
}
diff --git a/core/scheduler/config.go b/core/scheduler/config.go
index 5174d1a29..687afa3f6 100644
--- a/core/scheduler/config.go
+++ b/core/scheduler/config.go
@@ -1,13 +1,128 @@
package scheduler
-type JobSetting struct {
- Disabled bool `yaml:"disabled"`
- Schedule string `yaml:"schedule"`
- Name string `yaml:"name"`
- Timeout string `yaml:"timeout"`
+import (
+ "fmt"
+ "time"
+
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log/logfields"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/reugn/go-quartz/quartz"
+ "github.com/rs/zerolog"
+ "github.com/samber/lo"
+)
+
+func createConfig(configs []*Config) (map[string]*JobConfig, error) {
+ configMap := make(map[string]*JobConfig)
+ if len(configs) == 0 || configs[0] == nil {
+ return configMap, nil
+ }
+
+ for _, config := range configs[0].JobConfigs {
+ if config.Name == "" {
+ return nil, errors.Errorf("schedule job name is empty")
+ }
+
+ if _, ok := configMap[config.Name]; ok {
+ return nil, errors.Errorf("schedule job(%s) exists", config.Name)
+ }
+ }
+ return configMap, nil
+}
+
+func defaultConfig(name string) *JobConfig {
+ return &JobConfig{
+ Name: name,
+ Disabled: lo.ToPtr(false),
+ Timeout: lo.ToPtr(time.Second * 10),
+ RetryInterval: lo.ToPtr(time.Second),
+ MaxRetries: lo.ToPtr(3),
+ Replace: lo.ToPtr(false),
+ Location: lo.ToPtr(time.UTC.String()),
+ location: time.UTC,
+ }
+}
+
+func initAndMergeConfig(name string, jobConfigs ...*JobConfig) (r result.Result[*JobConfig]) {
+ defer result.Recovery(&r)
+
+ cfg := defaultConfig(name)
+ for _, jobConfig := range jobConfigs {
+ if jobConfig == nil {
+ continue
+ }
+
+ if jobConfig.Disabled != nil {
+ cfg.Disabled = jobConfig.Disabled
+ }
+
+ if jobConfig.Timeout != nil {
+ cfg.Timeout = jobConfig.Timeout
+ }
+
+ if jobConfig.RetryInterval != nil {
+ cfg.RetryInterval = jobConfig.RetryInterval
+ }
+
+ if jobConfig.MaxRetries != nil {
+ cfg.MaxRetries = jobConfig.MaxRetries
+ }
+
+ if jobConfig.Replace != nil {
+ cfg.Replace = jobConfig.Replace
+ }
+
+ if jobConfig.Location != nil {
+ cfg.Location = jobConfig.Location
+ }
+
+ cfg.location = result.Wrap(time.LoadLocation(lo.FromPtr(cfg.Location))).
+ UnwrapOrLog(func(e *zerolog.Event) {
+ e.Str(logfields.Msg, fmt.Sprintf("failed to parse time location:%s", lo.FromPtr(cfg.Location)))
+ })
+ }
+
+ return r.WithValue(cfg)
+}
+
+type JobConfig struct {
+ Disabled *bool `yaml:"disabled"`
+ Name string `yaml:"name"`
+ Timeout *time.Duration `yaml:"timeout"`
+
+ // MaxRetries is the maximum number of retries before aborting the
+ // current job execution.
+ // Default: 0.
+ MaxRetries *int `yaml:"max_retries"`
+
+ // RetryInterval is the fixed time interval between retry attempts.
+ // Default: 1 second.
+ RetryInterval *time.Duration `yaml:"retry_interval"`
+
+ // Replace indicates whether the job should replace an existing job
+ // with the same key.
+ // Default: false.
+ Replace *bool `yaml:"replace"`
+
+ Location *string `yaml:"location"`
+
+ location *time.Location
+}
+
+func (c JobConfig) ToJobDetailOptions() *quartz.JobDetailOptions {
+ return &quartz.JobDetailOptions{
+ MaxRetries: lo.FromPtr(c.MaxRetries),
+ RetryInterval: lo.FromPtr(c.RetryInterval),
+ Replace: lo.FromPtr(c.Replace),
+ Suspended: false,
+ }
}
type Config struct {
- Timeout string `yaml:"timeout"`
- JobSettings []JobSetting `yaml:"jobs"`
+ Timeout string `yaml:"timeout"`
+ JobConfigs []JobConfig `yaml:"jobs"`
+}
+
+type JobsConfigLoader struct {
+ Scheduler *Config `yaml:"scheduler"`
}
diff --git a/core/scheduler/interface.go b/core/scheduler/interface.go
deleted file mode 100644
index 64b18dfe4..000000000
--- a/core/scheduler/interface.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package scheduler
-
-import "context"
-
-type JobFunc func(ctx context.Context, name string) error
-
-type CronRouter interface {
- Crontab(s *Scheduler)
-}
diff --git a/core/scheduler/job.go b/core/scheduler/job.go
new file mode 100644
index 000000000..9551d6dbc
--- /dev/null
+++ b/core/scheduler/job.go
@@ -0,0 +1,101 @@
+package scheduler
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "time"
+
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/try"
+ "github.com/reugn/go-quartz/quartz"
+ "github.com/rs/zerolog"
+ "github.com/samber/lo"
+
+ "github.com/pubgo/lava/v2/core/metrics"
+)
+
+type namedJob struct {
+ s *Scheduler
+ log log.Logger
+
+ task *jobTask
+}
+
+func (t *namedJob) Description() string { return t.task.spec.Name }
+func (t *namedJob) Execute(ctx context.Context) (gErr error) {
+ start := time.Now()
+ name := t.task.spec.Name
+
+ defer func() {
+ cost := float64(time.Since(start).Milliseconds())
+ t.s.metric.Tagged(metrics.Tags{"job_name": name}).Gauge("job_cost_ms").Update(cost)
+
+ logger := funk.Ternary(gErr == nil, t.log.Info(), t.log.Err(gErr))
+ logger.Func(func(e *zerolog.Event) {
+ e.Float32("job_cost_ms", float32(cost))
+ e.Str("job_name", name)
+ e.Uint64("runs", t.task.runs.Load())
+ e.Msg("exec scheduler job")
+ })
+ }()
+
+ t.task.runs.Inc()
+ config := t.task.spec.Config
+ metadata := JobMetadata{
+ Name: config.Name,
+ Replace: lo.FromPtr(config.Replace),
+ MaxRetries: lo.FromPtr(config.MaxRetries),
+ RetryInterval: lo.FromPtr(config.RetryInterval),
+ Timeout: lo.FromPtr(config.Timeout),
+ Location: config.location.String(),
+ ExecTime: t.task.trigger.prev,
+ NextExecTime: t.task.trigger.next,
+ }
+
+ if t.task.trigger.err != nil {
+ if !errors.Is(t.task.trigger.err, quartz.ErrTriggerExpired) || t.task.spec.Once == nil {
+ return fmt.Errorf("schedule job(%s) trigger error: %w", name, t.task.trigger.err)
+ }
+ }
+
+ return try.Try(func() error {
+ ctx, cancel := context.WithTimeout(ctx, lo.FromPtr(config.Timeout))
+ defer cancel()
+
+ t.task.result = t.task.executor.Exec(ctx, name, &metadata)
+ return t.task.result.GetErr()
+ })
+}
+
+var _ quartz.Trigger = &triggerImpl{}
+
+func newTrigger(trigger quartz.Trigger) *triggerImpl {
+ return &triggerImpl{trigger: trigger}
+}
+
+type triggerImpl struct {
+ prev int64
+ next int64
+ err error
+ trigger quartz.Trigger
+}
+
+func (t *triggerImpl) NextFireTime(prev int64) (next int64, err error) {
+ defer func() {
+ t.err = err
+ if err != nil {
+ return
+ }
+
+ t.prev = prev / 1000_000_000
+ t.next = next / 1000_000_000
+ }()
+
+ return t.trigger.NextFireTime(prev)
+}
+
+func (t *triggerImpl) Description() string {
+ return t.trigger.Description()
+}
diff --git a/core/scheduler/scheduler.go b/core/scheduler/scheduler.go
index 84f2e7a90..bdf8eea89 100644
--- a/core/scheduler/scheduler.go
+++ b/core/scheduler/scheduler.go
@@ -3,115 +3,307 @@ package scheduler
import (
"context"
"fmt"
+ "sync"
"time"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/stack"
- "github.com/pubgo/funk/try"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/log/logfields"
+ "github.com/pubgo/funk/v2/result"
"github.com/reugn/go-quartz/quartz"
+ "github.com/rs/zerolog"
- "github.com/pubgo/lava/core/metrics"
+ "github.com/pubgo/lava/v2/core/metrics"
)
-type job struct {
- key string
- cron string
- dur time.Duration
- once bool
-}
+var (
+ _ JobManager = (*Scheduler)(nil)
+ _ JobRegistry = (*Scheduler)(nil)
+)
type Scheduler struct {
- metric metrics.Metric
- config map[string]JobSetting
- scheduler quartz.Scheduler
- log log.Logger
- cancel context.CancelFunc
- ctx context.Context
- jobs map[string]JobFunc
+ metric metrics.Metric
+ configMap map[string]*JobConfig
+ scheduler quartz.Scheduler
+ log log.Logger
+ cancel context.CancelFunc
+ ctx context.Context
+ jobExecutors map[string]JobExecutor
+
+ mu sync.Mutex
+
+ jobs sync.Map
}
-func (s *Scheduler) stop() {
- s.cancel()
- s.scheduler.Stop()
+func (s *Scheduler) createJob(spec JobSpec, fn JobFunc) (r result.Error) {
+ task := jobTask{
+ spec: &spec,
+ jobKey: parseJobKey(spec.Name),
+ status: StatusRunning,
+ }
+
+ defer func() {
+ logFn := func(e *zerolog.Event) {
+ e.Str("name", task.spec.Name)
+
+ if task.executor != nil {
+ e.Str("executor", task.executor.Name())
+ }
+
+ if task.spec.Config != nil {
+ e.Any("config", task.spec.Config)
+ }
+ e.Any("once", task.spec.Once)
+ e.Any("ticker", task.spec.Ticker)
+ e.Any("cron", task.spec.Cron)
+ }
+ if r.IsErr() {
+ s.log.Err(r.GetErr()).Func(logFn).Msg("failed to register scheduler job")
+ } else {
+ s.log.Info().Func(logFn).Msg("register scheduler job ok")
+ }
+ }()
+ defer result.Recovery(&r)
+
+ if spec.Name == "" {
+ return r.WithErrorf("job name is empty")
+ }
+
+ name := spec.Name
+ if _, ok := s.jobs.Load(name); ok {
+ return r.WithErrorf("job %s already exists", name)
+ }
+
+ executorRes := result.WrapFn(func() (JobExecutor, error) {
+ executor := s.jobExecutors[spec.Executor]
+ if executor == nil {
+ executor = fn
+ }
+
+ if executor == nil {
+ return nil, fmt.Errorf("schedule job executor is nil, name:%s", name)
+ }
+ return executor, nil
+ })
+ executorRes.IfOK(func(executor JobExecutor) { task.executor = executor })
+ if executorRes.Throw(&r) {
+ return r
+ }
+
+ config := initAndMergeConfig(name, s.configMap[name], spec.Config).
+ Log(func(e *zerolog.Event) {
+ e.Str(logfields.Msg, fmt.Sprintf("failed to init schedule job(%s) config", name))
+ }).
+ IfOK(func(config *JobConfig) {
+ task.spec.Config = config
+ }).
+ UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
+ }
+
+ triggerRes := getTrigger(spec, config.location).
+ IfErr(func(err error) {
+ log.Err(err).Msgf("failed to get schedule job(%s) trigger", name)
+ }).
+ IfOK(func(trigger *triggerImpl) {
+ task.trigger = trigger
+ })
+ if triggerRes.Throw(&r) {
+ return r
+ }
+
+ jobOpt := config.ToJobDetailOptions()
+ job := &namedJob{s: s, task: &task, log: s.log}
+ jobDetail := quartz.NewJobDetailWithOptions(job, parseJobKey(name), jobOpt)
+
+ if result.Throw(&r, s.scheduler.ScheduleJob(jobDetail, task.trigger)) {
+ return r
+ }
+
+ s.jobs.Store(name, &task)
+ return r
}
-func (s *Scheduler) start() {
- if s.scheduler.IsStarted() {
- return
+func (s *Scheduler) CreateJob(spec JobSpec) (r result.Error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ return s.createJob(spec, nil)
+}
+
+func (s *Scheduler) getJob(name string) (r result.Result[*jobTask]) {
+ if val, ok := s.jobs.Load(name); !ok {
+ return r.WithErrorf("job %s not exists", name)
+ } else {
+ return r.WithValue(val.(*jobTask))
}
+}
- s.scheduler.Start(s.ctx)
+func (s *Scheduler) PatchJob(name string, config *JobConfig) (r result.Error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ job := s.getJob(name).UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
+ }
+
+ initAndMergeConfig(name, job.spec.Config, config).
+ Log(func(e *zerolog.Event) {
+ e.Str(logfields.Msg, fmt.Sprintf("failed to patch schedule job(%s) config", name))
+ }).
+ IfOK(func(config *JobConfig) {
+ job.spec.Config = config
+ }).
+ Throw(&r)
+
+ return r
+}
+
+func (s *Scheduler) PauseJob(name string) (r result.Error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ job := s.getJob(name).UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
+ }
+
+ job.status = StatusStop
+ return result.ErrOf(s.scheduler.PauseJob(job.jobKey)).IfErr(func(err error) {
+ log.Err(err).Msgf("failed to pause schedule job(%s)", name)
+ })
+}
+
+func (s *Scheduler) ResumeJob(name string) (r result.Error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ job := s.getJob(name).UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
+ }
+
+ job.status = StatusRunning
+ return result.ErrOf(s.scheduler.ResumeJob(job.jobKey)).
+ IfErr(func(err error) {
+ log.Err(err).Msgf("failed to resume schedule job(%s)", name)
+ })
+}
+
+func (s *Scheduler) DeleteJob(name string) (r result.Error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ job := s.getJob(name).UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
+ }
+
+ s.jobs.Delete(name)
+ return result.ErrOf(s.scheduler.DeleteJob(job.jobKey)).
+ IfErr(func(err error) {
+ log.Err(err).Msgf("failed to delete schedule job(%s)", name)
+ })
}
-func (s *Scheduler) checkJobExists(name string, fn JobFunc) error {
- if s.jobs[name] != nil {
- return &errors.Err{
- Msg: fmt.Sprintf("job %s exists", name),
- Detail: stack.CallerWithFunc(s.jobs[name]).String(),
+func (s *Scheduler) ReloadJob(name string) (r result.Error) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ job := s.getJob(name).UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
+ }
+
+ jobOpt := job.spec.Config.ToJobDetailOptions()
+ jobDetail := quartz.NewJobDetailWithOptions(
+ &namedJob{s: s, task: job, log: s.log},
+ job.jobKey,
+ jobOpt,
+ )
+
+ jj, _ := s.scheduler.GetScheduledJob(job.jobKey)
+ if jj != nil {
+ if result.Throw(&r, s.scheduler.DeleteJob(job.jobKey)) {
+ return r
}
}
- s.jobs[name] = fn
- return nil
+ if result.Throw(&r, s.scheduler.ScheduleJob(jobDetail, job.trigger)) {
+ return r
+ }
+ return r
}
-func (s *Scheduler) Once(name string, delay time.Duration, fn JobFunc) {
- assert.Must(s.checkJobExists(name, fn))
+func (s *Scheduler) ListJobs() []*Job {
+ s.mu.Lock()
+ defer s.mu.Unlock()
- s.log.WithCallerSkip(1).Info().
- Str("name", name).
- Str("delay", delay.String()).
- Msg("register once scheduler")
- do(s, job{dur: delay, key: name, once: true}, fn)
+ var jobs []*Job
+ s.jobs.Range(func(key, value any) bool {
+ jobs = append(jobs, value.(*jobTask).ToJob())
+ return true
+ })
+ return jobs
}
-func (s *Scheduler) Every(name string, dur time.Duration, fn JobFunc) {
- assert.Must(s.checkJobExists(name, fn))
+func (s *Scheduler) GetJob(name string) (r result.Result[*Job]) {
+ s.mu.Lock()
+ defer s.mu.Unlock()
- s.log.WithCallerSkip(1).Info().
- Str("name", name).
- Str("dur", dur.String()).
- Msg("register periodic scheduler")
- do(s, job{dur: dur, key: name}, fn)
+ job := s.getJob(name).UnwrapOrThrow(&r)
+ if r.IsErr() {
+ return r
+ }
+
+ return r.WithValue(job.ToJob())
+}
+
+func (s *Scheduler) String() string {
+ return Name
}
-func (s *Scheduler) Cron(name, expr string, fn JobFunc) {
- assert.Must(s.checkJobExists(name, fn))
+func (s *Scheduler) Serve(ctx context.Context) error {
+ defer s.stop()
+ s.start()
- s.log.WithCallerSkip(1).Info().
- Str("name", name).
- Str("expr", expr).
- Msg("register cron scheduler")
- do(s, job{cron: expr, key: name}, fn)
+ s.scheduler.Wait(ctx)
+ return nil
}
-type namedJob struct {
- s *Scheduler
- name string
- fn JobFunc
- log log.Logger
+func (s *Scheduler) stop() {
+ s.cancel()
+
+ if s.scheduler.IsStarted() {
+ s.scheduler.Stop()
+ }
}
-func (t namedJob) Description() string { return t.name }
-func (t namedJob) Execute(ctx context.Context) error {
- start := time.Now()
- err := try.Try(func() error {
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
+func (s *Scheduler) start() {
+ if s.scheduler.IsStarted() {
+ return
+ }
- return t.fn(ctx, t.name)
- })
+ s.scheduler.Start(s.ctx)
+}
- t.s.metric.Tagged(metrics.Tags{"job_name": t.name}).Gauge("scheduler_job_cost").Update(float64(time.Since(start).Microseconds()) / 1000)
+func (s *Scheduler) Once(name string, delay time.Duration, fn JobFunc) result.Error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ return s.createJob(JobSpec{Name: name, Once: &OnceJob{Delay: delay}}, fn)
+}
+
+func (s *Scheduler) Every(name string, dur time.Duration, fn JobFunc) result.Error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ return s.createJob(JobSpec{Name: name, Ticker: &TickerJob{Dur: dur}}, fn)
+}
- logger := generic.Ternary(generic.IsNil(err), t.log.Info(), t.log.Err(err))
- logger.
- Float32("job-cost-ms", float32(time.Since(start).Microseconds())/1000).
- Str("job-name", t.name).
- Msg("scheduler job execution")
+func (s *Scheduler) Cron(name, expr string, fn JobFunc) result.Error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
- return err
+ return s.createJob(JobSpec{Name: name, Cron: &CronJob{Expr: expr}}, fn)
}
diff --git a/core/scheduler/schedulerbuilder/builder.go b/core/scheduler/schedulerbuilder/builder.go
new file mode 100644
index 000000000..66a368225
--- /dev/null
+++ b/core/scheduler/schedulerbuilder/builder.go
@@ -0,0 +1,30 @@
+package schedulerbuilder
+
+import (
+ "github.com/pubgo/lava/v2/core/scheduler"
+ "github.com/pubgo/lava/v2/core/supervisor"
+)
+
+type ResponseParams struct {
+ Service supervisor.Service
+ Manager scheduler.JobManager
+}
+
+func NewService(params scheduler.Params) (ResponseParams, error) {
+ s, err := scheduler.New(
+ params.M,
+ params.Log,
+ params.Metric,
+ params.Configs,
+ params.Routers,
+ params.Executors,
+ )
+ if err != nil {
+ return ResponseParams{}, err
+ }
+
+ return ResponseParams{
+ Service: supervisor.NewService(scheduler.Name, s.Serve),
+ Manager: s,
+ }, nil
+}
diff --git a/core/scheduler/schedulercronexpr/expr.go b/core/scheduler/schedulercronexpr/expr.go
new file mode 100644
index 000000000..8900098bd
--- /dev/null
+++ b/core/scheduler/schedulercronexpr/expr.go
@@ -0,0 +1,22 @@
+package schedulercronexpr
+
+// 一些常用的crontab表达式
+const (
+ EveryMinute = "* * * * *" // 每分钟的开始第0秒
+ EveryFiveMinute = "*/5 * * * *" // 每5分钟的开始第0秒
+ EveryTenMinute = "*/10 * * * *" // 每10分钟的开始第0秒
+ EveryFifteenMinute = "*/15 * * * *" // 每15分钟的开始第0秒
+ EveryTwentyMinute = "*/20 * * * *" // 每20分钟的开始第0秒
+ EveryThirtyMinute = "*/30 * * * *" // 每30分钟的开始第0秒
+ EveryFortyFiveMinute = "*/45 * * * *" // 每45分钟的开始第0秒
+ FirstDayOfMonth = "0 0 1 * *" // 每月的第一天的0点0分
+ LastDayOfMonth = "0 0 L * *" // 每月的最后一天的0点0分
+ FirstDayOfWeek = "0 0 * * 1" // 每周的第一天(周一)的0点0分
+ LastDayOfWeek = "0 0 * * 6" // 每周的最后一天(周天)的0点0分
+ DailyAtTenAM = "0 10 * * *" // 每天上午10点
+ DailyAtTwentyPM = "0 20 * * *" // 每天晚上20点
+ TenClockAtWeekday = "0 10 * * MON-FRI" // 每个工作日(周一~周五)的上午10点0分
+ TenClockAtWeekend = "0 10 * * SAT,SUN" // 每个周末(周六和周日)的上午10点0分
+ HourlyBetween9And17ClockAtWeekday = "0 9-17 * * MON-FRI" // 每个工作日(周一~周五)的上午9点0分到下午5点0分每小时一次
+ HalfHourlyBetween9And17ClockAtWeekday = "*/30 9-17 * * MON-FRI" // 每个工作日(周一~周五)的上午9点0分到下午5点0分每半时一次
+)
diff --git a/core/scheduler/schedulerdebug/debug.go b/core/scheduler/schedulerdebug/debug.go
new file mode 100644
index 000000000..65b7674c1
--- /dev/null
+++ b/core/scheduler/schedulerdebug/debug.go
@@ -0,0 +1,142 @@
+package schedulerdebug
+
+import (
+ "time"
+
+ "github.com/gofiber/fiber/v2"
+
+ "github.com/pubgo/lava/v2/core/debug"
+ "github.com/pubgo/lava/v2/core/scheduler"
+)
+
+func Init(scheduler scheduler.JobManager) {
+ debug.Route("/scheduler", func(router fiber.Router) {
+ router.Get("list", func(ctx *fiber.Ctx) error {
+ ctx.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)
+ return Page(time.Now(), scheduler.ListJobs()).Render(ctx)
+ })
+
+ router.Get("/get", func(ctx *fiber.Ctx) error {
+ dd := `
+
+
+
+
+ 基础菜单 - Layui
+
+
+
+
+
+
+
+
+
+
+
+
+
+`
+ ctx.Response().Header.SetContentType(fiber.MIMETextHTMLCharsetUTF8)
+ _, err := ctx.WriteString(dd)
+ return err
+ })
+ })
+}
diff --git a/core/scheduler/schedulerdebug/html.go b/core/scheduler/schedulerdebug/html.go
new file mode 100644
index 000000000..ffb4f5177
--- /dev/null
+++ b/core/scheduler/schedulerdebug/html.go
@@ -0,0 +1,131 @@
+package schedulerdebug
+
+import (
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "io"
+ "time"
+
+ "github.com/samber/lo"
+ . "maragu.dev/gomponents" //nolint
+ _ "maragu.dev/gomponents/components"
+ . "maragu.dev/gomponents/html" //nolint
+
+ "github.com/pubgo/lava/v2/core/scheduler"
+)
+
+type NodeFn func() Node
+
+func (f NodeFn) Render(w io.Writer) error {
+ return f().Render(w)
+}
+
+func ListSchedulers(schedulers []*scheduler.Job) Node {
+ return Div(
+ Class("overflow-x-auto"),
+ Table(
+ Class("table"),
+ THead(
+ Tr(
+ Th(Text("Name")),
+ Th(Text("Status")),
+ Th(Text("PreRun")),
+ Th(Text("ExecTime")),
+ Th(Text("Error")),
+ Th(Text("Result")),
+ Th(Text("RunCount")),
+ Th(Text("Spec")),
+ ),
+ ),
+ TBody(
+ Map(schedulers, func(s *scheduler.Job) Node {
+ return Tr(
+ Th(Text(s.Spec.Name)),
+ Th(Text(string(s.Status))),
+ Th(Textf("%v", s.PreExecTime)),
+ Th(Textf("%v", s.ExecTime)),
+ Th(NodeFn(func() Node {
+ if s.Error != nil {
+ return Text(s.Error.Error())
+ } else {
+ return Text("null")
+ }
+ })),
+ Th(Text(string(s.Result))),
+ Th(Textf("%v", s.Runs)),
+ Th(NodeFn(func() Node {
+ modeId := hex.EncodeToString([]byte(s.Spec.Name))
+ return Group{
+ Script(Rawf(`function model%s(params) {document.getElementById('%s').showModal()}`, modeId, modeId)),
+ Button(
+ Class("btn"),
+ Attr("onclick", fmt.Sprintf("model%s()", modeId)),
+ Text("open modal"),
+ ),
+ Dialog(
+ ID(modeId),
+ Class("modal"),
+ Div(
+ Class("modal-box"),
+ H3(
+ Class("text-lg font-bold"),
+ Text("Hello!"),
+ ),
+ P(
+ Class("py-4"),
+ Textf("%s", lo.Must(json.MarshalIndent(s.Spec, " ", " "))),
+ ),
+ Div(
+ Class("modal-action"),
+ Form(
+ Method("dialog"),
+ Button(
+ Class("btn"),
+ Text("Close"),
+ ),
+ ),
+ ),
+ ),
+ ),
+ }
+ })),
+ )
+ }),
+ ),
+ ),
+ )
+}
+
+const timeOnly = "15:04:05"
+
+func Page(now time.Time, schedulers []*scheduler.Job) Node {
+ return Doctype(
+ HTML(
+ Head(
+ Meta(Charset("utf-8")),
+ Meta(Name("viewport"), Content("width=device-width, initial-scale=1")),
+ // Meta(Attr("http-equiv", "refresh"), Attr("content", "5")),
+ TitleEl(Text("scheduler")),
+ Group{
+ Link(Href("https://cdn.jsdelivr.net/npm/daisyui@5"), Rel("stylesheet"), Type("text/css")),
+ Script(Src("https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4")),
+ Script(Src("https://cdn.tailwindcss.com?plugins=forms,typography")),
+ Script(Src("https://unpkg.com/htmx.org")),
+ },
+ ),
+ Body(Group{
+ Div(
+ H1(Text(`Scheduler Table`)),
+ partial(now),
+ ),
+
+ ListSchedulers(schedulers),
+ }),
+ ),
+ )
+}
+
+func partial(now time.Time) Node {
+ return P(ID("partial"), Textf(`Time was last updated at %v.`, now.Format(timeOnly)))
+}
diff --git a/core/scheduler/util.go b/core/scheduler/util.go
index 3080e770e..8508ff7d2 100644
--- a/core/scheduler/util.go
+++ b/core/scheduler/util.go
@@ -1,38 +1,55 @@
package scheduler
import (
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/result"
+ "strings"
+ "time"
+
+ "github.com/pubgo/funk/v2/result"
"github.com/reugn/go-quartz/quartz"
)
-func do(s *Scheduler, job job, fn JobFunc) {
- trigger := getTrigger(job)
- assert.If(job.key == "", "[name] should not be null")
- assert.If(fn == nil, "[fn] should not be nil")
- assert.If(trigger == nil, "please init dur or cron")
- assert.Must(s.scheduler.ScheduleJob(
- quartz.NewJobDetail(
- &namedJob{s: s, name: job.key, fn: fn, log: s.log},
- quartz.NewJobKey(job.key)), trigger))
+func regJobExecutor(jobExecutors map[string]JobExecutor, executor JobExecutor) (r result.Error) {
+ if executor == nil {
+ return r.WithErrorf("executor is nil")
+ }
+
+ if executor.Name() == "" {
+ return r.WithErrorf("executor name is empty")
+ }
+
+ if jobExecutors[executor.Name()] != nil {
+ return r.WithErrorf("[job executor] %s already exists", executor.Name())
+ }
+
+ jobExecutors[executor.Name()] = executor
+ return r
}
-func getTrigger(j job) quartz.Trigger {
- if j.once {
- return quartz.NewRunOnceTrigger(j.dur)
+func getTrigger(j JobSpec, location *time.Location) (r result.Result[*triggerImpl]) {
+ if j.Once != nil {
+ return r.WithValue(newTrigger(quartz.NewRunOnceTrigger(j.Once.Delay)))
}
- if j.cron != "" {
- r := result.Wrap(quartz.NewCronTrigger(j.cron))
- return r.Unwrap(func(err error) error {
- return errors.WrapKV(err, "cron-expr", j.cron)
- })
+ if j.Cron != nil {
+ trigger, err := quartz.NewCronTriggerWithLoc(j.Cron.Expr, location)
+ if err != nil {
+ return r.WithErrorf("cron-expr:%s, err:%s", j.Cron.Expr, err.Error())
+ }
+
+ return r.WithValue(newTrigger(trigger))
}
- if j.dur != 0 {
- return quartz.NewSimpleTrigger(j.dur)
+ if j.Ticker != nil {
+ return r.WithValue(newTrigger(quartz.NewSimpleTrigger(j.Ticker.Dur)))
}
- return nil
+ return r.WithErrorf("please init spec.Once, spec.Cron or spec.Ticker, spec:%#v", j)
+}
+
+func parseJobKey(name string) *quartz.JobKey {
+ keys := strings.SplitN(name, quartz.Sep, 2)
+ if len(keys) == 1 {
+ return quartz.NewJobKey(keys[0])
+ }
+ return quartz.NewJobKeyWithGroup(keys[0], keys[1])
}
diff --git a/core/signal/signal.go b/core/signal/signal.go
deleted file mode 100644
index a13aa83f2..000000000
--- a/core/signal/signal.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package signal
-
-import (
- "context"
- "os"
- "os/signal"
- "syscall"
-
- "github.com/pubgo/funk/log"
-)
-
-const Name = "signal"
-
-var logger = log.GetLogger(Name)
-
-func Wait() {
- ch := make(chan os.Signal, 1)
- signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP)
- sig := <-ch
- logger.Info().Str("signal", sig.String()).Msg("signal trigger notify")
-}
-
-func Context() context.Context {
- ctx, cancel := context.WithCancel(context.Background())
- ch := make(chan os.Signal, 1)
- signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP)
- go func() {
- select {
- case <-ch:
- cancel()
- case <-ctx.Done():
- cancel()
- }
- }()
- return ctx
-}
diff --git a/core/signals/signal.go b/core/signals/signal.go
new file mode 100644
index 000000000..4d46f8b8d
--- /dev/null
+++ b/core/signals/signal.go
@@ -0,0 +1,39 @@
+package signals
+
+import (
+ "context"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "github.com/pubgo/funk/v2/log"
+)
+
+const Name = "signals"
+
+var logger = log.GetLogger(Name)
+
+// Inspired by
+// https://github.com/kubernetes-sigs/controller-runtime/blob/8499b67e316a03b260c73f92d0380de8cd2e97a1/pkg/manager/signals/signal.go#L25
+var onlyOneSignalHandler = make(chan struct{})
+
+// var signals = []os.Signal{syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP, syscall.SIGUSR1}
+var shutdownSignals = []os.Signal{syscall.SIGTERM, syscall.SIGQUIT, os.Interrupt, os.Kill}
+
+func Context() context.Context {
+ close(onlyOneSignalHandler)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ ch := make(chan os.Signal, 2)
+ signal.Notify(ch, shutdownSignals...)
+ go func() {
+ sig := <-ch
+ logger.Info().Msgf("cancelling context, received signal:%s", sig)
+ cancel()
+ sig = <-ch
+ logger.Info().Msgf("os exit, received twice signal:%s", sig)
+ os.Exit(1)
+ }()
+
+ return ctx
+}
diff --git a/core/supervisor/_.go b/core/supervisor/_.go
new file mode 100644
index 000000000..426e0e9cc
--- /dev/null
+++ b/core/supervisor/_.go
@@ -0,0 +1,4 @@
+package supervisor
+
+// "github.com/kardianos/service"
+// github.com/thejerf/suture
diff --git a/core/supervisor/aaa.go b/core/supervisor/aaa.go
new file mode 100644
index 000000000..760de73c9
--- /dev/null
+++ b/core/supervisor/aaa.go
@@ -0,0 +1,32 @@
+package supervisor
+
+import (
+ "context"
+ "time"
+
+ "github.com/thejerf/suture/v4"
+)
+
+type Supervisor = suture.Supervisor
+
+type Metric struct {
+ Name string
+ Error string
+ Restart uint32
+ StartTime time.Time
+ OnlineDuration time.Duration
+}
+
+type Service interface {
+ Name() string
+ Error() error
+ String() string
+ Serve(ctx context.Context) error
+ Metric() *Metric
+}
+
+type serviceFn func(ctx context.Context) error
+
+func (fn serviceFn) Serve(ctx context.Context) error {
+ return fn(ctx)
+}
diff --git a/core/supervisor/errs.go b/core/supervisor/errs.go
new file mode 100644
index 000000000..86e056283
--- /dev/null
+++ b/core/supervisor/errs.go
@@ -0,0 +1,79 @@
+package supervisor
+
+import (
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/thejerf/suture/v4"
+)
+
+type FatalErr struct {
+ Err error
+ Status ExitStatus
+}
+
+// AsFatalErr wraps the given error creating a FatalErr. If the given error
+// already is of type FatalErr, it is not wrapped again.
+func AsFatalErr(err error, status ExitStatus) (gErr *FatalErr) {
+ if errors.As(err, &gErr) {
+ return gErr
+ }
+
+ return &FatalErr{
+ Err: err,
+ Status: status,
+ }
+}
+
+func IsFatal(err error) bool {
+ return errors.As(err, &FatalErr{})
+}
+
+func (e *FatalErr) Error() string {
+ return e.Err.Error()
+}
+
+func (e *FatalErr) Unwrap() error {
+ return e.Err
+}
+
+func (*FatalErr) Is(target error) bool {
+ return target == suture.ErrTerminateSupervisorTree
+}
+
+// NoRestartErr wraps the given error err (which may be nil) to make sure that
+// `errors.Is(err, suture.ErrDoNotRestart) == true`.
+func NoRestartErr(err error) error {
+ if err == nil {
+ return suture.ErrDoNotRestart
+ }
+ return &noRestartErr{err}
+}
+
+type noRestartErr struct {
+ err error
+}
+
+func (e *noRestartErr) Error() string {
+ return e.err.Error()
+}
+
+func (e *noRestartErr) Unwrap() error {
+ return e.err
+}
+
+func (*noRestartErr) Is(target error) bool {
+ return target == suture.ErrDoNotRestart
+}
+
+type ExitStatus int
+
+const (
+ ExitSuccess ExitStatus = 0
+ ExitError ExitStatus = 1
+ ExitNoUpgradeAvailable ExitStatus = 2
+ ExitRestart ExitStatus = 3
+ ExitUpgrade ExitStatus = 4
+)
+
+func (s ExitStatus) AsInt() int {
+ return int(s)
+}
diff --git a/core/supervisor/manager.go b/core/supervisor/manager.go
new file mode 100644
index 000000000..b0492874f
--- /dev/null
+++ b/core/supervisor/manager.go
@@ -0,0 +1,237 @@
+package supervisor
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/async"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/pubgo/funk/v2/stack"
+ "github.com/thejerf/suture/v4"
+
+ "github.com/pubgo/lava/v2/core/debug"
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/internal/logutil"
+ "github.com/pubgo/lava/v2/pkg/netutil"
+)
+
+type serviceWrapper struct {
+ token suture.ServiceToken
+ service Service
+}
+
+func Default(lc lifecycle.Getter) *Manager {
+ return NewManager(running.Project(), lc)
+}
+
+func NewManager(name string, lc lifecycle.Getter) *Manager {
+ m := &Manager{
+ lc: lc,
+ supervisor: suture.New(name, SpecWithInfoLogger()),
+ services: make(map[string]*serviceWrapper),
+ logger: log.GetLogger(name),
+ }
+ return m.init()
+}
+
+type Manager struct {
+ lc lifecycle.Getter
+ logger log.Logger
+ supervisor *Supervisor
+ services map[string]*serviceWrapper
+}
+
+func (m *Manager) init() *Manager {
+ debug.Route("/supervisor", func(router fiber.Router) {
+ router.Get("services", func(ctx *fiber.Ctx) error {
+ var services []*Metric
+ for _, srv := range m.services {
+ services = append(services, srv.service.Metric())
+ }
+ return ctx.JSON(services)
+ })
+ })
+
+ return m
+}
+
+func (m *Manager) Has(name string) bool {
+ _, ok := m.services[name]
+ return ok
+}
+
+func (m *Manager) OnClose(fn func()) {
+ m.supervisor.Add(serviceFn(func(ctx context.Context) error {
+ <-ctx.Done()
+ fn()
+ return nil
+ }))
+}
+
+func (m *Manager) Add(srv Service) error {
+ name := srv.Name()
+ if _, ok := m.services[name]; ok {
+ return errors.Errorf("service already exists, name=%s", name)
+ }
+
+ m.logger.Info().Str("name", name).Msg("add service to supervisor")
+ m.services[name] = &serviceWrapper{service: srv, token: m.supervisor.Add(srv)}
+ return nil
+}
+
+func (m *Manager) Delete(name string) error {
+ srv := m.services[name]
+ if srv == nil {
+ m.logger.Warn().Str("name", name).Msg("service not found, cannot delete")
+ return nil
+ }
+
+ defer func() { delete(m.services, name) }()
+ m.logger.Info().Str("name", name).Msg("delete service from supervisor")
+ return errors.Wrapf(m.supervisor.Remove(srv.token), "failed to remove service, name=%s", name)
+}
+
+func (m *Manager) RemoveServices() (gErr error) {
+ for name, srv := range m.services {
+ if result.ThrowErr(&gErr, m.supervisor.Remove(srv.token)) {
+ return errors.Wrapf(gErr, "failed to remove service, name=%s", name)
+ }
+ m.logger.Info().Str("name", name).Msg("removing service from supervisor")
+ }
+
+ m.services = make(map[string]*serviceWrapper)
+ return nil
+}
+
+func (m *Manager) RestartServices() (gErr error) {
+ for name, srv := range m.services {
+ if result.ThrowErr(&gErr, m.supervisor.Remove(srv.token)) {
+ return errors.Wrapf(gErr, "failed to remove service, name=%s", name)
+ }
+
+ m.services[name] = &serviceWrapper{service: srv.service, token: m.supervisor.Add(srv.service)}
+ m.logger.Info().Str("name", name).Msg("restarting service in supervisor")
+ }
+
+ return nil
+}
+
+func (m *Manager) RestartService(name string) (gErr error) {
+ srv := m.services[name]
+ if srv == nil {
+ m.logger.Warn().Str("name", name).Msg("service not found, cannot restart")
+ return nil
+ }
+
+ if result.ThrowErr(&gErr, m.supervisor.Remove(srv.token)) {
+ return errors.Wrapf(gErr, "failed to remove service, name=%s", name)
+ }
+
+ m.services[name] = &serviceWrapper{service: srv.service, token: m.supervisor.Add(srv.service)}
+ m.logger.Info().Str("name", name).Msg("restarting service in supervisor")
+
+ return nil
+}
+
+func (m *Manager) Services() []Service {
+ var services []Service
+ for _, srv := range m.services {
+ services = append(services, srv.service)
+ }
+ return services
+}
+
+func (m *Manager) start(ctx context.Context) error {
+ defer recovery.Exit()
+ logutil.OkOrFailed(m.logger, "start lifecycle before service", func() error {
+ defer recovery.Exit()
+ for _, run := range m.lc.GetBeforeStarts() {
+ m.logger.Info().Msgf("running %s", stack.CallerWithFunc(run.Exec))
+ assert.Exit(run.Exec(ctx))
+ }
+ return nil
+ })
+
+ async.GoDelay(func() error {
+ err := m.supervisor.Serve(ctx)
+ if netutil.IsErrServerClosed(err) {
+ return nil
+ }
+ assert.Exit(err)
+ return nil
+ })
+
+ logutil.OkOrFailed(m.logger, "start lifecycle after service", func() error {
+ defer recovery.Exit()
+ for _, run := range m.lc.GetAfterStarts() {
+ m.logger.Info().Msgf("running %s", stack.CallerWithFunc(run.Exec))
+ assert.Exit(run.Exec(ctx))
+ }
+ return nil
+ })
+
+ return nil
+}
+
+func (m *Manager) stop(ctx context.Context) error {
+ defer recovery.DebugPrint()
+
+ logutil.OkOrFailed(m.logger, "stop lifecycle before service", func() error {
+ for _, run := range m.lc.GetBeforeStops() {
+ logutil.LogOrErr(m.logger, fmt.Sprintf("running %s", stack.CallerWithFunc(run.Exec)), func() error {
+ return run.Exec(ctx)
+ })
+ }
+ return nil
+ })
+
+ unstoppedServices, _ := m.supervisor.UnstoppedServiceReport()
+ if len(unstoppedServices) > 0 {
+ for _, service := range unstoppedServices {
+ m.logger.Error().Any("service", service).Msgf("service:%s is still running", service.Name)
+ }
+ return errors.New("services are still running")
+ }
+
+ logutil.OkOrFailed(m.logger, "stop lifecycle after service", func() error {
+ for _, run := range m.lc.GetAfterStops() {
+ logutil.LogOrErr(m.logger, fmt.Sprintf("running %s", stack.CallerWithFunc(run.Exec)), func() error {
+ return run.Exec(ctx)
+ })
+ }
+ return nil
+ })
+
+ return nil
+}
+
+func (m *Manager) Run(ctx context.Context) error {
+ err := m.start(ctx)
+ if err != nil {
+ return err
+ }
+
+ <-ctx.Done()
+
+ return m.stop(ctx)
+}
+
+func (m *Manager) Serve(ctx context.Context) error {
+ err := m.supervisor.Serve(ctx)
+
+ if netutil.IsErrServerClosed(err) {
+ return nil
+ }
+
+ return err
+}
+
+func (m *Manager) ServeBackground(ctx context.Context) <-chan error {
+ return m.supervisor.ServeBackground(ctx)
+}
diff --git a/core/supervisor/service.go b/core/supervisor/service.go
new file mode 100644
index 000000000..2d0e1debf
--- /dev/null
+++ b/core/supervisor/service.go
@@ -0,0 +1,84 @@
+package supervisor
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "go.uber.org/atomic"
+)
+
+type serviceMetric struct {
+ Error atomic.String
+ Restart atomic.Uint32
+ StartTime atomic.Time
+ OnlineDuration atomic.Duration
+}
+
+func NewService(name string, fn func(ctx context.Context) error) Service {
+ return &serviceImpl{name: name, fn: fn, metric: &serviceMetric{}}
+}
+
+var _ Service = &serviceImpl{}
+
+type serviceImpl struct {
+ name string
+ err error
+ fn func(ctx context.Context) error
+
+ metric *serviceMetric
+}
+
+func (s *serviceImpl) Metric() *Metric {
+ metric := s.metric
+ return &Metric{
+ Name: s.name,
+ Error: metric.Error.Load(),
+ Restart: metric.Restart.Load(),
+ StartTime: metric.StartTime.Load(),
+ OnlineDuration: time.Since(metric.StartTime.Load()),
+ }
+}
+
+func (s *serviceImpl) Error() error {
+ return s.err
+}
+
+func (s *serviceImpl) Name() string {
+ return s.name
+}
+
+func (s *serviceImpl) String() string {
+ return s.name
+}
+
+func (s *serviceImpl) Serve(ctx context.Context) (gErr error) {
+ s.metric.StartTime.Store(time.Now())
+ defer func() {
+ if gErr != nil {
+ s.err = gErr
+ }
+
+ if s.err != nil {
+ s.metric.Error.Store(s.err.Error())
+ }
+
+ log.Info(ctx).
+ Str("service", s.name).
+ Any("metrics", s.Metric()).
+ Msg("stop service")
+ }()
+ defer recovery.Err(&gErr)
+
+ s.err = nil
+ s.metric.Restart.Add(1)
+ log.Info(ctx).Str("service", s.name).Msg("start service")
+ err := s.fn(ctx)
+ if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+ return fmt.Errorf("non-context error, service=%s meta=%v err=%w", s.name, s.Metric(), err)
+ }
+ return err
+}
diff --git a/core/supervisor/spec.go b/core/supervisor/spec.go
new file mode 100644
index 000000000..a516f0b6a
--- /dev/null
+++ b/core/supervisor/spec.go
@@ -0,0 +1,61 @@
+package supervisor
+
+import (
+ "log/slog"
+ "time"
+
+ "github.com/pubgo/funk/v2/log"
+ "github.com/thejerf/suture/v4"
+)
+
+const ServiceTimeout = 10 * time.Second
+
+func SpecWithDebugLogger() suture.Spec {
+ return spec(func(e suture.Event) { log.Debug().Msg(e.String()) })
+}
+
+func SpecWithInfoLogger() suture.Spec {
+ return spec(infoEventHook())
+}
+
+func spec(eventHook suture.EventHook) suture.Spec {
+ return suture.Spec{
+ EventHook: eventHook,
+ Timeout: ServiceTimeout,
+ PassThroughPanics: true,
+ DontPropagateTermination: false,
+ }
+}
+
+// infoEventHook prints service failures and failures to stop services at level
+// info. All other events and identical, consecutive failures are logged at
+// debug only.
+func infoEventHook() suture.EventHook {
+ var prevTerminate suture.EventServiceTerminate
+ return func(ei suture.Event) {
+ m := ei.Map()
+ l := slog.With("supervisor", m["supervisor_name"], "service", m["service_name"])
+ switch e := ei.(type) {
+ case suture.EventStopTimeout:
+ l.Warn("Service failed to terminate in a timely manner")
+ case suture.EventServicePanic:
+ l.Error("Caught a service panic, which shouldn't happen")
+ l.Warn(e.String()) //nolint:sloglint
+ case suture.EventServiceTerminate:
+ if e.ServiceName == prevTerminate.ServiceName && e.Err == prevTerminate.Err {
+ l.Debug("Service failed repeatedly", e.Err)
+ } else {
+ l.Warn("Service failed", e.Err)
+ }
+ prevTerminate = e
+ l.Debug(e.String()) // Contains some backoff statistics
+ case suture.EventBackoff:
+ l.Debug("Exiting the backoff state")
+ case suture.EventResume:
+ l.Debug("Too many service failures - entering the backoff state")
+ default:
+ l.Warn("Unknown suture supervisor event", slog.Any("type", e.Type()))
+ l.Warn(e.String()) //nolint:lint
+ }
+ }
+}
diff --git a/core/tracing/config.go b/core/tracing/config.go
index 7d943217a..f681c0f1a 100644
--- a/core/tracing/config.go
+++ b/core/tracing/config.go
@@ -40,7 +40,7 @@ type Config struct {
prettyPrint bool
bspOptions []sdktrace.BatchSpanProcessorOption
- // Metrics options
+ // Metric options
metricsEnabled bool
metricOptions []metric.Option
diff --git a/core/tracing/telemetry.go b/core/tracing/telemetry.go
index 0934ea30b..425d1d0a4 100644
--- a/core/tracing/telemetry.go
+++ b/core/tracing/telemetry.go
@@ -7,32 +7,28 @@ import (
"time"
"github.com/goccy/go-json"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/version"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
otlpTraceGrpc "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
otelprom "go.opentelemetry.io/otel/exporters/prometheus"
+ "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
+ otelmetric "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/propagation"
-
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
-
- otelmetric "go.opentelemetry.io/otel/metric"
oteltrace "go.opentelemetry.io/otel/trace"
-
"google.golang.org/grpc/encoding/gzip"
- "github.com/pubgo/lava/core/lifecycle"
-
- "go.opentelemetry.io/otel/sdk/metric"
-
- "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
+ "github.com/pubgo/lava/v2/core/lifecycle"
)
var logs = log.GetLogger("tracing")
@@ -76,11 +72,14 @@ func New(cfg *Config, lc lifecycle.Lifecycle) Provider {
propagation.Baggage{},
),
)
- meterProvider := NewPrometheusMeterProvider(config)
+ meterProvider, err := NewPrometheusMeterProvider(config)
+ assert.Exit(err)
- lc.AfterStop(func() {
+ lc.AfterStop(func(ctx context.Context) (gErr error) {
+ defer recovery.Err(&gErr)
assert.Must(tracerProvider.Shutdown(context.Background()))
assert.Must(meterProvider.Shutdown(context.Background()))
+ return nil
})
// name := instrumentationName + "/" + config.serviceInfo.Namespace + "/" + config.serviceInfo.Name
@@ -105,7 +104,7 @@ func mergeResource(config *Config) *resource.Resource {
semconv.ProcessCommandKey.String(os.Args[0]),
)
- return assert.Must1(resource.Merge(resource.Default(), defaultResource))
+ return assert.Exit1(resource.Merge(resource.Default(), defaultResource))
}
func NewTracer(config *Config) *sdktrace.TracerProvider {
@@ -165,14 +164,18 @@ func initTracerExporter(config *Config) (sdktrace.SpanExporter, error) {
return nil, fmt.Errorf("tracer exporter endpoint is nil, no exporter is inited")
}
-func NewPrometheusMeterProvider(config *Config) *sdkmetric.MeterProvider {
+func NewPrometheusMeterProvider(config *Config, opts ...otelprom.Option) (_ *sdkmetric.MeterProvider, gErr error) {
+ exporter, err := otelprom.New(opts...)
+ if result.ThrowErr(&gErr, err) {
+ return
+ }
+
res := mergeResource(config)
- exporter := assert.Must1(otelprom.New())
provider := sdkmetric.NewMeterProvider(
sdkmetric.WithReader(exporter),
sdkmetric.WithResource(res),
)
- return provider
+ return provider, nil
}
func TraceID(span oteltrace.Span) string {
@@ -208,7 +211,7 @@ func GetTraceId(ctx context.Context) string {
return oteltrace.SpanContextFromContext(ctx).TraceID().String()
}
-func initMetricExporter(config *Config) (metric.Exporter, error) {
+func initMetricExporter(config *Config) (sdkmetric.Exporter, error) {
if config.metricExporter.ExporterEndpoint == DefaultStdout {
encoder := json.NewEncoder(os.Stdout)
return stdoutmetric.New(stdoutmetric.WithEncoder(encoder))
diff --git a/core/tracing/tracingbuilder/config.go b/core/tracing/tracingbuilder/config.go
new file mode 100644
index 000000000..e9c0c0beb
--- /dev/null
+++ b/core/tracing/tracingbuilder/config.go
@@ -0,0 +1,118 @@
+package tracingbuilder
+
+import (
+ "crypto/tls"
+ "time"
+
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/attribute"
+ "go.opentelemetry.io/otel/propagation"
+ "go.opentelemetry.io/otel/sdk/metric"
+ "go.opentelemetry.io/otel/sdk/resource"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/protobuf/types/known/durationpb"
+
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ //"google.golang.org/grpc/credentials"
+)
+
+const (
+ DefaultStdout = "stdout"
+)
+
+type TraceConfigLoader struct {
+ TraceCfg *Config `yaml:"tracing"`
+}
+
+type Config struct {
+ traceExporter *Exporter
+ metricExporter *Exporter
+ metricReportPeriod string
+ serviceInfo *ServiceInfo
+ attributes map[string]string
+ headers map[string]string
+ idGenerator sdktrace.IDGenerator
+ otelErrorHandler otel.ErrorHandler
+ traceBatchOptions []sdktrace.BatchSpanProcessorOption
+ sampleRatio float64
+
+ resourceAttributes []attribute.KeyValue
+ resourceDetectors []resource.Detector
+
+ tlsConf *tls.Config
+
+ // Tracing options
+
+ tracingEnabled bool
+ textMapPropagator propagation.TextMapPropagator
+ tracerProvider *sdktrace.TracerProvider
+ traceSampler sdktrace.Sampler
+ prettyPrint bool
+ bspOptions []sdktrace.BatchSpanProcessorOption
+
+ // Metric options
+
+ metricsEnabled bool
+ metricOptions []metric.Option
+}
+
+type Exporter struct {
+ ExporterEndpoint string
+ Insecure bool
+ Creds credentials.TransportCredentials
+}
+
+type ServiceInfo struct {
+ Name string
+ Namespace string
+ Version string
+}
+
+// OTLP contains specific configuration used by the OpenTelemetry Metric exporter.
+type OTLP struct {
+ GRPC *OtelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+ HTTP *OtelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
+
+ AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"`
+ AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"`
+ AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"`
+ ExplicitBoundaries []float64 `description:"Boundaries for latency metrics." json:"explicitBoundaries,omitempty" toml:"explicitBoundaries,omitempty" yaml:"explicitBoundaries,omitempty" export:"true"`
+ PushInterval *durationpb.Duration `description:"Period between calls to collect a checkpoint." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"`
+ ServiceName string `description:"OTEL service name to use." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
+}
+
+// SetDefaults sets the default values.
+func (o *OTLP) SetDefaults() {
+ o.HTTP = &OtelHTTP{}
+ o.HTTP.SetDefaults()
+
+ o.AddEntryPointsLabels = true
+ o.AddServicesLabels = true
+ o.ExplicitBoundaries = []float64{.005, .01, .025, .05, .075, .1, .25, .5, .75, 1, 2.5, 5, 7.5, 10}
+ o.PushInterval = durationpb.New(10 * time.Second)
+ o.ServiceName = version.Project()
+}
+
+// OtelGRPC provides configuration settings for the gRPC open-telemetry.
+type OtelGRPC struct {
+ Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
+ Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
+ Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
+}
+
+// SetDefaults sets the default values.
+func (c *OtelGRPC) SetDefaults() {
+ c.Endpoint = "localhost:4317"
+}
+
+// OtelHTTP provides configuration settings for the HTTP open-telemetry.
+type OtelHTTP struct {
+ Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/path) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
+ Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"`
+}
+
+// SetDefaults sets the default values.
+func (c *OtelHTTP) SetDefaults() {
+ c.Endpoint = "https://localhost:4318"
+}
diff --git a/core/tracing/tracingbuilder/telemetry.go b/core/tracing/tracingbuilder/telemetry.go
new file mode 100644
index 000000000..ba521ef35
--- /dev/null
+++ b/core/tracing/tracingbuilder/telemetry.go
@@ -0,0 +1,211 @@
+package tracingbuilder
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+ "time"
+
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
+ "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
+ "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
+ otelprom "go.opentelemetry.io/otel/exporters/prometheus"
+ "go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
+ "go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
+ otelmetric "go.opentelemetry.io/otel/metric"
+ "go.opentelemetry.io/otel/propagation"
+ sdkmetric "go.opentelemetry.io/otel/sdk/metric"
+ "go.opentelemetry.io/otel/sdk/resource"
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
+ semconv "go.opentelemetry.io/otel/semconv/v1.7.0"
+ oteltrace "go.opentelemetry.io/otel/trace"
+ "google.golang.org/grpc/encoding/gzip"
+
+ "github.com/pubgo/lava/v2/core/lifecycle"
+)
+
+type Provider struct {
+ TracerProvider oteltrace.TracerProvider
+ Tracer oteltrace.Tracer
+ MeterProvider otelmetric.MeterProvider
+ Meter otelmetric.Meter
+}
+
+type Params struct {
+ Cfg *Config
+ LC lifecycle.Lifecycle
+}
+
+func New(params Params) Provider {
+ defer recovery.Exit()
+ config := &Config{
+ traceExporter: &Exporter{},
+ metricExporter: &Exporter{},
+ metricReportPeriod: "",
+ serviceInfo: &ServiceInfo{},
+ attributes: map[string]string{},
+ headers: map[string]string{},
+ idGenerator: nil,
+ traceBatchOptions: []sdktrace.BatchSpanProcessorOption{},
+ sampleRatio: 1,
+ }
+
+ tracerProvider := NewTracerProvider(config).Unwrap()
+ meterProvider := NewMeterProvider(config).Unwrap()
+ propagator := propagation.NewCompositeTextMapPropagator(
+ propagation.TraceContext{},
+ propagation.Baggage{},
+ )
+
+ otel.SetTracerProvider(tracerProvider)
+ otel.SetMeterProvider(meterProvider)
+ otel.SetTextMapPropagator(propagator)
+
+ params.LC.AfterStop(func(ctx context.Context) error {
+ return errors.Join(
+ tracerProvider.Shutdown(ctx),
+ meterProvider.Shutdown(ctx),
+ )
+ })
+
+ return Provider{
+ TracerProvider: tracerProvider,
+ Tracer: otel.Tracer(
+ version.Project(),
+ oteltrace.WithInstrumentationVersion(version.Version()),
+ oteltrace.WithInstrumentationAttributes(),
+ ),
+ MeterProvider: meterProvider,
+ Meter: otel.Meter(
+ version.Project(),
+ otelmetric.WithInstrumentationVersion(version.Version()),
+ otelmetric.WithInstrumentationAttributes(),
+ ),
+ }
+}
+
+// merge config resource with default resource
+func mergeResource(config *Config) (r result.Result[*resource.Resource]) {
+ defer result.Recovery(&r)
+ res := result.Wrap(resource.New(context.Background(),
+ resource.WithFromEnv(),
+ resource.WithTelemetrySDK(),
+ resource.WithOSType(),
+ resource.WithProcessCommandArgs(),
+ )).Unwrap()
+ res = result.Wrap(resource.Merge(resource.Default(), res)).Unwrap()
+
+ hostname, _ := os.Hostname()
+ defaultResource := resource.NewWithAttributes(
+ semconv.SchemaURL,
+ semconv.ServiceNameKey.String(config.serviceInfo.Name),
+ semconv.HostNameKey.String(hostname),
+ semconv.ServiceNamespaceKey.String(config.serviceInfo.Namespace),
+ semconv.ServiceVersionKey.String(config.serviceInfo.Version),
+ semconv.ProcessPIDKey.Int(os.Getpid()),
+ semconv.ProcessCommandKey.String(os.Args[0]),
+ )
+ res = result.Wrap(resource.Merge(defaultResource, res)).Log().Unwrap()
+
+ return r.WithValue(res)
+}
+
+func NewTracerProvider(config *Config) (r result.Result[*sdktrace.TracerProvider]) {
+ defer result.Recovery(&r)
+ res := mergeResource(config).Unwrap()
+
+ traceExporter := result.Wrap(newGrpcTracerExporter(config)).Unwrap()
+ sampler := sdktrace.ParentBased(sdktrace.AlwaysSample())
+ if config.sampleRatio < 1 && config.sampleRatio >= 0 {
+ sampler = sdktrace.ParentBased(sdktrace.TraceIDRatioBased(config.sampleRatio))
+ log.Info().Msgf("set sample ratio %v", config.sampleRatio)
+ }
+
+ traceProvider := sdktrace.NewTracerProvider(
+ sdktrace.WithResource(res),
+ sdktrace.WithSampler(sampler),
+ sdktrace.WithBatcher(traceExporter,
+ sdktrace.WithMaxQueueSize(queueSize()),
+ sdktrace.WithMaxExportBatchSize(queueSize()),
+ sdktrace.WithBatchTimeout(10*time.Second),
+ sdktrace.WithExportTimeout(10*time.Second),
+ ),
+ sdktrace.WithRawSpanLimits(sdktrace.SpanLimits{
+ AttributeCountLimit: 1024,
+ EventCountLimit: 1024,
+ LinkCountLimit: 1024,
+ AttributePerEventCountLimit: 1024,
+ AttributePerLinkCountLimit: 1024,
+ AttributeValueLengthLimit: 1024,
+ }),
+ )
+
+ return r.WithValue(traceProvider)
+}
+
+func newGrpcTracerExporter(config *Config) (sdktrace.SpanExporter, error) {
+ if config.traceExporter.ExporterEndpoint == DefaultStdout {
+ return stdouttrace.New(stdouttrace.WithPrettyPrint())
+ }
+
+ // opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
+ traceSecureOption := otlptracegrpc.WithTLSCredentials(config.traceExporter.Creds)
+ if config.traceExporter.Insecure {
+ traceSecureOption = otlptracegrpc.WithInsecure()
+ }
+
+ return otlptrace.New(
+ context.Background(),
+ otlptracegrpc.NewClient(
+ otlptracegrpc.WithEndpoint(config.traceExporter.ExporterEndpoint),
+ traceSecureOption,
+ otlptracegrpc.WithHeaders(config.headers),
+ otlptracegrpc.WithCompressor(gzip.Name),
+ ),
+ )
+}
+
+func newGrpcMetricExporter(config *Config) (sdkmetric.Exporter, error) {
+ if config.metricExporter.ExporterEndpoint == DefaultStdout {
+ encoder := json.NewEncoder(os.Stdout)
+ return stdoutmetric.New(stdoutmetric.WithEncoder(encoder))
+ }
+
+ if config.metricExporter.ExporterEndpoint != "" {
+ metricSecureOption := otlpmetricgrpc.WithTLSCredentials(config.metricExporter.Creds)
+ if config.metricExporter.Insecure {
+ metricSecureOption = otlpmetricgrpc.WithInsecure()
+ }
+
+ return otlpmetricgrpc.New(
+ context.Background(),
+ otlpmetricgrpc.WithEndpoint(config.metricExporter.ExporterEndpoint),
+ metricSecureOption,
+ otlpmetricgrpc.WithHeaders(config.headers),
+ otlpmetricgrpc.WithCompressor(gzip.Name))
+ }
+ return nil, fmt.Errorf("metric exporter endpoint is nil, no exporter is inited")
+}
+
+func NewMeterProvider(config *Config) (r result.Result[*sdkmetric.MeterProvider]) {
+ defer result.Recovery(&r)
+ // reader := metric.NewPeriodicReader(assert.Must1(newGrpcMetricExporter(config)))
+ // readerOpt := sdkmetric.WithReader(reader)
+
+ exporter := result.Wrap(otelprom.New()).Unwrap()
+ readerOpt := sdkmetric.WithReader(exporter)
+
+ res := mergeResource(config).Unwrap()
+ provider := sdkmetric.NewMeterProvider(
+ readerOpt,
+ sdkmetric.WithResource(res),
+ )
+ return r.WithValue(provider)
+}
diff --git a/core/tracing/tracingbuilder/util.go b/core/tracing/tracingbuilder/util.go
new file mode 100644
index 000000000..0af34d492
--- /dev/null
+++ b/core/tracing/tracingbuilder/util.go
@@ -0,0 +1,19 @@
+package tracingbuilder
+
+import (
+ "runtime"
+)
+
+func queueSize() int {
+ const minSize = 1000
+ const maxSize = 16000
+
+ n := (runtime.GOMAXPROCS(0) / 2) * 1000
+ if n < minSize {
+ return minSize
+ }
+ if n > maxSize {
+ return maxSize
+ }
+ return n
+}
diff --git a/docs/_doc.go b/docs/_doc.go
index df8bac9df..b22a41dd1 100644
--- a/docs/_doc.go
+++ b/docs/_doc.go
@@ -20,3 +20,5 @@ package docs
// import _ "go.etcd.io/etcd/pkg/fileutil"
// https://github.com/tideland/go-slices/blob/main/slices.go
// https://github.com/labstack/gommon
+
+// https://github.com/ThreeDotsLabs/watermill
diff --git a/go.mod b/go.mod
index 45c45e433..aa697b6c2 100644
--- a/go.mod
+++ b/go.mod
@@ -1,91 +1,89 @@
-module github.com/pubgo/lava
+module github.com/pubgo/lava/v2
-go 1.22.1
+go 1.24.0
require (
- github.com/gofiber/fiber/v2 v2.52.4
+ github.com/gofiber/fiber/v2 v2.52.9
github.com/golang/protobuf v1.5.4 // indirect
github.com/grandcat/zeroconf v1.0.0
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
github.com/jinzhu/copier v0.4.0 // indirect
github.com/json-iterator/go v1.1.12
github.com/mitchellh/mapstructure v1.5.0 // indirect
- github.com/olekukonko/tablewriter v0.0.5
+ github.com/olekukonko/tablewriter v1.0.8
github.com/pkg/errors v0.9.1
- github.com/pubgo/opendoc v0.0.5
- github.com/reugn/go-quartz v0.11.2
+ github.com/reugn/go-quartz v0.15.1
github.com/vmihailenco/msgpack/v5 v5.4.1
- go.opentelemetry.io/otel v1.30.0
- go.opentelemetry.io/otel/metric v1.30.0
- go.opentelemetry.io/otel/trace v1.30.0
- go.uber.org/atomic v1.11.0 // indirect
- go.uber.org/automaxprocs v1.5.3
- golang.org/x/crypto v0.27.0 // indirect
- golang.org/x/mod v0.21.0 // indirect
- golang.org/x/net v0.29.0
- golang.org/x/sys v0.25.0 // indirect
- google.golang.org/grpc v1.66.1
- google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689
+ go.opentelemetry.io/otel v1.37.0
+ go.opentelemetry.io/otel/metric v1.37.0
+ go.opentelemetry.io/otel/trace v1.37.0
+ go.uber.org/atomic v1.11.0
+ go.uber.org/automaxprocs v1.6.0
+ golang.org/x/crypto v0.43.0 // indirect
+ golang.org/x/mod v0.29.0 // indirect
+ golang.org/x/net v0.46.0
+ golang.org/x/sys v0.37.0 // indirect
+ google.golang.org/grpc v1.73.0
+ google.golang.org/protobuf v1.36.6
)
require (
- github.com/goccy/go-json v0.10.2
- github.com/mailgun/holster/v4 v4.19.0
- github.com/prometheus/client_golang v1.19.1
+ github.com/goccy/go-json v0.10.5
+ github.com/mailgun/holster/v4 v4.21.0
+ github.com/prometheus/client_golang v1.22.0
github.com/valyala/bytebufferpool v1.0.0
)
require (
- github.com/alecthomas/participle/v2 v2.1.1
- github.com/arl/statsviz v0.6.0
- github.com/dave/jennifer v1.7.0
+ dario.cat/mergo v1.0.2
+ github.com/alecthomas/participle/v2 v2.1.4
+ github.com/arl/statsviz v0.7.1
github.com/ecordell/optgen v0.0.9
- github.com/fasthttp/router v1.5.0
- github.com/fasthttp/websocket v1.5.8
+ github.com/expr-lang/expr v1.17.5
+ github.com/fasthttp/websocket v1.5.12
github.com/felixge/fgprof v0.9.5
github.com/fullstorydev/grpchan v1.1.1
- github.com/go-playground/validator/v10 v10.19.0
+ github.com/go-logr/logr v1.4.3
+ github.com/go-playground/validator/v10 v10.27.0
github.com/gofiber/adaptor/v2 v2.2.1
github.com/gofiber/utils v1.1.0
- github.com/gofiber/websocket/v2 v2.2.1
github.com/golangci/golangci-lint v1.61.0
- github.com/gorilla/websocket v1.5.1
- github.com/goyek/goyek/v2 v2.2.0
- github.com/goyek/workflow v0.0.0-20240815094733-414d2a904ff9
- github.com/goyek/x v0.2.0
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1
+ github.com/google/gops v0.3.28
+ github.com/gorilla/websocket v1.5.3
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19
- github.com/maragudk/gomponents v0.20.0
- github.com/prometheus/common v0.48.0
- github.com/pubgo/dix v0.3.19
- github.com/pubgo/funk v0.5.56
- github.com/pubgo/protobuild v0.0.21
- github.com/rs/xid v1.5.0
- github.com/rs/zerolog v1.33.0
- github.com/samber/lo v1.47.0
- github.com/stretchr/testify v1.9.0
- github.com/uber-go/tally/v4 v4.1.16
- github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240717192922-127cf54fac9f
- github.com/valyala/fasthttp v1.52.0
+ github.com/maragudk/gomponents v0.22.0
+ github.com/maruel/panicparse/v2 v2.5.0
+ github.com/pubgo/dix/v2 v2.0.0-beta.2
+ github.com/pubgo/funk/v2 v2.0.0-beta.2
+ github.com/rs/xid v1.6.0
+ github.com/rs/zerolog v1.34.0
+ github.com/samber/lo v1.52.0
+ github.com/stretchr/testify v1.10.0
+ github.com/thejerf/suture/v4 v4.0.6
+ github.com/uber-go/tally/v4 v4.1.17
+ github.com/ulikunitz/xz v0.5.15
+ github.com/urfave/cli/v3 v3.4.1
+ github.com/valyala/fasthttp v1.63.0
github.com/valyala/fasttemplate v1.2.2
- go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0
- go.opentelemetry.io/otel/exporters/prometheus v0.45.2
- go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1
- go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0
- go.opentelemetry.io/otel/sdk v1.30.0
- go.opentelemetry.io/otel/sdk/metric v1.26.0
- golang.org/x/tools v0.25.0
+ go.opentelemetry.io/contrib/zpages v0.62.0
+ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0
+ go.opentelemetry.io/otel/exporters/prometheus v0.59.0
+ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0
+ go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0
+ go.opentelemetry.io/otel/sdk v1.37.0
+ go.opentelemetry.io/otel/sdk/metric v1.37.0
+ golang.org/x/tools v0.38.0
golang.org/x/vuln v1.1.3
- google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1
+ google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822
gopkg.in/yaml.v3 v3.0.1
+ maragu.dev/gomponents v1.2.0
)
require (
4d63.com/gocheckcompilerdirectives v1.2.1 // indirect
4d63.com/gochecknoglobals v0.2.1 // indirect
- dario.cat/mergo v1.0.0 // indirect
github.com/4meepo/tagalign v1.3.4 // indirect
github.com/Abirdcfly/dupword v0.1.1 // indirect
github.com/Antonboom/errname v0.1.13 // indirect
@@ -97,13 +95,12 @@ require (
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 // indirect
- github.com/a8m/envsubst v1.3.0 // indirect
+ github.com/a8m/envsubst v1.4.3 // indirect
github.com/alecthomas/go-check-sumtype v0.1.4 // indirect
- github.com/alecthomas/repr v0.4.0 // indirect
github.com/alexkohler/nakedret/v2 v2.0.4 // indirect
github.com/alexkohler/prealloc v1.0.0 // indirect
github.com/alingse/asasalint v0.0.11 // indirect
- github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/andybalholm/brotli v1.2.0 // indirect
github.com/ashanbrown/forbidigo v1.6.0 // indirect
github.com/ashanbrown/makezero v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@@ -118,7 +115,7 @@ require (
github.com/catenacyber/perfsprint v0.7.1 // indirect
github.com/ccojocar/zxcvbn-go v1.0.2 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
- github.com/cenkalti/backoff/v4 v4.3.0 // indirect
+ github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charithe/durationcheck v0.0.10 // indirect
github.com/chavacava/garif v0.1.0 // indirect
@@ -126,24 +123,19 @@ require (
github.com/creasty/defaults v1.7.0 // indirect
github.com/curioswitch/go-reassign v0.2.0 // indirect
github.com/daixiang0/gci v0.13.5 // indirect
+ github.com/dave/jennifer v1.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
- github.com/deckarep/golang-set/v2 v2.6.0 // indirect
github.com/denis-tingaikin/go-header v0.5.0 // indirect
- github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
github.com/ettle/strcase v0.2.0 // indirect
- github.com/fatih/color v1.17.0 // indirect
+ github.com/fatih/color v1.18.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
github.com/firefart/nonamedreturns v1.0.5 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/fzipp/gocyclo v0.6.0 // indirect
- github.com/gabriel-vasile/mimetype v1.4.3 // indirect
- github.com/getkin/kin-openapi v0.124.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/ghostiam/protogetter v0.3.6 // indirect
github.com/go-critic/go-critic v0.11.4 // 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.20.2 // indirect
- github.com/go-openapi/swag v0.22.8 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-toolsmith/astcast v1.1.0 // indirect
@@ -165,32 +157,32 @@ require (
github.com/golangci/plugin-module-register v0.1.1 // indirect
github.com/golangci/revgrep v0.5.3 // indirect
github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect
- github.com/google/go-cmp v0.6.0 // indirect
- github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect
+ github.com/google/go-cmp v0.7.0 // indirect
+ github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gordonklaus/ineffassign v0.1.0 // indirect
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
github.com/gostaticanalysis/comment v1.4.2 // indirect
github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect
github.com/gostaticanalysis/nilerr v0.1.1 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
+ github.com/huandu/go-clone v1.5.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/invopop/yaml v0.3.1 // indirect
github.com/jgautheron/goconst v1.7.1 // indirect
github.com/jhump/protoreflect v1.17.0 // indirect
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect
github.com/jjti/go-spancheck v0.6.2 // indirect
github.com/joho/godotenv v1.5.1 // indirect
- github.com/josharian/intern v1.0.0 // indirect
github.com/julz/importas v0.1.0 // indirect
- github.com/k0kubun/pp/v3 v3.2.0 // indirect
+ github.com/k0kubun/pp/v3 v3.5.0 // indirect
github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect
github.com/kisielk/errcheck v1.7.0 // indirect
github.com/kkHAIKE/contextcheck v1.1.5 // indirect
- github.com/klauspost/compress v1.17.9 // indirect
+ github.com/klauspost/compress v1.18.0 // indirect
github.com/kulti/thelper v0.6.3 // indirect
github.com/kunwardeep/paralleltest v1.0.10 // indirect
github.com/kyoh86/exportloopref v0.1.11 // indirect
@@ -199,53 +191,51 @@ require (
github.com/ldez/tagliatelle v0.5.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/leonklingele/grouper v1.1.2 // indirect
+ github.com/lmittmann/tint v1.1.2 // indirect
github.com/lufeee/execinquery v1.2.1 // indirect
github.com/macabu/inamedparam v0.1.3 // indirect
github.com/magiconair/properties v1.8.7 // indirect
- github.com/mailru/easyjson v0.7.7 // indirect
github.com/maratori/testableexamples v1.0.0 // indirect
github.com/maratori/testpackage v1.1.1 // indirect
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect
- github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-runewidth v0.0.15 // indirect
- github.com/mattn/go-shellwords v1.0.12 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgechev/revive v1.3.9 // indirect
- github.com/miekg/dns v1.1.50 // indirect
+ github.com/miekg/dns v1.1.66 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/moricho/tparallel v0.3.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nakabonne/nestif v0.3.1 // indirect
- github.com/nats-io/nats.go v1.37.0 // indirect
- github.com/nats-io/nkeys v0.4.7 // indirect
- github.com/nats-io/nuid v1.0.1 // indirect
github.com/nishanths/exhaustive v0.12.0 // indirect
github.com/nishanths/predeclared v0.2.2 // indirect
github.com/nunnatsa/ginkgolinter v0.16.2 // indirect
- github.com/panjf2000/ants/v2 v2.10.0 // indirect
+ github.com/olekukonko/errors v1.1.0 // indirect
+ github.com/olekukonko/ll v0.0.9 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
- github.com/perimeterx/marshmallow v1.1.5 // indirect
- github.com/phuslu/goid v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/polyfloyd/go-errorlint v1.6.0 // indirect
- github.com/prometheus/client_model v0.6.1 // indirect
- github.com/prometheus/procfs v0.12.0 // indirect
+ github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 // indirect
+ github.com/prometheus/client_model v0.6.2 // indirect
+ github.com/prometheus/common v0.65.0 // indirect
+ github.com/prometheus/procfs v0.17.0 // indirect
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect
github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect
github.com/quasilyte/gogrep v0.5.0 // indirect
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
- github.com/rivo/uniseg v0.2.0 // indirect
+ github.com/rivo/uniseg v0.4.7 // indirect
github.com/ryancurrah/gomodguard v1.3.5 // indirect
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
+ github.com/samber/slog-common v0.19.0 // indirect
github.com/sanposhiho/wastedassign/v2 v2.0.7 // indirect
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
github.com/sashamelentyev/usestdlibvars v1.27.0 // indirect
- github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 // indirect
+ github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 // indirect
github.com/securego/gosec/v2 v2.21.2 // indirect
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
@@ -269,11 +259,10 @@ require (
github.com/timonwong/loggercheck v0.9.4 // indirect
github.com/tomarrell/wrapcheck/v2 v2.9.0 // indirect
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
- github.com/twmb/murmur3 v1.1.5 // indirect
+ github.com/twmb/murmur3 v1.1.8 // indirect
github.com/ultraware/funlen v0.1.0 // indirect
github.com/ultraware/whitespace v0.1.1 // indirect
github.com/uudashr/gocognit v1.1.3 // indirect
- github.com/valyala/tcplisten v1.0.0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xen0n/gosmopolitan v1.2.2 // indirect
github.com/yagipy/maintidx v1.0.0 // indirect
@@ -282,15 +271,16 @@ require (
gitlab.com/bosi/decorder v0.4.2 // indirect
go-simpler.org/musttag v0.12.2 // indirect
go-simpler.org/sloglint v0.7.2 // indirect
- go.opentelemetry.io/proto/otlp v1.2.0 // indirect
+ go.opentelemetry.io/auto/sdk v1.1.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
- go.uber.org/zap v1.24.0 // indirect
- golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
+ go.uber.org/zap v1.27.0 // indirect
+ golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f // indirect
- golang.org/x/sync v0.8.0 // indirect
- golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect
- golang.org/x/text v0.18.0 // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
+ golang.org/x/sync v0.18.0 // indirect
+ golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 // indirect
+ golang.org/x/text v0.31.0 // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
honnef.co/go/tools v0.5.1 // indirect
diff --git a/go.sum b/go.sum
index e4890f5d4..cdaeb9d45 100644
--- a/go.sum
+++ b/go.sum
@@ -2,12 +2,11 @@
4d63.com/gocheckcompilerdirectives v1.2.1/go.mod h1:yjDJSxmDTtIHHCqX0ufRYZDL6vQtMG7tJdKVeWwsqvs=
4d63.com/gochecknoglobals v0.2.1 h1:1eiorGsgHOFOuoOiJDy2psSrQbRdIHrlge0IJIkUgDc=
4d63.com/gochecknoglobals v0.2.1/go.mod h1:KRE8wtJB3CXCsb1xy421JfTHIIbmT3U5ruxw2Qu8fSU=
-cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w=
-cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg=
+cel.dev/expr v0.23.0 h1:wUb94w6OYQS4uXraxo9U+wUAs9jT47Xvl4iPgAwM2ss=
+cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
-dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
+dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
+dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/4meepo/tagalign v1.3.4 h1:P51VcvBnf04YkHzjfclN6BbsopfJR5rxs1n+5zHt+w8=
github.com/4meepo/tagalign v1.3.4/go.mod h1:M+pnkHH2vG8+qhE5bVc/zeP7HS/j910Fwa9TUSyZVI0=
github.com/Abirdcfly/dupword v0.1.1 h1:Bsxe0fIw6OwBtXMIncaTxCLHYO5BB+3mcsR5E8VXloY=
@@ -31,41 +30,33 @@ github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/OpenPeeDeeP/depguard/v2 v2.2.0 h1:vDfG60vDtIuf0MEOhmLlLLSzqaRM8EMcgJPdp74zmpA=
github.com/OpenPeeDeeP/depguard/v2 v2.2.0/go.mod h1:CIzddKRvLBC4Au5aYP/i3nyaWQ+ClszLIuVocRiCYFQ=
-github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg=
-github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
+github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
+github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
github.com/ahmetb/go-linq v3.0.0+incompatible h1:qQkjjOXKrKOTy83X8OpRmnKflXKQIL/mC/gMVVDMhOA=
github.com/ahmetb/go-linq v3.0.0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY=
-github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
-github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
+github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
+github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/go-check-sumtype v0.1.4 h1:WCvlB3l5Vq5dZQTFmodqL2g68uHiSwwlWcT5a2FGK0c=
github.com/alecthomas/go-check-sumtype v0.1.4/go.mod h1:WyYPfhfkdhyrdaligV6svFopZV8Lqdzn5pyVBaV6jhQ=
-github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
-github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
+github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
+github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
-github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
-github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
-github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alexkohler/nakedret/v2 v2.0.4 h1:yZuKmjqGi0pSmjGpOC016LtPJysIL0WEUiaXW5SUnNg=
github.com/alexkohler/nakedret/v2 v2.0.4/go.mod h1:bF5i0zF2Wo2o4X4USt9ntUWve6JbFv02Ff4vlkmS/VU=
github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw=
github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE=
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I=
-github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
-github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
-github.com/arl/statsviz v0.6.0 h1:jbW1QJkEYQkufd//4NDYRSNBpwJNrdzPahF7ZmoGdyE=
-github.com/arl/statsviz v0.6.0/go.mod h1:0toboo+YGSUXDaS4g1D5TVS4dXs7S7YYT5J/qnW2h8s=
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
+github.com/arl/statsviz v0.7.1 h1:W32VBGV/YBTMg3sBsr1Ix1bJzrykZHWNZS7quqtDqYc=
+github.com/arl/statsviz v0.7.1/go.mod h1:uFJZYUcGDeFpo/Mb9nLkq/83YUYaib2IccejOm+y1t0=
github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY=
github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU=
github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s=
github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI=
-github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
-github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
-github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bkielbasa/cyclop v1.2.1 h1:AeF71HZDob1P2/pRm1so9cd1alZnrpyc4q2uP2l0gJY=
@@ -84,19 +75,15 @@ github.com/butuzov/ireturn v0.3.0 h1:hTjMqWw3y5JC3kpnC5vXmFJAWI/m31jaCYQqzkS6PL0
github.com/butuzov/ireturn v0.3.0/go.mod h1:A09nIiwiqzN/IoVo9ogpa0Hzi9fex1kd9PSD6edP5ZA=
github.com/butuzov/mirror v1.2.0 h1:9YVK1qIjNspaqWutSv8gsge2e/Xpq1eqEkslEUHy5cs=
github.com/butuzov/mirror v1.2.0/go.mod h1:DqZZDtzm42wIAIyHXeN8W/qb1EPlb9Qn/if9icBOpdQ=
-github.com/cactus/go-statsd-client/v5 v5.0.0/go.mod h1:COEvJ1E+/E2L4q6QE5CkjWPi4eeDw9maJBMIuMPBZbY=
github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyyTJ/rMmc=
github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50=
github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg=
github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
-github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
-github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
+github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
-github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charithe/durationcheck v0.0.10 h1:wgw73BiocdBDQPik+zcEoBG/ob8uyBHf2iyoHGPf5w4=
@@ -114,8 +101,8 @@ github.com/ckaznocha/intrange v0.2.0/go.mod h1:r5I7nUlAAG56xmkOpw4XVr16BXhwYTUdc
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
-github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
+github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f h1:C5bqEmzEPLsHm9Mv73lSE9e9bKV23aB1vxOsmZrkl3k=
+github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
@@ -129,8 +116,6 @@ github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM=
-github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4=
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
github.com/ecordell/optgen v0.0.9 h1:kmRMqOkbNsWayOnZSk2m5SeGaOTOc7amfi+MAnaMOeI=
@@ -139,19 +124,20 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc=
-github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA=
+github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
+github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM=
-github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4=
+github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
+github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
-github.com/fasthttp/router v1.5.0 h1:3Qbbo27HAPzwbpRzgiV5V9+2faPkPt3eNuRaDV6LYDA=
-github.com/fasthttp/router v1.5.0/go.mod h1:FddcKNXFZg1imHcy+uKB0oo/o6yE9zD3wNguqlhWDak=
-github.com/fasthttp/websocket v1.5.8 h1:k5DpirKkftIF/w1R8ZzjSgARJrs54Je9YJK37DL/Ah8=
-github.com/fasthttp/websocket v1.5.8/go.mod h1:d08g8WaT6nnyvg9uMm8K9zMYyDjfKyj3170AtPRuVU0=
-github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
-github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
+github.com/expr-lang/expr v1.17.5 h1:i1WrMvcdLF249nSNlpQZN1S6NXuW9WaOfF5tPi3aw3k=
+github.com/expr-lang/expr v1.17.5/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4=
+github.com/fasthttp/websocket v1.5.12 h1:e4RGPpWW2HTbL3zV0Y/t7g0ub294LkiuXXUuTOUInlE=
+github.com/fasthttp/websocket v1.5.12/go.mod h1:I+liyL7/4moHojiOgUOIKEWm9EIxHqxZChS+aMFltyg=
+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/felixge/fgprof v0.9.5 h1:8+vR6yu2vvSKn08urWyEuxx75NWPEvybbkBirEpsbVY=
@@ -166,44 +152,32 @@ github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6
github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc=
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
-github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
-github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
-github.com/getkin/kin-openapi v0.124.0 h1:VSFNMB9C9rTKBnQ/fpyDU8ytMTr4dWI9QovSKj9kz/M=
-github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM=
+github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
+github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/ghostiam/protogetter v0.3.6 h1:R7qEWaSgFCsy20yYHNIJsU9ZOb8TziSRRxuAOTVKeOk=
github.com/ghostiam/protogetter v0.3.6/go.mod h1:7lpeDnEJ1ZjL/YtyoN99ljO4z0pd3H0d18/t2dPBxHw=
github.com/go-critic/go-critic v0.11.4 h1:O7kGOCx0NDIni4czrkRIXTnit0mkyKOCePh3My6OyEU=
github.com/go-critic/go-critic v0.11.4/go.mod h1:2QAdo4iuLik5S9YG0rT4wcZ8QxwHYkrr6/2MWAiv/vc=
-github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
-github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
-github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
-github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
-github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
+github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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-openapi/jsonpointer v0.20.2 h1:mQc3nmndL8ZBzStEo3JYF8wzmeWffDH4VbXz58sAx6Q=
-github.com/go-openapi/jsonpointer v0.20.2/go.mod h1:bHen+N0u1KEO3YlmqOjTT9Adn1RfD91Ar825/PuiRVs=
-github.com/go-openapi/swag v0.22.8 h1:/9RjDSQ0vbFR+NyjGMkFTsA1IA0fmhKSThmfGZjicbw=
-github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI=
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.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
-github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
+github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
-github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
-github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8=
github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU=
github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s=
@@ -232,27 +206,23 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
-github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
-github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4=
github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc=
-github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM=
-github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
+github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
+github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
-github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
-github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
-github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@@ -262,7 +232,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
@@ -295,12 +264,14 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
-github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gops v0.3.28 h1:2Xr57tqKAmQYRAfG12E+yLcoa2Y42UJo2lOrUFL9ark=
+github.com/google/gops v0.3.28/go.mod h1:6f6+Nl8LcHrzJwi8+p0ii+vmBFSlB4f8cOOkTJ7sk4c=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
-github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ=
-github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
+github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ=
+github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA=
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -308,8 +279,8 @@ 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/gordonklaus/ineffassign v0.1.0 h1:y2Gd/9I7MdY1oEIt+n+rowjBNDcLQq3RsH5hwJd0f9s=
github.com/gordonklaus/ineffassign v0.1.0/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0=
-github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
-github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc=
github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado=
@@ -322,18 +293,12 @@ github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW
github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M=
github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY=
github.com/gostaticanalysis/testutil v0.4.0/go.mod h1:bLIoPefWXrRi/ssLFWX1dx7Repi5x3CuviD3dgAZaBU=
-github.com/goyek/goyek/v2 v2.2.0 h1:FXdta04rwVN/HE33XKcBtYht6iaDkJ9Z5Bdh1TdXjO0=
-github.com/goyek/goyek/v2 v2.2.0/go.mod h1:mqU0PSD8q3TRCzhJ1mL79/X7FOrXJcpvOtDEBXi92+E=
-github.com/goyek/workflow v0.0.0-20240815094733-414d2a904ff9 h1:rB0kFkFNb66K5JPGDrh+xibOJQBmCQgcC4EkKYCkU/w=
-github.com/goyek/workflow v0.0.0-20240815094733-414d2a904ff9/go.mod h1:ApuON/7oB3flhv3Fy5XrKe3K/lQDkVedvWjP/mUAq4Y=
-github.com/goyek/x v0.2.0 h1:vLTzDTqbUQnWTM3NOnejGAWwWZA+hJHElv/cd/3ybQc=
-github.com/goyek/x v0.2.0/go.mod h1:I1yv17Zj0g3Zv5iDC+kB9gh3cqJquJSTEgfognEfe94=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
@@ -341,12 +306,13 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
+github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c=
+github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
+github.com/huandu/go-clone v1.5.1 h1:1wlwYRlHZo4HspdOM0YQ6O7Y7bjtxTrrt+4jnDeejVo=
+github.com/huandu/go-clone v1.5.1/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE=
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
-github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
-github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
-github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jgautheron/goconst v1.7.1 h1:VpdAG7Ca7yvvJk5n8dMwQhfEZJh95kl/Hl9S1OI5Jkk=
github.com/jgautheron/goconst v1.7.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
@@ -365,20 +331,13 @@ github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pk
github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
-github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
-github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
-github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
-github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
-github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY=
github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0=
-github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs=
-github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA=
+github.com/k0kubun/pp/v3 v3.5.0 h1:iYNlYA5HJAJvkD4ibuf9c8y6SHM0QFhaBuCqm1zHp0w=
+github.com/k0kubun/pp/v3 v3.5.0/go.mod h1:5lzno5ZZeEeTV/Ky6vs3g6d1U3WarDrH8k240vMtGro=
github.com/karamaru-alpha/copyloopvar v1.1.0 h1:x7gNyKcC2vRBO1H2Mks5u1VxQtYvFiym7fCjIP8RPos=
github.com/karamaru-alpha/copyloopvar v1.1.0/go.mod h1:u7CIfztblY0jZLOQZgH3oYsJzpC2A7S6u/lfgSXHy0k=
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19 h1:WjT3fLi9n8YWh/Ih8Q1LHAPsTqGddPcHqscN+PJ3i68=
@@ -389,11 +348,9 @@ github.com/kisielk/errcheck v1.7.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg=
github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA=
-github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
-github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
-github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -405,6 +362,8 @@ github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs=
github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I=
github.com/kunwardeep/paralleltest v1.0.10 h1:wrodoaKYzS2mdNVnc4/w31YaXFtsc21PCTdvWJ/lDDs=
github.com/kunwardeep/paralleltest v1.0.10/go.mod h1:2C7s65hONVqY7Q5Efj5aLzRCNLjw2h4eMc9EcypGjcY=
+github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
+github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyoh86/exportloopref v0.1.11 h1:1Z0bcmTypkL3Q4k+IDHMWTcnCliEZcaPiIe0/ymEyhQ=
github.com/kyoh86/exportloopref v0.1.11/go.mod h1:qkV4UF1zGl6EkF1ox8L5t9SwyeBAZ3qLMd6up458uqA=
github.com/lasiar/canonicalheader v1.1.1 h1:wC+dY9ZfiqiPwAexUApFush/csSPXeIi4QqyxXmng8I=
@@ -418,43 +377,43 @@ 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/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY=
github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA=
+github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
+github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM=
github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM=
github.com/macabu/inamedparam v0.1.3 h1:2tk/phHkMlEL/1GNe/Yf6kkR/hkcUdAEY3L0hjYV1Mk=
github.com/macabu/inamedparam v0.1.3/go.mod h1:93FLICAIk/quk7eaPPQvbzihUdn/QkGDwIZEoLtpH6I=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
-github.com/mailgun/holster/v4 v4.19.0 h1:BQ390TMYg7CxBTMvZsHbq0fKnfRY7wxNb0uV+ZdwTJk=
-github.com/mailgun/holster/v4 v4.19.0/go.mod h1:/5ijRCyMjOHxt69WdAgvB2gyYCapJaJdT/QciGIcu50=
-github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailgun/holster/v4 v4.21.0 h1:EH3fwKEGv56WA5gUwxjOTqZbeILY+oJ/VWEo1xku7t8=
+github.com/mailgun/holster/v4 v4.21.0/go.mod h1:G06Q741dj+zsH1WFrmoFvih3LtaocvBIoNtxITdWEtg=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/maragudk/gomponents v0.20.0 h1:lJn5PMYHAKm8jywfk3UiL9tm54ZBkv5R5WktY4S8kxY=
-github.com/maragudk/gomponents v0.20.0/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg=
+github.com/maragudk/gomponents v0.22.0 h1:0gNrSDC1nM6w0Vxj5wgGXqV8frDH9UVPE+dEyy4ApPQ=
+github.com/maragudk/gomponents v0.22.0/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg=
github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI=
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
+github.com/maruel/panicparse/v2 v2.5.0 h1:yCtuS0FWjfd0RTYMXGpDvWcb0kINm8xJGu18/xMUh00=
+github.com/maruel/panicparse/v2 v2.5.0/go.mod h1:DA2fDiBk63bKfBf4CVZP9gb4fuvzdPbLDsSI873hweQ=
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 h1:gWg6ZQ4JhDfJPqlo2srm/LN17lpybq15AryXIRcWYLE=
github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
-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-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
+github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
-github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
-github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
-github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
-github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
-github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A=
github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU=
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
-github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
-github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
+github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
+github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
@@ -462,32 +421,26 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
-github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
-github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
-github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE=
-github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
-github.com/nats-io/nats.go v1.37.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
-github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI=
-github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc=
-github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
-github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhKRf3Swg=
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
github.com/nunnatsa/ginkgolinter v0.16.2 h1:8iLqHIZvN4fTLDC0Ke9tbSZVcyVHoBs0HIbnVSxfHJk=
github.com/nunnatsa/ginkgolinter v0.16.2/go.mod h1:4tWRinDN1FeJgU+iJANW/kz7xKN5nYRAOfJDQUS9dOQ=
-github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
-github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
+github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
+github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
+github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
+github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=
+github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4=
github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag=
github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8=
@@ -501,17 +454,10 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
-github.com/panjf2000/ants/v2 v2.10.0 h1:zhRg1pQUtkyRiOFo2Sbqwjp0GfBNo9cUY2/Grpx1p+8=
-github.com/panjf2000/ants/v2 v2.10.0/go.mod h1:7ZxyxsqE4vvW0M7LSD8aI3cKwgFhBHbxnlN8mDqHa1I=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
-github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
-github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
-github.com/phuslu/goid v1.0.0 h1:Cgcvd/R54UO1fCtyt+iKXAi+yZQ/KWlAm6MmZNizCLM=
-github.com/phuslu/goid v1.0.0/go.mod h1:txc2fUIdrdnn+v9Vq+QpiPQ3dnrXEchjoVDgic+r+L0=
-github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -523,37 +469,21 @@ github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg
github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
-github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
-github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
-github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
-github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
-github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
-github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
-github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
-github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582 h1:eR+0HE//Ciyfwy3HC7fjRyKShSJHYoX2Pv7pPshjK/Q=
+github.com/projectdiscovery/machineid v0.0.0-20250715113114-c77eb3567582/go.mod h1:3G3BRKui7nMuDFAZKR/M2hiOLtaOmyukT20g88qRQjI=
+github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
+github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
-github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
-github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
-github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
-github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
-github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
-github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
-github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
-github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
-github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
-github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
-github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
-github.com/pubgo/dix v0.3.19 h1:a4RRmljw7ePUvc9uJKqXXs6JpYL8s3VqPP2NV2vxI9U=
-github.com/pubgo/dix v0.3.19/go.mod h1:J0PyuMm7mW6ZrN13l9xz0rtKbzliREFyvb7fuaGYyVw=
-github.com/pubgo/funk v0.5.56 h1:LYpjOBDSSeTorRWda0lmf23sJNDm6ZtbbNS0xQjb1CE=
-github.com/pubgo/funk v0.5.56/go.mod h1:uAs5IQDz6ovr5zy4LC0Uq7No/eTIbeloSCjF0EQJesw=
-github.com/pubgo/opendoc v0.0.5 h1:aM6xkBQ0XMSq8OWytl5JhTMBUv8L+aYQFXd2z9eNkZE=
-github.com/pubgo/opendoc v0.0.5/go.mod h1:uO//pJZTJNFEKGuGWrv51/of0EoyxO1il4MuoC3Enp0=
-github.com/pubgo/protobuild v0.0.21 h1:QSwh92QxsD6Mv7kQlowzE7fxtcOm1eMWmgADsD803Js=
-github.com/pubgo/protobuild v0.0.21/go.mod h1:gCBq+4Wldy2OS7zkjWASpXtbq8pzWDZw6mOEyAnh3x8=
+github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
+github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
+github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
+github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
+github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
+github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
+github.com/pubgo/dix/v2 v2.0.0-beta.2 h1:vLPl7/ftL2qCgm6iNZM62jqtfsdvXA6Gqt48Gjnc+Ew=
+github.com/pubgo/dix/v2 v2.0.0-beta.2/go.mod h1:jV/9KWf+YxtoQATuZLyUraACduxHvfaum5EZDSCK5gE=
+github.com/pubgo/funk/v2 v2.0.0-beta.2 h1:Y9tiaq8YLbvJ6JBZtbzypiA55Go8DXrjLJd+71dj6ec=
+github.com/pubgo/funk/v2 v2.0.0-beta.2/go.mod h1:E2QfiKP4IKOM0iVbwK+VBBtQcgeNwyeRbEDXVK97nPY=
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo=
github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI=
github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE=
@@ -564,23 +494,26 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
-github.com/reugn/go-quartz v0.11.2 h1:+jc54Ji06n/D/endEPmc+CuG/Jc8466nda1oxtFRrks=
-github.com/reugn/go-quartz v0.11.2/go.mod h1:no4ktgYbAAuY0E1SchR8cTx1LF4jYIzdgaQhzRPSkpk=
-github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/reugn/go-quartz v0.15.1 h1:8hMtC+ERa9G5tiL9fiuBpPreId+Tu1Xt2pkXVeTZAy0=
+github.com/reugn/go-quartz v0.15.1/go.mod h1:00DVnBKq2Fxag/HlR9mGXjmHNlMFQ1n/LNM+Fn0jUaE=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
-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/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
-github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
-github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
+github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
+github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
+github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
+github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV3oJmPU=
github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE=
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
-github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
-github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
+github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
+github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
+github.com/samber/slog-common v0.19.0 h1:fNcZb8B2uOLooeYwFpAlKjkQTUafdjfqKcwcC89G9YI=
+github.com/samber/slog-common v0.19.0/go.mod h1:dTz+YOU76aH007YUU0DffsXNsGFQRQllPQh9XyNoA3M=
github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc=
github.com/sanposhiho/wastedassign/v2 v2.0.7/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
@@ -589,17 +522,15 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI=
github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8=
-github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511 h1:KanIMPX0QdEdB4R3CiimCAbxFrhB3j7h0/OvpYGVQa8=
-github.com/savsgio/gotils v0.0.0-20240303185622-093b76447511/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
+github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287 h1:qIQ0tWF9vxGtkJa24bR+2i53WBCz1nW/Pc47oVYauC4=
+github.com/savsgio/gotils v0.0.0-20250408102913-196191ec6287/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg=
github.com/securego/gosec/v2 v2.21.2 h1:deZp5zmYf3TWwU7A7cR2+SolbTpZ3HQiwFqnzQyEl3M=
github.com/securego/gosec/v2 v2.21.2/go.mod h1:au33kg78rNseF5PwPnTWhuYBFf534bvJRvOrgZ/bFzU=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU=
github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs=
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
-github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
-github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
@@ -639,10 +570,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.7.0/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.2/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/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tdakkota/asciicheck v0.2.0 h1:o8jvnUANo0qXtnslk2d3nMKTFNlOnJjRrNcj0j9qkHM=
@@ -653,6 +583,8 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
github.com/tetafro/godot v1.4.17 h1:pGzu+Ye7ZUEFx7LHU0dAKmCOXWsPjl7qA6iMGndsjPs=
github.com/tetafro/godot v1.4.17/go.mod h1:2oVxTBSftRTh4+MVfUaUXR6bn2GDXCaMcOG4Dk3rfio=
+github.com/thejerf/suture/v4 v4.0.6 h1:QsuCEsCqb03xF9tPAsWAj8QOAJBgQI1c0VqJNaingg8=
+github.com/thejerf/suture/v4 v4.0.6/go.mod h1:gu9Y4dXNUWFrByqRt30Rm9/UZ0wzRSt9AJS6xu/ZGxU=
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
@@ -667,34 +599,34 @@ github.com/tomarrell/wrapcheck/v2 v2.9.0 h1:801U2YCAjLhdN8zhZ/7tdjB3EnAoRlJHt/s+
github.com/tomarrell/wrapcheck/v2 v2.9.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo=
github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw=
github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw=
-github.com/twmb/murmur3 v1.1.5 h1:i9OLS9fkuLzBXjt6dptlAEyk58fJsSTXbRg3SgVyqgk=
-github.com/twmb/murmur3 v1.1.5/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
-github.com/uber-go/tally/v4 v4.1.16 h1:by2hveWRh/cUReButk6ns1sHK/hiKry7BuOV6iY16XI=
-github.com/uber-go/tally/v4 v4.1.16/go.mod h1:RW5DgqsyEPs0lA4b0YNf4zKj7DveKHd73hnO6zVlyW0=
-github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
-github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
+github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
+github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
+github.com/uber-go/tally/v4 v4.1.17 h1:C+U4BKtVDXTszuzU+WH8JVQvRVnaVKxzZrROFyDrvS8=
+github.com/uber-go/tally/v4 v4.1.17/go.mod h1:ZdpiHRGSa3z4NIAc1VlEH4SiknR885fOIF08xmS0gaU=
+github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
+github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI=
github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4=
github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ=
github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
-github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240717192922-127cf54fac9f h1:yCJ90PBe7+45EQSF3qJXyAGW5rkE65lE8huv5pM0HY8=
-github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240717192922-127cf54fac9f/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
+github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
+github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM=
github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U=
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/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
-github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
+github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns=
+github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
-github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU=
github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg=
+github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
+github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM=
github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk=
github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5JsjqtoFs=
@@ -716,56 +648,58 @@ go-simpler.org/musttag v0.12.2 h1:J7lRc2ysXOq7eM8rwaTYnNrHd5JwjppzB6mScysB2Cs=
go-simpler.org/musttag v0.12.2/go.mod h1:uN1DVIasMTQKk6XSik7yrJoEysGtR2GRqvWnI9S7TYM=
go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY=
go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo=
-go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
-go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0 h1:+hm+I+KigBy3M24/h1p/NHkUx/evbLH0PNcjpMyCHc4=
-go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.26.0/go.mod h1:NjC8142mLvvNT6biDpaMjyz78kyEHIwAJlSX0N9P5KI=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0 h1:1u/AyyOqAWzy+SkPxDpahCNZParHV8Vid1RnI2clyDE=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0 h1:Waw9Wfpo/IXzOI8bCB7DIk+0JZcqqsyn1JFnAc+iam8=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo=
-go.opentelemetry.io/otel/exporters/prometheus v0.45.2 h1:pe2Jqk1K18As0RCw7J08QhgXNqr+6npx0a5W4IgAFA8=
-go.opentelemetry.io/otel/exporters/prometheus v0.45.2/go.mod h1:B38pscHKI6bhFS44FDw0eFU3iqG3ASNIvY+fZgR5sAc=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1 h1:C8r95vDR125t815KD+b1tI0Fbc1pFnwHTBxkbIZ6Szc=
-go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1/go.mod h1:Qr0qomr64jentMtOjWMbtYeJMSuMSlsPEjmnRA2sWZ4=
-go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0 h1:EVSnY9JbEEW92bEkIYOVMw4q1WJxIAGoFTrtYOzWuRQ=
-go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.28.0/go.mod h1:Ea1N1QQryNXpCD0I1fdLibBAIpQuBkznMmkdKrapk1Y=
-go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
-go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
-go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
-go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
-go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y=
-go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE=
-go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
-go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
-go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
-go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
+go.opentelemetry.io/contrib/zpages v0.62.0 h1:9fUYTLmrK0x/lweM2uM+BOx069jLx8PxVqWhegGJ9Bo=
+go.opentelemetry.io/contrib/zpages v0.62.0/go.mod h1:C8kXoiC1Ytvereztus2R+kqdSa6W/MZ8FfS8Zwj+LiM=
+go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
+go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A=
+go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
+go.opentelemetry.io/otel/exporters/prometheus v0.59.0 h1:HHf+wKS6o5++XZhS98wvILrLVgHxjA/AMjqHKes+uzo=
+go.opentelemetry.io/otel/exporters/prometheus v0.59.0/go.mod h1:R8GpRXTZrqvXHDEGVH5bF6+JqAZcK8PjJcZ5nGhEWiE=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0 h1:6VjV6Et+1Hd2iLZEPtdV7vie80Yyqf7oikJLjQ/myi0=
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.37.0/go.mod h1:u8hcp8ji5gaM/RfcOo8z9NMnf1pVLfVY7lBY2VOGuUU=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4=
+go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
+go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
+go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
+go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
+go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
+go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
+go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
+go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
+go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=
+go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
-go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
-go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
+go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
+go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
-go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
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/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
-go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
-go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
-golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
-golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
-golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
+golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
+golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
-golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
+golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
+golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/exp/typeparams v0.0.0-20240314144324-c7f7c6466f7f h1:phY1HzDcf18Aq9A8KkmRtY9WvOFIxN8wgfvy6Zm1DV8=
@@ -785,16 +719,13 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
-golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
+golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
+golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -803,42 +734,32 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
-golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
+golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
+golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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-20200625203802-6e8e738ad208/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-20201207232520-09787c993a3a/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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
-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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -846,9 +767,7 @@ golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -864,10 +783,10 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
-golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0=
-golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
+golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
+golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
+golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -882,8 +801,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
-golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -906,7 +825,6 @@ golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0t
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
@@ -914,8 +832,12 @@ golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
-golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
+golang.org/x/tools/go/expect v0.1.0-deprecated h1:jY2C5HGYR5lqex3gEniOQL0r7Dq5+VGVgY1nudX5lXY=
+golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
+golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
+golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8=
golang.org/x/vuln v1.1.3 h1:NPGnvPOTgnjBc9HTaUx+nj+EaUYxl5SJOWqaDYGaFYw=
golang.org/x/vuln v1.1.3/go.mod h1:7Le6Fadm5FOqE9C926BCD0g12NWyhg7cxV4BwcPFuNY=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -928,18 +850,18 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
-google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
+google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY=
+google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
-google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
+google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
+google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
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=
@@ -951,9 +873,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689 h1:hNwajDgT0MlsxZzlUajZVmUYFpts8/CYe4BSNx503ZE=
-google.golang.org/protobuf v1.34.3-0.20240816073751-94ecbc261689/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
-gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -961,13 +882,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
-gopkg.in/validator.v2 v2.0.0-20200605151824-2b28d334fa05/go.mod h1:o4V0GXN9/CAmCsvJ0oXYZvrZOe7syiDZSN1GWGZTGzc=
-gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -978,6 +894,8 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
+maragu.dev/gomponents v1.2.0 h1:H7/N5htz1GCnhu0HB1GasluWeU2rJZOYztVEyN61iTc=
+maragu.dev/gomponents v1.2.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=
mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU=
mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo=
mvdan.cc/unparam v0.0.0-20240528143540-8a5130ca722f h1:lMpcwN6GxNbWtbpI1+xzFLSW8XzX0u72NttUGVFjO3U=
diff --git a/internal/configs/components/app.yaml b/internal/configs/components/app.yaml
new file mode 100644
index 000000000..1c5be3d41
--- /dev/null
+++ b/internal/configs/components/app.yaml
@@ -0,0 +1,4 @@
+app:
+ env: ${ENV:-"dev"}
+go_pool:
+ worker_size: 100
diff --git a/internal/configs/components/grpc_server.yaml b/internal/configs/components/grpc_server.yaml
new file mode 100644
index 000000000..2023be14c
--- /dev/null
+++ b/internal/configs/components/grpc_server.yaml
@@ -0,0 +1,8 @@
+grpc_server:
+ base_url: /welogin
+ enable_print_router: true
+
+ # 256k
+# ws_read_limit: 262144
+# grpc_port: 8000
+# http_port: 8001
diff --git a/internal/configs/components/http_server.yaml b/internal/configs/components/http_server.yaml
new file mode 100644
index 000000000..c7ed96dbc
--- /dev/null
+++ b/internal/configs/components/http_server.yaml
@@ -0,0 +1,4 @@
+http_server:
+ base_url: /scheduler
+ enable_print_router: true
+# http_port: 8001
diff --git a/internal/configs/components/logger.yaml b/internal/configs/components/logger.yaml
new file mode 100644
index 000000000..60acd39ab
--- /dev/null
+++ b/internal/configs/components/logger.yaml
@@ -0,0 +1,5 @@
+logger:
+ level: ${LOG_LEVEL:-"debug"}
+ as_json: ${LOG_AS_JSON:-false}
+ filters:
+ - level not in ["debug"]
diff --git a/internal/configs/components/metric.yaml b/internal/configs/components/metric.yaml
new file mode 100644
index 000000000..964bfba1f
--- /dev/null
+++ b/internal/configs/components/metric.yaml
@@ -0,0 +1,3 @@
+metric:
+ driver: "prometheus"
+ interval: 5s
diff --git a/internal/configs/scheduler.yaml b/internal/configs/scheduler.yaml
new file mode 100644
index 000000000..e62322fb6
--- /dev/null
+++ b/internal/configs/scheduler.yaml
@@ -0,0 +1,8 @@
+resources:
+ - components
+
+patch_resources:
+ - .local.yaml
+
+patch_envs:
+ - envs/envs.yaml
diff --git a/internal/examples/fileserver/main.go b/internal/examples/fileserver/main.go
new file mode 100644
index 000000000..cbb8d1a66
--- /dev/null
+++ b/internal/examples/fileserver/main.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "context"
+
+ "github.com/pubgo/funk/v2/env"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/pubgo/lava/v2/cmds/fileservercmd"
+ "github.com/pubgo/lava/v2/core/lavabuilder"
+ "github.com/pubgo/lava/v2/core/logging/logext/slog"
+)
+
+func main() {
+ defer recovery.Exit()
+
+ slog.SetLogger(log.GetLogger())
+ log.SetEnableChecker(func(ctx context.Context, lvl log.Level, name, message string, fields log.Fields) bool {
+ if running.Debug() {
+ return true
+ }
+
+ if name == "dix" || name == "env" {
+ return false
+ }
+ return true
+ })
+
+ env.Reload()
+
+ builder := lavabuilder.New()
+ builder.Provide(fileservercmd.New)
+
+ lavabuilder.Run(builder)
+}
diff --git a/internal/examples/scheduler/main.go b/internal/examples/scheduler/main.go
new file mode 100644
index 000000000..355c76ba6
--- /dev/null
+++ b/internal/examples/scheduler/main.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/pubgo/funk/v2/cmds/configcmd"
+ "github.com/pubgo/funk/v2/cmds/envcmd"
+ "github.com/pubgo/funk/v2/config"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/pubgo/lava/v2/core/lavabuilder"
+ "github.com/pubgo/lava/v2/core/logging"
+ "github.com/pubgo/lava/v2/core/metrics"
+ "github.com/pubgo/lava/v2/core/scheduler"
+ "github.com/pubgo/lava/v2/servers/https"
+)
+
+type Config struct {
+ metrics.MetricConfigLoader `yaml:",inline"`
+ logging.LogConfigLoader `yaml:",inline"`
+ https.HttpServerConfigLoader `yaml:",inline"`
+}
+
+var _ scheduler.JobRegister = (*schedulerExample)(nil)
+
+type schedulerExample struct {
+}
+
+func (s schedulerExample) RegisterSchedulerJob(reg scheduler.JobRegistry) {
+ reg.Once("once_task", time.Second*10, func(ctx context.Context, name string, metadata *scheduler.JobMetadata) result.Result[[]byte] {
+ fmt.Printf("exec once task: %s: %#v\n", name, metadata)
+ time.Sleep(time.Second * 5)
+ return result.OK([]byte("once"))
+ })
+
+ reg.Every("every_task", time.Second*5, func(ctx context.Context, name string, metadata *scheduler.JobMetadata) result.Result[[]byte] {
+ fmt.Printf("exec every task: %s: %#v\n", name, metadata)
+ time.Sleep(time.Second * 1)
+ return result.OK([]byte("every"))
+ })
+
+ reg.Cron("cron_task", "*/7 * * * * *", func(ctx context.Context, name string, metadata *scheduler.JobMetadata) result.Result[[]byte] {
+ fmt.Printf("exec cron task: %s: %#v\n", name, metadata)
+ time.Sleep(time.Second * 2)
+ return result.OK([]byte("cron"))
+ })
+}
+
+func main() {
+ defer recovery.Exit()
+
+ builder := lavabuilder.New()
+ builder.Provide(config.Load[Config])
+ builder.Provide(envcmd.New)
+ builder.Provide(configcmd.New[Config])
+ builder.Provide(func() scheduler.JobRegister { return new(schedulerExample) })
+
+ lavabuilder.Run(builder)
+}
diff --git a/internal/examples/scheduler/taskfile.yml b/internal/examples/scheduler/taskfile.yml
new file mode 100644
index 000000000..34ac11d0a
--- /dev/null
+++ b/internal/examples/scheduler/taskfile.yml
@@ -0,0 +1,26 @@
+# https://taskfile.dev
+
+version: '3'
+
+vars:
+ SchedulerRelease: "v0.1.0"
+ SchedulerProject: "scheduler"
+
+tasks:
+ info:
+ cmds:
+ - echo "{{.SchedulerRelease}}" "{{.SchedulerProject}}"
+
+ default:
+ cmds:
+ - task scheduler:info
+
+ build:
+ cmds:
+# - go build -v -x -race -ldflags="-w" -o ./bin/scheduler ./internal/examples/scheduler/main.go
+ - go build -o ./bin/scheduler ./internal/examples/scheduler/main.go
+ - ls -alh ./bin
+ run:
+ cmds:
+ - task scheduler:build
+ - SERVER_HTTP_PORT=8082 ./bin/scheduler scheduler -c ./internal/configs/scheduler.yaml
diff --git a/internal/examples/xz/main.go b/internal/examples/xz/main.go
new file mode 100644
index 000000000..d6106de3d
--- /dev/null
+++ b/internal/examples/xz/main.go
@@ -0,0 +1,40 @@
+// Copyright 2014-2022 Ulrich Kunitz. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "log"
+ "os"
+
+ "github.com/ulikunitz/xz"
+)
+
+func main() {
+ const text = "The quick brown fox jumps over the lazy dog.\n"
+ var buf bytes.Buffer
+ // compress text
+ w, err := xz.NewWriter(&buf)
+ if err != nil {
+ log.Fatalf("xz.NewWriter error %s", err)
+ }
+ if _, err := io.WriteString(w, text); err != nil {
+ log.Fatalf("WriteString error %s", err)
+ }
+ if err := w.Close(); err != nil {
+ log.Fatalf("w.Close error %s", err)
+ }
+ fmt.Println(buf.Len(), buf.String())
+ // decompress buffer and write output to stdout
+ r, err := xz.NewReader(&buf)
+ if err != nil {
+ log.Fatalf("NewReader error %s", err)
+ }
+ if _, err = io.Copy(os.Stdout, r); err != nil {
+ log.Fatalf("io.Copy error %s", err)
+ }
+}
diff --git a/internal/logutil/util.go b/internal/logutil/util.go
index 71d3df9db..c9a1d9576 100644
--- a/internal/logutil/util.go
+++ b/internal/logutil/util.go
@@ -4,13 +4,13 @@ import (
"log/slog"
"strings"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/try"
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/try"
)
func HandlerErr(err error) {
- if err == nil || generic.IsNil(err) {
+ if err == nil {
return
}
@@ -24,7 +24,7 @@ func HandleClose(log log.Logger, fn func() error) {
}
err := fn()
- if generic.IsNil(err) {
+ if funk.IsNil(err) {
return
}
@@ -36,7 +36,7 @@ func LogOrErr(log log.Logger, msg string, fn func() error) {
log = log.WithCallerSkip(1)
err := try.Try(fn)
- if generic.IsNil(err) {
+ if funk.IsNil(err) {
log.Info().Msg(msg)
} else {
log.Err(err).Msg(msg)
@@ -48,7 +48,7 @@ func OkOrFailed(log log.Logger, msg string, fn func() error) {
log.Info().Msg(msg)
err := try.Try(fn)
- if generic.IsNil(err) {
+ if funk.IsNil(err) {
log.Info().Msg(msg + " ok")
} else {
log.Err(err).Msg(msg + " failed")
@@ -56,7 +56,7 @@ func OkOrFailed(log log.Logger, msg string, fn func() error) {
}
func ErrRecord(logger log.Logger, err error, fn func(evt *log.Event) string) {
- if generic.IsNil(err) {
+ if funk.IsNil(err) {
return
}
diff --git a/internal/middlewares/middleware_accesslog/middleware.go b/internal/middlewares/middleware_accesslog/middleware.go
index 1a989ffc3..dbd49b90d 100644
--- a/internal/middlewares/middleware_accesslog/middleware.go
+++ b/internal/middlewares/middleware_accesslog/middleware.go
@@ -6,17 +6,17 @@ import (
"time"
"github.com/gofiber/utils"
- "github.com/pubgo/funk/convert"
- "github.com/pubgo/funk/errors/errutil"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/proto/errorpb"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/core/lavacontexts"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/grpcutil"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/convert"
+ "github.com/pubgo/funk/v2/errors/errcode"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/proto/errorpb"
"github.com/rs/zerolog"
"google.golang.org/grpc/codes"
+
+ "github.com/pubgo/lava/v2/core/lavacontexts"
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/grpcutil"
)
const Name = "accesslog"
@@ -66,11 +66,11 @@ func (l LogMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc {
// 错误和panic处理
defer func() {
- if !generic.IsNil(gErr) {
- logOpts := handleLogOption(req.Header().PeekAll("X-Log-Option"))
- if logOpts["all"] {
+ if gErr != nil {
+ evt.Stringer("req_header", req.Header())
+ logOpts := handleLogOption(req.Header())
+ if logOpts.EnableAll() {
evt.Any("req_body", req.Payload())
- evt.Bytes("req_header", req.Header().Header())
if rsp != nil {
evt.Any("rsp_body", rsp.Payload())
evt.Any("rsp_header", rsp.Header())
@@ -92,7 +92,7 @@ func (l LogMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc {
// 记录错误日志
var e *zerolog.Event
- if generic.IsNil(gErr) {
+ if gErr == nil {
// Record requests with a timeout of 200 milliseconds
//if latency > time.Millisecond*200 && !req.Stream() {
// e = l.logger.Err(errTimeout).Func(log.WithEvent(evt))
@@ -100,66 +100,48 @@ func (l LogMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc {
e = l.logger.Info().Func(log.WithEvent(evt))
//}
} else {
- //errors.Debug(gErr)
+ // errors.Debug(gErr)
e = l.logger.Err(gErr).Func(log.WithEvent(evt))
- pb := errutil.ParseError(gErr)
- {
- if pb.Trace == nil {
- pb.Trace = new(errorpb.ErrTrace)
- }
- pb.Trace.Operation = req.Operation()
- pb.Trace.Service = req.Service()
- pb.Trace.Version = version.Version()
+ pb := errcode.ParseError(gErr)
+ if pb.Message == "" {
+ pb.Message = gErr.Error()
}
- {
- if pb.Msg != nil {
- pb.Msg = new(errorpb.ErrMsg)
- }
- pb.Msg.Msg = gErr.Error()
- pb.Msg.Detail = fmt.Sprintf("%#v", gErr)
- if pb.Msg.Tags == nil {
- pb.Msg.Tags = make(map[string]string)
- }
+ if pb.StatusCode == errorpb.Code_OK {
+ log.Warn(ctx).Any("code", pb.Code).Msg("grpc response error with status code is 0")
}
- {
- if pb.Code.Message == "" {
- pb.Code.Message = gErr.Error()
- }
-
- if pb.Code.StatusCode == errorpb.Code_OK {
- log.Warn(ctx).Any("code", pb.Code).Msg("grpc response error with status code is 0")
- }
-
- if pb.Code.Code == 0 {
- pb.Code.Code = int32(errutil.GrpcCodeToHTTP(codes.Code(pb.Code.StatusCode)))
- pb.Code.StatusCode = errorpb.Code_Internal
- }
+ if pb.Code == 0 {
+ pb.Code = int32(errcode.GrpcCodeToHTTP(codes.Code(pb.StatusCode)))
+ pb.StatusCode = errorpb.Code_Internal
}
- gErr = errutil.ConvertErr2Status(pb).Err()
+ gErr = errcode.ConvertErr2Status(pb).Err()
}
e.Msg("record request")
}()
// 集成logger到context
- ctx = log.CreateEventCtx(ctx, log.NewEvent().Str("request_id", reqId).Str("operation", req.Operation()))
+ ctx = log.CreateFieldsCtx(ctx, log.Fields{"request_id": reqId, "operation": req.Operation()})
return next(ctx, req)
}
}
-func handleLogOption(data [][]byte) (val map[string]bool) {
- if len(data) == 0 {
- val = map[string]bool{}
- return
- }
-
- val = make(map[string]bool, len(data))
+func handleLogOption(header *lava.RequestHeader) *logOption {
+ data := header.PeekAll("X-Log-Option")
+ val := make(map[string]bool, len(data))
for i := range data {
val[convert.B2S(data[i])] = true
}
- return val
+ return &logOption{data: val}
+}
+
+type logOption struct {
+ data map[string]bool
+}
+
+func (opt logOption) EnableAll() bool {
+ return opt.data["all"]
}
diff --git a/internal/middlewares/middleware_metric/middleware.go b/internal/middlewares/middleware_metric/middleware.go
index feab56ec1..1245a02fb 100644
--- a/internal/middlewares/middleware_metric/middleware.go
+++ b/internal/middlewares/middleware_metric/middleware.go
@@ -5,11 +5,11 @@ import (
"strings"
"time"
- "github.com/pubgo/funk/generic"
+ "github.com/pubgo/funk/v2"
"github.com/uber-go/tally/v4"
- "github.com/pubgo/lava/core/metrics"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/core/metrics"
+ "github.com/pubgo/lava/v2/lava"
)
// grpc metric
@@ -57,7 +57,7 @@ func (m MetricMiddleware) Middleware(next lava.HandlerFunc) lava.HandlerFunc {
grpcServerRpcCallTotal(m.m, req.Operation())
defer func() {
- if !generic.IsNil(gErr) {
+ if !funk.IsNil(gErr) {
grpcServerRpcErrTotal(m.m, req.Operation())
}
diff --git a/internal/middlewares/middleware_recovery/middleware.go b/internal/middlewares/middleware_recovery/middleware.go
index 9ed3c2e00..45dcac031 100644
--- a/internal/middlewares/middleware_recovery/middleware.go
+++ b/internal/middlewares/middleware_recovery/middleware.go
@@ -2,11 +2,11 @@ package middleware_recovery
import (
"context"
- "runtime/debug"
- "github.com/pubgo/funk/errors"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/errors/errparser"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/lava"
)
func New() lava.Middleware {
@@ -15,10 +15,7 @@ func New() lava.Middleware {
Next: func(next lava.HandlerFunc) lava.HandlerFunc {
return func(ctx context.Context, req lava.Request) (rsp lava.Response, gErr error) {
defer func() {
- if err := errors.Parse(recover()); err != nil {
- debug.PrintStack()
- gErr = errors.WrapStack(err)
- }
+ gErr = errors.WrapStack(errparser.Parse(recover()))
}()
return next(ctx, req)
diff --git a/internal/middlewares/middleware_service_info/middleware.go b/internal/middlewares/middleware_serviceinfo/middleware.go
similarity index 84%
rename from internal/middlewares/middleware_service_info/middleware.go
rename to internal/middlewares/middleware_serviceinfo/middleware.go
index debfa106b..187a58a94 100644
--- a/internal/middlewares/middleware_service_info/middleware.go
+++ b/internal/middlewares/middleware_serviceinfo/middleware.go
@@ -1,18 +1,19 @@
-package middleware_service_info
+package middleware_serviceinfo
import (
"context"
- "github.com/pubgo/funk/convert"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/strutil"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/core/lavacontexts"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/grpcutil"
- "github.com/pubgo/lava/pkg/httputil"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/convert"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/pubgo/funk/v2/strutil"
"github.com/rs/xid"
+
+ "github.com/pubgo/lava/v2/core/lavacontexts"
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/grpcutil"
+ "github.com/pubgo/lava/v2/pkg/httputil"
+ "github.com/pubgo/lava/v2/pkg/proto/lavapbv1"
)
func New() lava.Middleware {
diff --git a/lava/annotation.go b/lava/annotation.go
deleted file mode 100644
index 7da449eee..000000000
--- a/lava/annotation.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package lava
-
-// Annotation is used to attach arbitrary metadata to the schema objects
-type Annotation interface {
- Name() string
-}
-
-type Annotations = []Annotation
diff --git a/lava/middleware.go b/lava/middleware.go
index de0c0c100..43d827ee9 100644
--- a/lava/middleware.go
+++ b/lava/middleware.go
@@ -1,19 +1,25 @@
package lava
-import "context"
+import (
+ "context"
+)
type HandlerFunc func(ctx context.Context, req Request) (Response, error)
type Middlewares []Middleware
type Middleware interface {
- Middleware(next HandlerFunc) HandlerFunc
String() string
+ Middleware(next HandlerFunc) HandlerFunc
+}
+
+func WithMiddleware(name string, next func(next HandlerFunc) HandlerFunc) MiddlewareWrap {
+ return MiddlewareWrap{Name: name, Next: next}
}
type MiddlewareWrap struct {
- Next func(next HandlerFunc) HandlerFunc
Name string
+ Next func(next HandlerFunc) HandlerFunc
}
func (m MiddlewareWrap) Middleware(next HandlerFunc) HandlerFunc {
@@ -24,16 +30,16 @@ func (m MiddlewareWrap) String() string {
return m.Name
}
-func Chain(m ...Middleware) Middleware {
+func Chain(middlewares ...Middleware) Middleware {
return MiddlewareWrap{
Name: "chain",
Next: func(next HandlerFunc) HandlerFunc {
- for i := len(m) - 1; i >= 0; i-- {
- if m[i] == nil {
+ for i := len(middlewares) - 1; i >= 0; i-- {
+ if middlewares[i] == nil {
continue
}
- next = m[i].Middleware(next)
+ next = middlewares[i].Middleware(next)
}
return next
},
diff --git a/lava/request.go b/lava/request.go
index 3d921709d..0d4073dcb 100644
--- a/lava/request.go
+++ b/lava/request.go
@@ -4,6 +4,13 @@ import (
"github.com/valyala/fasthttp"
)
+type RequestKind = string
+
+const (
+ RequestKindHttp RequestKind = "http"
+ RequestKindGrpc RequestKind = "grpc"
+)
+
type RequestHeader = fasthttp.RequestHeader
// Request is a synchronous request interface
@@ -12,26 +19,26 @@ type Request interface {
Client() bool
// Kind [http|grpc...]
- Kind() string
+ Kind() RequestKind
// Stream Indicates whether it's a stream
Stream() bool
- // Service name requested
+ // Service name requested, grpc service name
Service() string
- // Operation requested
+ // Operation requested, grpc method name
Operation() string
- // Endpoint requested
+ // Endpoint requested, http router path
Endpoint() string
- // ContentType Content type provided
+ // ContentType Content type provided, application/json, application/grpc
ContentType() string
// Header of the request
Header() *RequestHeader
- // Payload is the decoded value
- Payload() interface{}
+ // Payload is the decoded value, []byte or proto message
+ Payload() any
}
diff --git a/lava/response.go b/lava/response.go
index 17284fa9d..e4cde0454 100644
--- a/lava/response.go
+++ b/lava/response.go
@@ -6,9 +6,14 @@ import (
type ResponseHeader = fasthttp.ResponseHeader
-// Response is the response writer for un encoded messages
+// Response is the response writer interface
type Response interface {
+ // Header returns the response header
Header() *ResponseHeader
- Payload() interface{}
+
+ // Payload returns the response payload, []byte or protobuf message
+ Payload() any
+
+ // Stream returns true if the response is a stream
Stream() bool
}
diff --git a/lava/router.go b/lava/router.go
index a193f0f81..467672abb 100644
--- a/lava/router.go
+++ b/lava/router.go
@@ -1,25 +1,30 @@
package lava
import (
- "context"
-
"github.com/gofiber/fiber/v2"
- "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
)
-type ProxyCfg struct {
- // Name service name
+type GrpcProxyCfg struct {
+ // Name grpc service name
Name string `yaml:"name"`
- // Addr service address
+
+ // Addr grpc service address, dns://auth:8080, auth:8080
Addr string `yaml:"addr"`
- // Resolver service resolver, default direct
+
+ // Resolver service resolver[direct, k8s, dns, etc...], default direct
Resolver string `yaml:"resolver"`
}
type GrpcProxy interface {
GrpcRouter
- Proxy() ProxyCfg
+ Proxy() GrpcProxyCfg
+}
+
+type GrpcHttpRouter interface {
+ GrpcRouter
+ Router(router fiber.Router)
+ Prefix() string
}
type GrpcRouter interface {
@@ -27,14 +32,10 @@ type GrpcRouter interface {
ServiceDesc() *grpc.ServiceDesc
}
-type GrpcGatewayRouter interface {
- GrpcRouter
- RegisterGateway(ctx context.Context, mux *runtime.ServeMux, conn grpc.ClientConnInterface) error
-}
-
type HttpRouter interface {
Middlewares() []Middleware
Router(router fiber.Router)
+
+ // Prefix router prefix, required
Prefix() string
- // Annotation() []Annotation
}
diff --git a/lava/server.go b/lava/server.go
new file mode 100644
index 000000000..1e46ff361
--- /dev/null
+++ b/lava/server.go
@@ -0,0 +1,19 @@
+package lava
+
+import (
+ "context"
+ "net"
+)
+
+type Closer interface {
+ Close(ctx context.Context) error
+}
+
+// Listener provides an interface for starting and stopping the server.
+type Listener interface {
+ Listen(context.Context, net.Listener) error
+}
+
+type Validator interface {
+ Validate() error
+}
diff --git a/lava/service.go b/lava/service.go
deleted file mode 100644
index 5d071a8dc..000000000
--- a/lava/service.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package lava
-
-import (
- "context"
- "net"
-
- "google.golang.org/grpc"
-)
-
-type Init interface {
- Init()
-}
-
-type Close interface {
- Close()
-}
-
-type Service interface {
- Start()
- Stop()
- Run()
-}
-
-// Server provides an interface for starting and stopping the server.
-type Server interface {
- Serve(context.Context, net.Listener) error
-}
-
-type Validator interface {
- Validate() error
-}
-
-// Initializer ...
-type Initializer interface {
- Initialize()
-}
-
-type InnerServer struct {
- grpc.ClientConnInterface
-}
diff --git a/main.go b/main.go
deleted file mode 100644
index 05d8e1267..000000000
--- a/main.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package main
-
-import (
- "flag"
-
- "github.com/goyek/goyek/v2"
- "github.com/goyek/workflow"
- "github.com/goyek/x/boot"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/lava/buildtasks"
- "github.com/pubgo/lava/core/flags"
-)
-
-func main() {
- goyek.Undefine(workflow.PipelineAll)
-
- workflow.StageTest.SetDeps(append(workflow.StageTest.Deps(), buildtasks.GoLint))
- goyek.SetDefault(goyek.Define(goyek.Task{
- Name: "all",
- Usage: "exec all tasks",
- Deps: goyek.Deps{
- workflow.StageInit,
- workflow.StageBuild,
- workflow.StageTest,
- },
- }))
-
- for _, f := range flags.GetFlags() {
- assert.Exit(f.Apply(flag.CommandLine))
- }
- boot.Main()
-}
diff --git a/pkg/cmdutil/cmd.go b/pkg/cliutil/cmd.go
similarity index 64%
rename from pkg/cmdutil/cmd.go
rename to pkg/cliutil/cmd.go
index a1ce3ab80..86cc2c9ba 100644
--- a/pkg/cmdutil/cmd.go
+++ b/pkg/cliutil/cmd.go
@@ -1,11 +1,11 @@
-package cmdutil
+package cliutil
import (
"fmt"
"strings"
)
-func ExampleFmt(data ...string) string {
+func ExampleDesc(data ...string) string {
str := ""
for i := range data {
str += " " + data[i] + "\n"
@@ -13,7 +13,7 @@ func ExampleFmt(data ...string) string {
return " " + strings.TrimSpace(str)
}
-func UsageDesc(format string, args ...interface{}) string {
+func UsageDesc(format string, args ...any) string {
s := fmt.Sprintf(format, args...)
return strings.ToUpper(s[0:1]) + s[1:]
}
diff --git a/pkg/cmdutil/flag.go b/pkg/cliutil/flag.go
similarity index 83%
rename from pkg/cmdutil/flag.go
rename to pkg/cliutil/flag.go
index 434ba0507..f0845790d 100644
--- a/pkg/cmdutil/flag.go
+++ b/pkg/cliutil/flag.go
@@ -1,4 +1,4 @@
-package cmdutil
+package cliutil
import (
"flag"
@@ -25,6 +25,3 @@ func IsHelp() bool {
arg := strings.TrimSpace(os.Args[len(os.Args)-1])
return arg == "--help" || arg == "-h"
}
-
-//https://github.com/valyala/fasttemplate
-//"text/template"
diff --git a/pkg/fasttemplate/template.go b/pkg/fasttemplate/template.go
index c4d271e30..c8dc7d5c8 100644
--- a/pkg/fasttemplate/template.go
+++ b/pkg/fasttemplate/template.go
@@ -5,10 +5,10 @@ import (
"fmt"
"io"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/errors"
- "github.com/pubgo/funk/convert"
+ "github.com/pubgo/funk/v2/convert"
"github.com/valyala/bytebufferpool"
)
@@ -71,7 +71,7 @@ func ExecuteFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int
//
// This function is optimized for constantly changing templates.
// Use Template.Execute for frozen templates.
-func Execute(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {
+func Execute(template, startTag, endTag string, w io.Writer, m map[string]any) (int64, error) {
return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
@@ -87,7 +87,7 @@ func Execute(template, startTag, endTag string, w io.Writer, m map[string]interf
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteStd for frozen templates.
-func ExecuteStd(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {
+func ExecuteStd(template, startTag, endTag string, w io.Writer, m map[string]any) (int64, error) {
return ExecuteFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) })
}
@@ -138,7 +138,7 @@ var byteBufferPool bytebufferpool.Pool
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteString for frozen templates.
-func ExecuteString(template, startTag, endTag string, m map[string]interface{}) string {
+func ExecuteString(template, startTag, endTag string, m map[string]any) string {
return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
@@ -152,7 +152,7 @@ func ExecuteString(template, startTag, endTag string, m map[string]interface{})
//
// This function is optimized for constantly changing templates.
// Use Template.ExecuteStringStd for frozen templates.
-func ExecuteStringStd(template, startTag, endTag string, m map[string]interface{}) string {
+func ExecuteStringStd(template, startTag, endTag string, m map[string]any) string {
return ExecuteFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, startTag, endTag, tag, m) })
}
@@ -302,7 +302,7 @@ func (t *Template) ExecuteFunc(w io.Writer, f TagFunc) (int64, error) {
// - TagFunc - flexible value type
//
// Returns the number of bytes written to w.
-func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error) {
+func (t *Template) Execute(w io.Writer, m map[string]any) (int64, error) {
return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
@@ -315,7 +315,7 @@ func (t *Template) Execute(w io.Writer, m map[string]interface{}) (int64, error)
// - TagFunc - flexible value type
//
// Returns the number of bytes written to w.
-func (t *Template) ExecuteStd(w io.Writer, m map[string]interface{}) (int64, error) {
+func (t *Template) ExecuteStd(w io.Writer, m map[string]any) (int64, error) {
return t.ExecuteFunc(w, func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) })
}
@@ -364,7 +364,7 @@ func (t *Template) ExecuteFuncStringWithErr(f TagFunc) (string, error) {
//
// This function is optimized for frozen templates.
// Use ExecuteString for constantly changing templates.
-func (t *Template) ExecuteString(m map[string]interface{}) string {
+func (t *Template) ExecuteString(m map[string]any) string {
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })
}
@@ -378,11 +378,11 @@ func (t *Template) ExecuteString(m map[string]interface{}) string {
//
// This function is optimized for frozen templates.
// Use ExecuteStringStd for constantly changing templates.
-func (t *Template) ExecuteStringStd(m map[string]interface{}) string {
+func (t *Template) ExecuteStringStd(m map[string]any) string {
return t.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { return keepUnknownTagFunc(w, t.startTag, t.endTag, tag, m) })
}
-func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {
+func stdTagFunc(w io.Writer, tag string, m map[string]any) (int, error) {
v := m[tag]
if v == nil {
return 0, nil
@@ -400,7 +400,7 @@ func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error)
}
}
-func keepUnknownTagFunc(w io.Writer, startTag, endTag, tag string, m map[string]interface{}) (int, error) {
+func keepUnknownTagFunc(w io.Writer, startTag, endTag, tag string, m map[string]any) (int, error) {
v, ok := m[tag]
if !ok {
if _, err := w.Write(convert.S2B(startTag)); err != nil {
diff --git a/pkg/fiber_builder/config.go b/pkg/fiber_builder/config.go
deleted file mode 100644
index 1d13bee78..000000000
--- a/pkg/fiber_builder/config.go
+++ /dev/null
@@ -1,47 +0,0 @@
-package fiber_builder
-
-import (
- "time"
-
- "github.com/gofiber/fiber/v2"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/merge"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/result"
-)
-
-type Config struct {
- Prefork bool `yaml:"prefork"`
- ServerHeader string `yaml:"server_header"`
- StrictRouting bool `yaml:"strict_routing"`
- CaseSensitive bool `yaml:"case_sensitive"`
- Immutable bool `yaml:"immutable"`
- UnescapePath bool `yaml:"unescape_path"`
- ETag bool `yaml:"etag"`
- BodyLimit int `yaml:"body_limit"`
- Concurrency int `yaml:"concurrency"`
- Templates struct {
- Dir string `yaml:"dir"`
- Ext string `yaml:"ext"`
- } `yaml:"templates"`
- ReadTimeout time.Duration `yaml:"read_timeout"`
- WriteTimeout time.Duration `yaml:"write_timeout"`
- IdleTimeout time.Duration `yaml:"idle_timeout"`
- ReadBufferSize int `yaml:"read_buffer_size"`
- WriteBufferSize int `yaml:"write_buffer_size"`
- CompressedFileSuffix string `yaml:"compressed_file_suffix"`
- ProxyHeader string `yaml:"proxy_header"`
- GETOnly bool `yaml:"get_only"`
- DisableKeepalive bool `yaml:"disable_keepalive"`
- DisableDefaultDate bool `yaml:"disable_default_date"`
- DisableDefaultContentType bool `yaml:"disable_default_content_type"`
- DisableHeaderNormalizing bool `yaml:"disable_header_normalizing"`
- DisableStartupMessage bool `yaml:"disable_startup_message"`
- ReduceMemoryUsage bool `yaml:"reduce_memory_usage"`
-}
-
-func (t *Config) Build() (r result.Result[*fiber.App]) {
- defer recovery.Result(&r)
- fc := merge.Struct(generic.Ptr(fiber.New().Config()), &t).Unwrap()
- return r.WithVal(fiber.New(*fc))
-}
diff --git a/pkg/fiberbuilder/config.go b/pkg/fiberbuilder/config.go
new file mode 100644
index 000000000..b7fbc8c7d
--- /dev/null
+++ b/pkg/fiberbuilder/config.go
@@ -0,0 +1,126 @@
+package fiberbuilder
+
+import (
+ "log/slog"
+ "time"
+
+ "dario.cat/mergo"
+ "github.com/gofiber/fiber/v2"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/samber/lo"
+)
+
+type Config struct {
+ Prefork bool `yaml:"-"`
+ ServerHeader string `yaml:"-"`
+ CaseSensitive bool `yaml:"-"`
+ Immutable bool `yaml:"-"`
+ UnescapePath bool `yaml:"-"`
+ ProxyHeader string `yaml:"-"`
+ GETOnly bool `yaml:"-"`
+ DisableKeepalive bool `yaml:"-"`
+ DisableDefaultDate bool `yaml:"-"`
+ DisableDefaultContentType bool `yaml:"-"`
+ ErrorHandler fiber.ErrorHandler `yaml:"-" json:"-"`
+ BodyLimit int `yaml:"-"`
+ Concurrency int `yaml:"-"`
+
+ // StreamRequestBody enables request body streaming,
+ // and calls the handler sooner when given body is
+ // larger then the current limit.
+ StreamRequestBody bool `yaml:"stream_request_body"`
+
+ // Will not pre parse Multipart Form data if set to true.
+ //
+ // This option is useful for servers that desire to treat
+ // multipart form data as a binary blob, or choose when to parse the data.
+ //
+ // Server pre parses multipart form data by default.
+ DisablePreParseMultipartForm bool `yaml:"disable_pre_parse_multipart_form"`
+
+ // Aggressively reduces memory usage at the cost of higher CPU usage
+ // if set to true.
+ //
+ // Try enabling this option only if the server consumes too much memory
+ // serving mostly idle keep-alive connections. This may reduce memory
+ // usage by more than 50%.
+ //
+ // Default: false
+ ReduceMemoryUsage bool `yaml:"reduce_memory_usage"`
+
+ // If set to true, c.IP() and c.IPs() will validate IP addresses before returning them.
+ // Also, c.IP() will return only the first valid IP rather than just the raw header
+ // WARNING: this has a performance cost associated with it.
+ //
+ // Default: false
+ EnableIPValidation bool `yaml:"enable_ip_validation"`
+
+ // If set to true, will print all routes with their method, path and handler.
+ // Default: false
+ EnablePrintRoutes bool `yaml:"enable_print_routes"`
+
+ // EnableSplittingOnParsers splits the query/body/header parameters by comma when it's true.
+ // For example, you can use it to parse multiple values from a query parameter like this:
+ // /api?foo=bar,baz == foo[]=bar&foo[]=baz
+ //
+ // Optional. Default: false
+ EnableSplittingOnParsers bool `yaml:"enable_splitting_on_parsers"`
+
+ ETag bool `yaml:"etag"`
+ ReadTimeout time.Duration `yaml:"read_timeout"`
+ WriteTimeout time.Duration `yaml:"write_timeout"`
+ IdleTimeout time.Duration `yaml:"idle_timeout"`
+ ReadBufferSize int `yaml:"read_buffer_size"`
+ WriteBufferSize int `yaml:"write_buffer_size"`
+ CompressedFileSuffix string `yaml:"compressed_file_suffix"`
+ DisableHeaderNormalizing bool `yaml:"disable_header_normalizing"`
+ DisableStartupMessage bool `yaml:"disable_startup_message"`
+}
+
+func (t *Config) ToCfg() fiber.Config {
+ return fiber.Config{
+ Prefork: t.Prefork,
+ ServerHeader: t.ServerHeader,
+ CaseSensitive: t.CaseSensitive,
+ Immutable: t.Immutable,
+ UnescapePath: t.UnescapePath,
+ ProxyHeader: t.ProxyHeader,
+ GETOnly: t.GETOnly,
+ DisableKeepalive: t.DisableKeepalive,
+ DisableDefaultDate: t.DisableDefaultDate,
+ DisableDefaultContentType: t.DisableDefaultContentType,
+ ErrorHandler: t.ErrorHandler,
+ BodyLimit: t.BodyLimit,
+ Concurrency: t.Concurrency,
+ StreamRequestBody: t.StreamRequestBody,
+ DisablePreParseMultipartForm: t.DisablePreParseMultipartForm,
+ ReduceMemoryUsage: t.ReduceMemoryUsage,
+ EnableIPValidation: t.EnableIPValidation,
+ EnablePrintRoutes: t.EnablePrintRoutes,
+ EnableSplittingOnParsers: t.EnableSplittingOnParsers,
+ ETag: t.ETag,
+ ReadTimeout: t.ReadTimeout,
+ WriteTimeout: t.WriteTimeout,
+ IdleTimeout: t.IdleTimeout,
+ ReadBufferSize: t.ReadBufferSize,
+ WriteBufferSize: t.WriteBufferSize,
+ CompressedFileSuffix: t.CompressedFileSuffix,
+ DisableHeaderNormalizing: t.DisableHeaderNormalizing,
+ DisableStartupMessage: t.DisableStartupMessage,
+ }
+}
+
+func (t *Config) Build() (r result.Result[fiber.Config]) {
+ if t == nil {
+ return r.WithValue(fiber.New().Config())
+ }
+
+ defer result.Recovery(&r)
+ cfg := fiber.New().Config()
+ err := mergo.Merge(&cfg, lo.ToPtr(t.ToCfg()), mergo.WithOverride, mergo.WithAppendSlice)
+ if err != nil {
+ slog.Error("failed to merge config", "err", err, "source", t, "target", cfg)
+ return r.WithErrorf("failed to merge config, err:%v", err)
+ }
+ return r.WithValue(cfg)
+}
diff --git a/pkg/gateway/_doc.go b/pkg/gateway/_doc.go
index 5f26478fa..5c610c0d5 100644
--- a/pkg/gateway/_doc.go
+++ b/pkg/gateway/_doc.go
@@ -13,3 +13,14 @@ package gateway
// https://github.com/nhooyr/websocket
// https://github.com/connectrpc/vanguard-go
// https://github.com/emcfarlane/larking
+
+// https://github.com/GoogleCloudPlatform/esp-v2/blob/master/src/go/configgenerator/routegen/helpers/backend_route.go
+// https://github.com/vine-io/vine/blob/e435e77c14082e84c92028ef035d1d7597e8616b/lib/api/router/httprule/runtime.go
+// https://github.com/grpc-ecosystem/grpc-gateway/blob/1bf77dd97e2f74c7511a7405ad7c950d36e45894/runtime/mux.go#L313
+// https://github.com/solo-io/gloo
+// https://github.com/altipla-consulting/protoc-gen-grpc_browser/tree/master
+// https://github.com/emcfarlane/larking/blob/91250e03da0d4670288dbc663aab7481e07c81dc/larking/rules.go#L53
+// https://github.com/connectrpc/vanguard-go
+// https://github.com/stackrox/go-grpc-http1
+// https://github.com/flakrimjusufi/grpc-with-rest
+// https://github.com/tidwall/match/blob/master/match.go
diff --git a/pkg/gateway/aaa.go b/pkg/gateway/aaa.go
index 2884f6150..7ff04beca 100644
--- a/pkg/gateway/aaa.go
+++ b/pkg/gateway/aaa.go
@@ -6,11 +6,12 @@ import (
"net/http"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/lava/pkg/gateway/internal/routertree"
"google.golang.org/grpc"
"google.golang.org/grpc/encoding"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
+
+ "github.com/pubgo/lava/v2/pkg/gateway/routertree"
)
type (
@@ -24,7 +25,7 @@ type (
SetRequestDecoder(protoreflect.FullName, func(ctx *fiber.Ctx, msg proto.Message) error)
SetResponseEncoder(protoreflect.FullName, func(ctx *fiber.Ctx, msg proto.Message) error)
- RegisterService(sd *grpc.ServiceDesc, ss interface{})
+ RegisterService(sd *grpc.ServiceDesc, ss any)
GetOperation(operation string) *GrpcMethod
Handler(*fiber.Ctx) error
@@ -37,7 +38,7 @@ type (
type Codec interface {
encoding.Codec
// MarshalAppend appends the marshaled form of v to b and returns the result.
- MarshalAppend([]byte, interface{}) ([]byte, error)
+ MarshalAppend([]byte, any) ([]byte, error)
}
// StreamCodec is used in streaming RPCs where the message boundaries are
@@ -62,6 +63,8 @@ type Compressor interface {
encoding.Compressor
}
-type GrpcMethodHandler = func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error)
-type GrpcStreamHandler = grpc.StreamHandler
-type StreamDirector func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error)
+type (
+ MethodHandler = func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error)
+ StreamHandler = grpc.StreamHandler
+ StreamDirector func(ctx context.Context, fullMethodName string) (context.Context, grpc.ClientConnInterface, error)
+)
diff --git a/pkg/gateway/codec.go b/pkg/gateway/codec.go
index 3cec71602..e921cd09a 100644
--- a/pkg/gateway/codec.go
+++ b/pkg/gateway/codec.go
@@ -42,7 +42,7 @@ type CodecProto struct {
proto.MarshalOptions
}
-func (c CodecProto) Marshal(v interface{}) ([]byte, error) {
+func (c CodecProto) Marshal(v any) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, errInvalidType(v)
@@ -50,7 +50,7 @@ func (c CodecProto) Marshal(v interface{}) ([]byte, error) {
return c.MarshalOptions.Marshal(m)
}
-func (c CodecProto) MarshalAppend(b []byte, v interface{}) ([]byte, error) {
+func (c CodecProto) MarshalAppend(b []byte, v any) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, errInvalidType(v)
@@ -58,7 +58,7 @@ func (c CodecProto) MarshalAppend(b []byte, v interface{}) ([]byte, error) {
return c.MarshalOptions.MarshalAppend(b, m)
}
-func (CodecProto) Unmarshal(data []byte, v interface{}) error {
+func (CodecProto) Unmarshal(data []byte, v any) error {
m, ok := v.(proto.Message)
if !ok {
return errInvalidType(v)
@@ -132,7 +132,7 @@ type CodecJSON struct {
protojson.UnmarshalOptions
}
-func (c CodecJSON) Marshal(v interface{}) ([]byte, error) {
+func (c CodecJSON) Marshal(v any) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, errInvalidType(v)
@@ -140,7 +140,7 @@ func (c CodecJSON) Marshal(v interface{}) ([]byte, error) {
return c.MarshalOptions.Marshal(m)
}
-func (c CodecJSON) MarshalAppend(b []byte, v interface{}) ([]byte, error) {
+func (c CodecJSON) MarshalAppend(b []byte, v any) ([]byte, error) {
m, ok := v.(proto.Message)
if !ok {
return nil, errInvalidType(v)
@@ -148,7 +148,7 @@ func (c CodecJSON) MarshalAppend(b []byte, v interface{}) ([]byte, error) {
return c.MarshalOptions.MarshalAppend(b, m)
}
-func (c CodecJSON) Unmarshal(data []byte, v interface{}) error {
+func (c CodecJSON) Unmarshal(data []byte, v any) error {
m, ok := v.(proto.Message)
if !ok {
return errInvalidType(v)
@@ -217,15 +217,15 @@ func (CodecJSON) Name() string { return "json" }
type codecHTTPBody struct{}
-func (codecHTTPBody) Marshal(v interface{}) ([]byte, error) {
+func (codecHTTPBody) Marshal(v any) ([]byte, error) {
panic("not implemented")
}
-func (codecHTTPBody) MarshalAppend(b []byte, v interface{}) ([]byte, error) {
+func (codecHTTPBody) MarshalAppend(b []byte, v any) ([]byte, error) {
panic("not implemented")
}
-func (codecHTTPBody) Unmarshal(data []byte, v interface{}) error {
+func (codecHTTPBody) Unmarshal(data []byte, v any) error {
panic("not implemented")
}
diff --git a/pkg/gateway/context.go b/pkg/gateway/context.go
index ea16ff73b..c5b9468b9 100644
--- a/pkg/gateway/context.go
+++ b/pkg/gateway/context.go
@@ -23,11 +23,15 @@ const MetadataPrefix = "grpcgateway-"
// HTTP headers in a response handled by grpc-gateway
const MetadataTrailerPrefix = "Grpc-Trailer-"
-const metadataGrpcTimeout = "Grpc-Timeout"
-const metadataHeaderBinarySuffix = "-Bin"
+const (
+ metadataGrpcTimeout = "Grpc-Timeout"
+ metadataHeaderBinarySuffix = "-Bin"
+)
-const xForwardedFor = "X-Forwarded-For"
-const xForwardedHost = "X-Forwarded-Host"
+const (
+ xForwardedFor = "X-Forwarded-For"
+ xForwardedHost = "X-Forwarded-Host"
+)
// DefaultContextTimeout is used for gRPC call context.WithTimeout whenever a Grpc-Timeout inbound
// header isn't present. If the value is 0 the sent `context` will not have a timeout.
@@ -179,7 +183,7 @@ func ServerMetadataFromContext(ctx context.Context) (md ServerMetadata, ok bool)
return md, false
}
md, ok = ctx.Value(serverMetadataKey{}).(ServerMetadata)
- return
+ return md, ok
}
// ServerTransportStream implements grpc.ServerTransportStream.
@@ -270,7 +274,7 @@ func timeoutUnitToDuration(u uint8) (d time.Duration, ok bool) {
case 'n':
return time.Nanosecond, true
default:
- return
+ return d, ok
}
}
diff --git a/pkg/gateway/fieldmask.go b/pkg/gateway/fieldmask.go
index 63169af00..ef2214056 100644
--- a/pkg/gateway/fieldmask.go
+++ b/pkg/gateway/fieldmask.go
@@ -24,7 +24,7 @@ func getFieldByName(fields protoreflect.FieldDescriptors, name string) protorefl
// FieldMaskFromRequestBody creates a FieldMask printing all complete paths from the JSON body.
func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*fieldmask.FieldMask, error) {
fm := &fieldmask.FieldMask{}
- var root interface{}
+ var root any
if err := json.NewDecoder(r).Decode(&root); err != nil {
if errors.Is(err, io.EOF) {
@@ -39,7 +39,7 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*fieldmask.FieldM
item := queue[0]
queue = queue[1:]
- m, ok := item.node.(map[string]interface{})
+ m, ok := item.node.(map[string]any)
switch {
case ok:
// if the item is an object, then enqueue all of its children
@@ -65,7 +65,7 @@ func FieldMaskFromRequestBody(r io.Reader, msg proto.Message) (*fieldmask.FieldM
}
if isProtobufAnyMessage(fd.Message()) && !fd.IsList() {
- _, hasTypeField := v.(map[string]interface{})["@type"]
+ _, hasTypeField := v.(map[string]any)["@type"]
if hasTypeField {
queue = append(queue, fieldMaskPathItem{path: k})
continue
@@ -122,8 +122,8 @@ func isDynamicProtoMessage(md protoreflect.MessageDescriptor) bool {
// the unmarshalled json contained within in.
// Returns a slice containing all subpaths with the root at the
// passed in name and json value.
-func buildPathsBlindly(name string, in interface{}) []string {
- m, ok := in.(map[string]interface{})
+func buildPathsBlindly(name string, in any) []string {
+ m, ok := in.(map[string]any)
if !ok {
return []string{name}
}
@@ -134,14 +134,14 @@ func buildPathsBlindly(name string, in interface{}) []string {
cur := queue[0]
queue = queue[1:]
- m, ok := cur.node.(map[string]interface{})
+ m, ok := cur.node.(map[string]any)
if !ok {
// This should never happen since we should always check that we only add
// nodes of type map[string]interface{} to the queue.
continue
}
for k, v := range m {
- if mi, ok := v.(map[string]interface{}); ok {
+ if mi, ok := v.(map[string]any); ok {
queue = append(queue, fieldMaskPathItem{path: cur.path + "." + k, node: mi})
} else {
// This is not a struct, so there are no more levels to descend.
@@ -159,7 +159,7 @@ type fieldMaskPathItem struct {
path string
// a generic decoded json object the current item to inspect for further path extraction
- node interface{}
+ node any
// parent message
msg protoreflect.Message
diff --git a/pkg/gateway/query_params.go b/pkg/gateway/gatewayutils/query_params.go
similarity index 96%
rename from pkg/gateway/query_params.go
rename to pkg/gateway/gatewayutils/query_params.go
index fcfd049cb..2a5d3d4e1 100644
--- a/pkg/gateway/query_params.go
+++ b/pkg/gateway/gatewayutils/query_params.go
@@ -1,4 +1,4 @@
-package gateway
+package gatewayutils
import (
"bytes"
@@ -11,15 +11,13 @@ import (
"strings"
"time"
- "github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
- "github.com/pubgo/funk/errors"
+ "github.com/pubgo/funk/v2/errors"
"google.golang.org/grpc/grpclog"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/known/durationpb"
- "google.golang.org/protobuf/types/known/fieldmaskpb"
fieldmask "google.golang.org/protobuf/types/known/fieldmaskpb"
"google.golang.org/protobuf/types/known/structpb"
"google.golang.org/protobuf/types/known/timestamppb"
@@ -32,12 +30,12 @@ var currentQueryParser QueryParameterParser = &DefaultQueryParser{}
// QueryParameterParser defines interface for all query parameter parsers
type QueryParameterParser interface {
- Parse(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error
+ Parse(msg proto.Message, values url.Values, filter *DoubleArray) error
}
// PopulateQueryParameters parses query parameters
// into "msg" using current query parser
-func PopulateQueryParameters(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error {
+func PopulateQueryParameters(msg proto.Message, values url.Values, filter *DoubleArray) error {
return errors.WrapCaller(currentQueryParser.Parse(msg, values, filter))
}
@@ -49,7 +47,7 @@ type DefaultQueryParser struct{}
// Parse populates "values" into "msg".
// A value is ignored if its key starts with one of the elements in "filter".
-func (*DefaultQueryParser) Parse(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error {
+func (*DefaultQueryParser) Parse(msg proto.Message, values url.Values, filter *DoubleArray) error {
for key, v := range values {
if len(v) == 0 {
delete(values, key)
@@ -348,7 +346,7 @@ func parseMessage(msgDescriptor protoreflect.MessageDescriptor, value string) (p
}
msg = &v
default:
- return protoreflect.Value{}, errors.Format("unsupported message type: %q", string(msgDescriptor.FullName()))
+ return protoreflect.Value{}, errors.Errorf("unsupported message type: %q", string(msgDescriptor.FullName()))
}
return protoreflect.ValueOfMessage(msg.ProtoReflect()), nil
@@ -442,7 +440,7 @@ func parseParam(fds []protoreflect.FieldDescriptor, raw []byte) (param, error) {
enumVal := fd.Enum().Values().ByName(protoreflect.Name(s))
if enumVal == nil {
- return param{}, errors.Format("unexpected enum %s", raw)
+ return param{}, errors.Errorf("unexpected enum %s", raw)
}
return param{fds: fds, val: protoreflect.ValueOfEnum(enumVal.Number())}, nil
@@ -519,17 +517,17 @@ func parseParam(fds []protoreflect.FieldDescriptor, raw []byte) (param, error) {
}
return param{fds: fds, val: protoreflect.ValueOfMessage(msg.ProtoReflect())}, nil
case "FieldMask":
- var msg fieldmaskpb.FieldMask
+ var msg fieldmask.FieldMask
if err := protojson.Unmarshal(quote(raw), &msg); err != nil {
return param{}, errors.WrapCaller(err)
}
return param{fds: fds, val: protoreflect.ValueOfMessage(msg.ProtoReflect())}, nil
}
}
- return param{}, errors.Format("unexpected message type %s", name)
+ return param{}, errors.Errorf("unexpected message type %s", name)
default:
- return param{}, errors.Format("unknown param type %s", kind)
+ return param{}, errors.Errorf("unknown param type %s", kind)
}
}
diff --git a/pkg/gateway/gatewayutils/trie.go b/pkg/gateway/gatewayutils/trie.go
new file mode 100644
index 000000000..7bbfbd16c
--- /dev/null
+++ b/pkg/gateway/gatewayutils/trie.go
@@ -0,0 +1,174 @@
+package gatewayutils
+
+import (
+ "sort"
+)
+
+// DoubleArray is a Double Array implementation of trie on sequences of strings.
+type DoubleArray struct {
+ // Encoding keeps an encoding from string to int
+ Encoding map[string]int
+ // Base is the base array of Double Array
+ Base []int
+ // Check is the check array of Double Array
+ Check []int
+}
+
+// NewDoubleArray builds a DoubleArray from a set of sequences of strings.
+func NewDoubleArray(seqs [][]string) *DoubleArray {
+ da := &DoubleArray{Encoding: make(map[string]int)}
+ if len(seqs) == 0 {
+ return da
+ }
+
+ encoded := registerTokens(da, seqs)
+ sort.Sort(byLex(encoded))
+
+ root := node{row: -1, col: -1, left: 0, right: len(encoded)}
+ addSeqs(da, encoded, 0, root)
+
+ for i := len(da.Base); i > 0; i-- {
+ if da.Check[i-1] != 0 {
+ da.Base = da.Base[:i]
+ da.Check = da.Check[:i]
+ break
+ }
+ }
+ return da
+}
+
+func registerTokens(da *DoubleArray, seqs [][]string) [][]int {
+ var result [][]int
+ for _, seq := range seqs {
+ encoded := make([]int, 0, len(seq))
+ for _, token := range seq {
+ if _, ok := da.Encoding[token]; !ok {
+ da.Encoding[token] = len(da.Encoding)
+ }
+ encoded = append(encoded, da.Encoding[token])
+ }
+ result = append(result, encoded)
+ }
+ for i := range result {
+ result[i] = append(result[i], len(da.Encoding))
+ }
+ return result
+}
+
+type node struct {
+ row, col int
+ left, right int
+}
+
+func (n node) value(seqs [][]int) int {
+ return seqs[n.row][n.col]
+}
+
+func (n node) children(seqs [][]int) []*node {
+ var result []*node
+ lastVal := int(-1)
+ last := new(node)
+ for i := n.left; i < n.right; i++ {
+ if lastVal == seqs[i][n.col+1] {
+ continue
+ }
+ last.right = i
+ last = &node{
+ row: i,
+ col: n.col + 1,
+ left: i,
+ }
+ result = append(result, last)
+ }
+ last.right = n.right
+ return result
+}
+
+func addSeqs(da *DoubleArray, seqs [][]int, pos int, n node) {
+ ensureSize(da, pos)
+
+ children := n.children(seqs)
+ var i int
+ for i = 1; ; i++ {
+ ok := func() bool {
+ for _, child := range children {
+ code := child.value(seqs)
+ j := i + code
+ ensureSize(da, j)
+ if da.Check[j] != 0 {
+ return false
+ }
+ }
+ return true
+ }()
+ if ok {
+ break
+ }
+ }
+ da.Base[pos] = i
+ for _, child := range children {
+ code := child.value(seqs)
+ j := i + code
+ da.Check[j] = pos + 1
+ }
+ terminator := len(da.Encoding)
+ for _, child := range children {
+ code := child.value(seqs)
+ if code == terminator {
+ continue
+ }
+ j := i + code
+ addSeqs(da, seqs, j, *child)
+ }
+}
+
+func ensureSize(da *DoubleArray, i int) {
+ for i >= len(da.Base) {
+ da.Base = append(da.Base, make([]int, len(da.Base)+1)...)
+ da.Check = append(da.Check, make([]int, len(da.Check)+1)...)
+ }
+}
+
+type byLex [][]int
+
+func (l byLex) Len() int { return len(l) }
+func (l byLex) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
+func (l byLex) Less(i, j int) bool {
+ si := l[i]
+ sj := l[j]
+ var k int
+ for k = 0; k < len(si) && k < len(sj); k++ {
+ if si[k] < sj[k] {
+ return true
+ }
+ if si[k] > sj[k] {
+ return false
+ }
+ }
+ return k < len(sj)
+}
+
+// HasCommonPrefix determines if any sequence in the DoubleArray is a prefix of the given sequence.
+func (da *DoubleArray) HasCommonPrefix(seq []string) bool {
+ if len(da.Base) == 0 {
+ return false
+ }
+
+ var i int
+ for _, t := range seq {
+ code, ok := da.Encoding[t]
+ if !ok {
+ break
+ }
+ j := da.Base[i] + code
+ if len(da.Check) <= j || da.Check[j] != i+1 {
+ break
+ }
+ i = j
+ }
+ j := da.Base[i] + len(da.Encoding)
+ if len(da.Check) <= j || da.Check[j] != i+1 {
+ return false
+ }
+ return true
+}
diff --git a/pkg/gateway/gatewayutils/utils.go b/pkg/gateway/gatewayutils/utils.go
new file mode 100644
index 000000000..9cc63a449
--- /dev/null
+++ b/pkg/gateway/gatewayutils/utils.go
@@ -0,0 +1,10 @@
+package gatewayutils
+
+import "strconv"
+
+func quote(raw []byte) []byte {
+ if n := len(raw); n > 0 && (raw[0] != '"' || raw[n-1] != '"') {
+ raw = strconv.AppendQuote(raw[:0], string(raw))
+ }
+ return raw
+}
diff --git a/pkg/gateway/code.go b/pkg/gateway/grpccodes.go
similarity index 98%
rename from pkg/gateway/code.go
rename to pkg/gateway/grpccodes.go
index 0d256fda0..383908b75 100644
--- a/pkg/gateway/code.go
+++ b/pkg/gateway/grpccodes.go
@@ -3,7 +3,7 @@ package gateway
import (
"net/http"
- "github.com/pubgo/funk/log"
+ "github.com/pubgo/funk/v2/log"
"google.golang.org/grpc/codes"
)
diff --git a/pkg/gateway/compress.go b/pkg/gateway/internal/compress.go
similarity index 98%
rename from pkg/gateway/compress.go
rename to pkg/gateway/internal/compress.go
index bbf847dea..2e5460999 100644
--- a/pkg/gateway/compress.go
+++ b/pkg/gateway/internal/compress.go
@@ -1,4 +1,4 @@
-package gateway
+package internal
import (
"compress/gzip"
diff --git a/pkg/gateway/internal/routertree/router.go b/pkg/gateway/internal/routertree/router.go
deleted file mode 100644
index bb6b7577d..000000000
--- a/pkg/gateway/internal/routertree/router.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package routertree
-
-import (
- "fmt"
- "strings"
-
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/generic"
-)
-
-var (
- ErrPathNodeNotFound = errors.New("path node not found")
- ErrNotFound = errors.New("operation not found")
-)
-
-func NewRouteTree() *RouteTree {
- return &RouteTree{nodes: make(map[string]*nodeTree)}
-}
-
-type RouteTree struct {
- nodes map[string]*nodeTree
-}
-
-func (r *RouteTree) List() []RouteOperation {
- return getOpt(r.nodes)
-}
-
-func (r *RouteTree) Add(method string, path string, operation string, extras map[string]any) error {
- var errMsg = func() string {
- return fmt.Sprintf("method: %s, path: %s, operation: %s", method, path, operation)
- }
-
- rule, err := parse(path)
- if err != nil {
- return errors.Wrap(err, errMsg())
- }
-
- var node = parseToRoute(rule)
- if len(node.Paths) == 0 {
- return errors.Wrap(fmt.Errorf("path is null"), errMsg())
- }
-
- var nodes = r.nodes
- paths := append(node.Paths, handlerMethod(method))
- for i, n := range paths {
- var lastNode = nodes[n]
- if lastNode == nil {
- lastNode = &nodeTree{nodes: make(map[string]*nodeTree), verbs: make(map[string]*routeTarget)}
- nodes[n] = lastNode
- }
- nodes = lastNode.nodes
-
- if i == len(paths)-1 {
- lastNode.verbs[generic.FromPtr(node.Verb)] = &routeTarget{
- Method: method,
- Path: path,
- Operation: operation,
- extras: extras,
- Verb: node.Verb,
- Vars: node.Vars,
- }
- }
- }
- return nil
-}
-
-func (r *RouteTree) Match(method, url string) (*MatchOperation, error) {
- var paths = strings.Split(strings.Trim(strings.TrimSpace(url), "/"), "/")
- var lastPath = strings.SplitN(paths[len(paths)-1], ":", 2)
- var errMsg = func(tags ...errors.Tag) errors.Tags {
- return append(tags, errors.T("method", method), errors.T("url", url))
- }
- var verb = ""
-
- paths[len(paths)-1] = lastPath[0]
- if len(lastPath) > 1 {
- verb = lastPath[1]
- }
-
- paths = append(paths, handlerMethod(method))
- var getVars = func(vars []*pathVariable, paths []string) []PathFieldVar {
- var vv = make([]PathFieldVar, 0, len(vars))
- for _, v := range vars {
- pathVar := PathFieldVar{Fields: v.Fields}
- if v.end > 0 {
- pathVar.Value = strings.Join(paths[v.start:v.end+1], "/")
- } else {
- pathVar.Value = strings.Join(paths[v.start:], "/")
- }
-
- vv = append(vv, pathVar)
- }
- return vv
- }
- var getPath = func(nodes map[string]*nodeTree, names ...string) *nodeTree {
- for _, n := range names {
- path := nodes[n]
- if path != nil {
- return path
- }
- }
- return nil
- }
-
- var nodes = r.nodes
- for _, n := range paths {
- path := getPath(nodes, n, star, doubleStar)
- if path == nil {
- return nil, errors.WrapFn(ErrPathNodeNotFound, func() errors.Tags {
- return errMsg(errors.T("node", n))
- })
- }
-
- if vv := path.verbs[verb]; vv != nil && vv.Operation != "" && vv.Method == method {
- return &MatchOperation{
- Extras: vv.extras,
- Method: vv.Method,
- Path: vv.Path,
- Operation: vv.Operation,
- Verb: verb,
- Vars: getVars(vv.Vars, paths),
- }, nil
- }
- nodes = path.nodes
- }
-
- return nil, errors.WrapTag(ErrNotFound, errMsg()...)
-}
-
-type RouteOperation struct {
- Method string `json:"method,omitempty"`
- Path string `json:"path,omitempty"`
- Operation string `json:"operation,omitempty"`
- Verb string `json:"verb,omitempty"`
- Vars []string `json:"vars,omitempty"`
- Extras map[string]any `json:"extras"`
-}
-
-type routeTarget struct {
- Method string
- Path string
- Operation string
- Verb *string
- Vars []*pathVariable
- extras map[string]any
-}
-
-type nodeTree struct {
- nodes map[string]*nodeTree
- verbs map[string]*routeTarget
-}
-
-type MatchOperation struct {
- Method string
- Path string
- Operation string
- Verb string
- Vars []PathFieldVar
- Extras map[string]any
-}
-
-func getOpt(nodes map[string]*nodeTree) []RouteOperation {
- var sets []RouteOperation
- for _, n := range nodes {
- for _, v := range n.verbs {
- sets = append(sets, RouteOperation{
- Method: v.Method,
- Path: v.Path,
- Operation: v.Operation,
- Verb: generic.FromPtr(v.Verb),
- Vars: generic.Map(v.Vars, func(i int) string { return strings.Join(v.Vars[i].Fields, ".") }),
- Extras: v.extras,
- })
- }
- sets = append(sets, getOpt(n.nodes)...)
- }
- return sets
-}
-
-func handlerMethod(method string) string {
- return fmt.Sprintf("__%s__", strings.ToUpper(method))
-}
diff --git a/pkg/gateway/internal/routertree/router_test.go b/pkg/gateway/internal/routertree/router_test.go
deleted file mode 100644
index 19d23f4fe..000000000
--- a/pkg/gateway/internal/routertree/router_test.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package routertree
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestRoute(t *testing.T) {
- tree := NewRouteTree()
- assert.NoError(t, tree.Add("get", "/user/user/{id}:get1", "get_user", nil))
- assert.NoError(t, tree.Add("post", "/user/user/{id}", "post_user", nil))
- assert.NoError(t, tree.Add("post", "/user/user/{id}/send_mail", "post_mail", nil))
- opt, err := tree.Match("get", "/user/user/1:get1")
- assert.NoError(t, err)
- assert.NotNil(t, opt)
- assert.Equal(t, "get_user", opt.Operation)
- assert.Equal(t, "get1", opt.Verb)
-}
diff --git a/pkg/gateway/internal/routex/_doc.go b/pkg/gateway/internal/routex/_doc.go
deleted file mode 100644
index e81ab3808..000000000
--- a/pkg/gateway/internal/routex/_doc.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package routex
-
-// https://github.com/GoogleCloudPlatform/esp-v2/blob/master/src/go/configgenerator/routegen/helpers/backend_route.go
-// https://github.com/vine-io/vine/blob/e435e77c14082e84c92028ef035d1d7597e8616b/lib/api/router/httprule/runtime.go
-// https://github.com/grpc-ecosystem/grpc-gateway/blob/1bf77dd97e2f74c7511a7405ad7c950d36e45894/runtime/mux.go#L313
-// https://github.com/solo-io/gloo
-// https://github.com/altipla-consulting/protoc-gen-grpc_browser/tree/master
-// https://github.com/emcfarlane/larking/blob/91250e03da0d4670288dbc663aab7481e07c81dc/larking/rules.go#L53
-// https://github.com/connectrpc/vanguard-go
-// https://github.com/stackrox/go-grpc-http1
-// https://github.com/flakrimjusufi/grpc-with-rest
-// https://github.com/tidwall/match/blob/master/match.go
diff --git a/pkg/gateway/internal/routex/path_parser.go b/pkg/gateway/internal/routex/path_parser.go
deleted file mode 100644
index d3071d51a..000000000
--- a/pkg/gateway/internal/routex/path_parser.go
+++ /dev/null
@@ -1,377 +0,0 @@
-// Copyright 2023-2024 Buf Technologies, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package routex
-
-import (
- "fmt"
- "net/url"
- "strconv"
- "strings"
-
- "github.com/pubgo/funk/errors"
-)
-
-// pathSegments holds the path segments for a HttpMethod.
-// The verb is the final segment, if any. Wildcards segments are annotated by
-// '*' and '**' path values. Each segment is URL unescaped.
-type pathSegments struct {
- path []string // segment path values.
- verb string // final segment verb, if any.
-}
-
-// String returns the URL HttpPath representation of the segments.
-func (s pathSegments) String() string {
- var out strings.Builder
- for _, value := range s.path {
- out.WriteByte('/')
- if value != "*" && value != "**" {
- value = url.PathEscape(value)
- }
- out.WriteString(value)
- }
- if s.verb != "" {
- out.WriteByte(':')
- out.WriteString(url.PathEscape(s.verb))
- }
- return out.String()
-}
-
-// pathVariable holds the HttpPath variables for a HttpMethod.
-// The start and end Fields are the start and end HttpPath segments, inclusive-exclusive.
-// If the end is -1, the variable is unbounded, representing a '**' wildcard capture.
-type pathVariable struct {
- FieldPath string // field HttpPath for the variable.
- start, end int // start and end HttpPath segments, inclusive-exclusive, -1 for unbounded.
-}
-
-// parsePathTemplate parsers a methods template into HttpPath segments and variables.
-//
-// The grammar for the HttpPath template is given in the protobuf definition
-// in [google/api/http.proto].
-//
-// Template = "/" Segments [ Verb ] ;
-// Segments = Segment { "/" Segment } ;
-// Segment = "*" | "**" | LITERAL | Variable ;
-// Variable = "{" FieldPath [ "=" Segments ] "}" ;
-// FieldPath = IDENT { "." IDENT } ;
-// Verb = ":" LITERAL ;
-//
-// [google/api/http.proto]: https://github.com/googleapis/googleapis/blob/ecb1cf0a0021267dd452289fc71c75674ae29fe3/google/api/http.proto#L227-L235
-func parsePathTemplate(template string) (pathSegments, []pathVariable, error) {
- parser := &pathParser{scan: pathScanner{input: template}}
- if err := parser.parseTemplate(); err != nil {
- return pathSegments{}, nil, err
- }
- return parser.segments, parser.variables, nil
-}
-
-// pathParser holds the state for the recursive descent HttpPath template parser.
-type pathParser struct {
- scan pathScanner // scanner for the input.
- seenVars map[string]bool // set of field paths.
- seenDoubleStar bool // true if we've seen a double star wildcard.
- segments pathSegments // output segments.
- variables []pathVariable // output variables.
-}
-
-func (p *pathParser) currentChar() string {
- if char := p.scan.current(); char != eof {
- return strconv.QuoteRune(char)
- }
- return "EOF"
-}
-
-func (p *pathParser) errSyntax(msg string) error {
- return fmt.Errorf("syntax error at column %v: %s", p.scan.pos, msg)
-}
-
-func (p *pathParser) errUnexpected() error {
- return p.errSyntax("unexpected " + p.currentChar())
-}
-
-func (p *pathParser) errExpected(expected rune) error {
- return p.errSyntax("expected " + strconv.QuoteRune(expected) + ", got " + p.currentChar())
-}
-
-func (p *pathParser) parseTemplate() error {
- if !p.scan.consume('/') {
- return p.errExpected('/') // empty HttpPath is not allowed.
- }
- if err := p.parseSegments(); err != nil {
- return err
- }
- switch p.scan.next() {
- case ':':
- p.scan.discard()
- return p.parseVerb()
- case eof:
- return nil
- default:
- return p.errUnexpected()
- }
-}
-
-func (p *pathParser) parseVerb() error {
- literal, err := p.parseLiteral()
- if err != nil {
- return err
- }
- p.segments.verb = literal
- if !p.scan.consume(eof) {
- return p.errUnexpected()
- }
- return nil
-}
-
-func (p *pathParser) parseSegments() error {
- for {
- if err := p.parseSegment(); err != nil {
- return err
- }
- if p.scan.next() != '/' {
- p.scan.backup()
- return nil
- }
- p.scan.discard()
- if p.seenDoubleStar {
- return errors.New("double wildcard '**' must be the final HttpPath segment")
- }
- }
-}
-
-// parseLiteral parses a URL HttpPath segment in URL HttpPath escaped form.
-func (p *pathParser) parseLiteral() (string, error) {
- literal := p.scan.captureRun(isLiteral)
- if literal == "" {
- p.scan.next()
- return "", errors.WrapCaller(p.errUnexpected())
- }
-
- unescaped, err := pathUnescape(literal, pathEncodeSingle)
- if err != nil {
- return "", errors.WrapCaller(p.errSyntax(err.Error()))
- }
-
- return pathEscape(unescaped, pathEncodeSingle), nil
-}
-
-func (p *pathParser) parseSegment() error {
- var segment string
- switch p.scan.next() {
- case '*':
- if p.scan.next() == '*' {
- p.seenDoubleStar = true
- } else {
- p.scan.backup()
- }
- segment = p.scan.capture()
- case '{':
- p.scan.discard()
- return p.parseVariable()
- default:
- if !isLiteral(p.scan.current()) {
- return p.errSyntax("expected HttpPath Value")
- }
- literal, err := p.parseLiteral()
- if err != nil {
- return err
- }
- segment = literal
- }
- p.segments.path = append(p.segments.path, segment)
- return nil
-}
-
-func (p *pathParser) parseFieldPath() (string, error) {
- for {
- if !isIdentStart(p.scan.next()) {
- return "", p.errSyntax("expected identifier")
- }
- for isIdent(p.scan.next()) {
- continue
- }
- if p.scan.current() != '.' {
- p.scan.backup()
- return p.scan.capture(), nil
- }
- }
-}
-
-func (p *pathParser) parseVariable() error {
- fieldPath, err := p.parseFieldPath()
- if err != nil {
- return err
- }
- if p.seenVars[fieldPath] {
- return fmt.Errorf("duplicate variable %q", fieldPath)
- }
- if p.seenVars == nil {
- p.seenVars = make(map[string]bool)
- }
- p.seenVars[fieldPath] = true
-
- variable := pathVariable{FieldPath: fieldPath, start: len(p.segments.path)}
-
- switch p.scan.next() {
- case '}':
- p.scan.discard()
- p.segments.path = append(p.segments.path, "*") // default capture.
- case '=':
- p.scan.discard()
- if err := p.parseSegments(); err != nil {
- return err
- }
- if !p.scan.consume('}') {
- return p.errExpected('}')
- }
- default:
- return p.errExpected('}')
- }
- variable.end = len(p.segments.path)
- if p.seenDoubleStar {
- variable.end = -1 // double star wildcard.
- }
- p.variables = append(p.variables, variable)
- return nil
-}
-
-const upperhex = "0123456789ABCDEF"
-
-func ishex(char byte) bool {
- switch {
- case '0' <= char && char <= '9':
- return true
- case 'a' <= char && char <= 'f':
- return true
- case 'A' <= char && char <= 'F':
- return true
- }
- return false
-}
-
-func unhex(char byte) byte {
- switch {
- case '0' <= char && char <= '9':
- return char - '0'
- case 'a' <= char && char <= 'f':
- return char - 'a' + 10
- case 'A' <= char && char <= 'F':
- return char - 'A' + 10
- }
- return 0
-}
-
-// pathEncoding is the encoding used for HttpPath variables.
-// Single encoding is used for single segment capture variables,
-// while multi encoding is used for multi segment capture variables.
-// On multi encoding variables, '/' is not escaped and is preserved
-// as '%2F' if encoded in the HttpPath.
-//
-// See: https://github.com/googleapis/googleapis/blob/1769846666fbeb0f9ece6ad929ddc0d563cccd8d/google/api/http.proto#L249-L264
-type pathEncoding int
-
-const (
- pathEncodeSingle pathEncoding = iota
- pathEncodeMulti
-)
-
-func pathShouldEscape(char byte, _ pathEncoding) bool {
- return !isVariable(rune(char))
-}
-
-func pathIsHexSlash(input string) bool {
- if len(input) < 3 {
- return false
- }
- return input[0] == '%' && input[1] == '2' && (input[2] == 'f' || input[2] == 'F')
-}
-
-func pathEscape(input string, mode pathEncoding) string {
- // Count the number of characters that possibly escaping.
- hexCount := 0
- for i := 0; i < len(input); i++ {
- if pathShouldEscape(input[i], mode) {
- hexCount++
- }
- }
- if hexCount == 0 {
- return input
- }
-
- var sb strings.Builder
- sb.Grow(len(input) + 2*hexCount)
- for i := 0; i < len(input); i++ {
- switch char := input[i]; {
- case char == '%' && mode == pathEncodeMulti && pathIsHexSlash(input[i:]):
- sb.WriteString("%2F")
- i += 2
- case pathShouldEscape(char, mode):
- sb.WriteByte('%')
- sb.WriteByte(upperhex[char>>4])
- sb.WriteByte(upperhex[char&15])
- default:
- sb.WriteByte(char)
- }
- }
- return sb.String()
-}
-
-func validateHex(input string) error {
- if len(input) < 3 || input[0] != '%' || !ishex(input[1]) || !ishex(input[2]) {
- if len(input) > 3 {
- input = input[:3]
- }
- return errors.WrapCaller(url.EscapeError(input))
- }
- return nil
-}
-
-func pathUnescape(input string, mode pathEncoding) (string, error) {
- // Count %, check that they're well-formed.
- percentCount := 0
- for i := 0; i < len(input); {
- switch input[i] {
- case '%':
- percentCount++
- if err := validateHex(input[i:]); err != nil {
- return "", errors.WrapCaller(err)
- }
- i += 3
- default:
- i++
- }
- }
- if percentCount == 0 {
- return input, nil
- }
-
- var sb strings.Builder
- sb.Grow(len(input) - 2*percentCount)
- for i := 0; i < len(input); i++ {
- switch input[i] {
- case '%':
- if mode == pathEncodeMulti && pathIsHexSlash(input[i:]) {
- // Multi doesn't escape /, so we don't escape.
- sb.WriteString("%2F")
- } else {
- sb.WriteByte(unhex(input[i+1])<<4 | unhex(input[i+2]))
- }
- i += 2
- default:
- sb.WriteByte(input[i])
- }
- }
- return sb.String(), nil
-}
diff --git a/pkg/gateway/internal/routex/path_parser_test.go b/pkg/gateway/internal/routex/path_parser_test.go
deleted file mode 100644
index d174c03a7..000000000
--- a/pkg/gateway/internal/routex/path_parser_test.go
+++ /dev/null
@@ -1,313 +0,0 @@
-// Copyright 2023-2024 Buf Technologies, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package routex
-
-import (
- "net/url"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-func TestPath_ParsePathTemplate(t *testing.T) {
- t.Parallel()
-
- testCases := []struct {
- tmpl string
- wantPath []string
- wantVerb string
- wantVars []pathVariable
- expectedErr string
- }{{
- // no error, but lots of encoding for special/reserved characters
- tmpl: "/my%2fcool-blog~about%2Cstuff%5Bwat%5D/{var={abc=%2F%2A}/{def=%2F%2A/**}}:baz",
- wantPath: []string{"my%2Fcool-blog~about%2Cstuff%5Bwat%5D", "%2F%2A", "%2F%2A", "**"},
- wantVerb: "baz",
- wantVars: []pathVariable{
- {FieldPath: "var", start: 1, end: -1},
- {FieldPath: "abc", start: 1, end: 2},
- {FieldPath: "def", start: 2, end: -1},
- },
- }, {
- tmpl: "/{1}",
- expectedErr: "syntax error at column 3: expected identifier",
- }, {
- tmpl: "/{field.1}",
- expectedErr: "syntax error at column 9: expected identifier",
- }, {
- tmpl: "/{_}",
- wantPath: []string{"*"},
- wantVars: []pathVariable{
- {FieldPath: "_", start: 0, end: 1},
- },
- }, {
- tmpl: "/{-}",
- expectedErr: "syntax error at column 3: expected identifier",
- }, {
- tmpl: "/{field-}",
- expectedErr: "syntax error at column 8: expected '}', got '-'",
- }, {
- tmpl: "/foo/bar/baz?abc=def",
- expectedErr: "syntax error at column 13: unexpected '?'", // no query string allowed
- }, {
- tmpl: "/foo/bar/baz buzz",
- expectedErr: "syntax error at column 13: unexpected ' '", // no whitespace allowed
- }, {
- tmpl: "foo/bar/baz",
- expectedErr: "syntax error at column 1: expected '/', got 'f'", // must start with slash
- }, {
- tmpl: "/foo/bar/",
- expectedErr: "syntax error at column 9: expected HttpPath Value", // must not end in slash
- }, {
- tmpl: "/foo/bar:baz/buzz",
- expectedErr: "syntax error at column 13: unexpected '/'", // ":baz" Verb can only come at the very end
- }, {
- tmpl: "/foo/{bar/baz}/buzz",
- expectedErr: "syntax error at column 10: expected '}', got '/'", // invalid field HttpPath
- }, {
- tmpl: "/foo/bar:baz%12xyz%abcde",
- wantPath: []string{"foo", "bar"},
- wantVerb: "baz%12xyz%ABcde",
- }, {
- tmpl: "/{hello}/world",
- wantPath: []string{"*", "world"},
- wantVars: []pathVariable{
- {FieldPath: "hello", start: 0, end: 1},
- },
- }, {
- tmpl: "/foo/bar%55:baz%1",
- expectedErr: "syntax error at column 17: invalid URL escape \"%1\"",
- }, {
- tmpl: "/foo/bar*",
- expectedErr: "syntax error at column 9: unexpected '*'", // wildcard must be entire HttpPath component
- }, {
- tmpl: "/foo/bar/***",
- expectedErr: "syntax error at column 12: unexpected '*'", // no such thing as triple-wildcard
- }, {
- tmpl: "/foo/**/bar",
- expectedErr: "double wildcard '**' must be the final HttpPath segment", // double-wildcard must be at end
- }, {
- tmpl: "/{a}/{a}", // TODO: allow this?
- expectedErr: "duplicate variable \"a\"",
- }, {
- tmpl: "/f/bar",
- wantPath: []string{"f", "bar"},
- }, {
- tmpl: "/v1/{Name=shelves/*/books/*}",
- wantPath: []string{"v1", "shelves", "*", "books", "*"},
- wantVars: []pathVariable{
- {FieldPath: "Name", start: 1, end: 5},
- },
- }, {
- tmpl: "/v1/{parent=shelves/*}/books",
- wantPath: []string{"v1", "shelves", "*", "books"},
- wantVars: []pathVariable{
- {FieldPath: "parent", start: 1, end: 3},
- },
- }, {
- tmpl: "/v1/{book.Name=shelves/*/books/*}",
- wantPath: []string{"v1", "shelves", "*", "books", "*"},
- wantVars: []pathVariable{
- {FieldPath: "book.Name", start: 1, end: 5},
- },
- }, {
- tmpl: "/v1:watch",
- wantPath: []string{"v1"},
- wantVerb: "watch",
- }, {
- tmpl: "/v3/events:clear",
- wantPath: []string{"v3", "events"},
- wantVerb: "clear",
- }, {
- tmpl: "/v3/{Name=events/*}:cancel",
- wantPath: []string{"v3", "events", "*"},
- wantVerb: "cancel",
- wantVars: []pathVariable{
- {FieldPath: "Name", start: 1, end: 3},
- },
- }, {
- tmpl: "/foo/bar/baz/buzz",
- wantPath: []string{"foo", "bar", "baz", "buzz"},
- }, {
- tmpl: "/foo/bar/{Name}",
- wantPath: []string{"foo", "bar", "*"},
- wantVars: []pathVariable{
- {FieldPath: "Name", start: 2, end: 3},
- },
- }, {
- tmpl: "/foo/bar/{Name}/baz/{child}",
- wantPath: []string{"foo", "bar", "*", "baz", "*"},
- wantVars: []pathVariable{
- {FieldPath: "Name", start: 2, end: 3},
- {FieldPath: "child", start: 4, end: 5},
- },
- }, {
- tmpl: "/foo/bar/{Name}/baz/{child.id}/buzz/{child.thing.id}",
- wantPath: []string{"foo", "bar", "*", "baz", "*", "buzz", "*"},
- wantVars: []pathVariable{
- {FieldPath: "Name", start: 2, end: 3},
- {FieldPath: "child.id", start: 4, end: 5},
- {FieldPath: "child.thing.id", start: 6, end: 7},
- },
- }, {
- tmpl: "/foo/bar/*/{thing.id}/{cat=**}",
- wantPath: []string{"foo", "bar", "*", "*", "**"},
- wantVars: []pathVariable{
- {FieldPath: "thing.id", start: 3, end: 4},
- {FieldPath: "cat", start: 4, end: -1},
- },
- }, {
- tmpl: "/foo/bar/*/{thing.id}/{cat=**}:do",
- wantPath: []string{"foo", "bar", "*", "*", "**"},
- wantVerb: "do",
- wantVars: []pathVariable{
- {FieldPath: "thing.id", start: 3, end: 4},
- {FieldPath: "cat", start: 4, end: -1},
- },
- }, {
- tmpl: "/foo/bar/*/{thing.id}/{cat=**}:cancel",
- wantPath: []string{"foo", "bar", "*", "*", "**"},
- wantVerb: "cancel",
- wantVars: []pathVariable{
- {FieldPath: "thing.id", start: 3, end: 4},
- {FieldPath: "cat", start: 4, end: -1},
- },
- }, {
- tmpl: "/foo/bob/{book_id={author}/{isbn}/*}/details",
- wantPath: []string{"foo", "bob", "*", "*", "*", "details"},
- wantVars: []pathVariable{
- {FieldPath: "book_id", start: 2, end: 5},
- {FieldPath: "author", start: 2, end: 3},
- {FieldPath: "isbn", start: 3, end: 4},
- },
- }, {
- tmpl: "/foo/blah/{longest_var={long_var.a={medium.a={short.aa}/*/{short.ab}/foo}/*}/{long_var.b={medium.b={short.ba}/*/{short.bb}/foo}/{last=**}}}:details",
- wantPath: []string{
- "foo", "blah",
- "*", // 2 logest_var, long_var.a, medium.a, short.aa
- "*", // 3
- "*", // 4 short.ab
- "foo", // 5
- "*", // 6
- "*", // 7 long_var.b, medium.b, short.ba
- "*", // 8
- "*", // 9 short.bb
- "foo", // 10
- "**", // 11 last
- },
- wantVerb: "details",
- wantVars: []pathVariable{
- {FieldPath: "longest_var", start: 2, end: -1},
- {FieldPath: "long_var.a", start: 2, end: 7},
- {FieldPath: "medium.a", start: 2, end: 6},
- {FieldPath: "short.aa", start: 2, end: 3},
- {FieldPath: "short.ab", start: 4, end: 5},
- {FieldPath: "long_var.b", start: 7, end: -1},
- {FieldPath: "medium.b", start: 7, end: 11},
- {FieldPath: "short.ba", start: 7, end: 8},
- {FieldPath: "short.bb", start: 9, end: 10},
- {FieldPath: "last", start: 11, end: -1},
- },
- }, {
- tmpl: "/foo%2Fbar/%2A/%2A%2a/{starstar=%2A%2a/**}:%2c",
- wantPath: []string{"foo%2Fbar", "%2A", "%2A%2A", "%2A%2A", "**"},
- wantVerb: "%2C",
- wantVars: []pathVariable{
- {FieldPath: "starstar", start: 3, end: -1},
- },
- }}
- for _, testCase := range testCases {
- testCase := testCase
- t.Run(testCase.tmpl, func(t *testing.T) {
- t.Parallel()
- segments, variables, err := parsePathTemplate(testCase.tmpl)
- if testCase.expectedErr != "" {
- assert.ErrorContains(t, err, testCase.expectedErr)
- return
- }
- t.Log(segments)
- require.NoError(t, err)
- assert.ElementsMatch(t, testCase.wantPath, segments.path, "HttpPath mismatch")
- assert.Equal(t, testCase.wantVerb, segments.verb, "Verb mismatch")
- assert.ElementsMatch(t, testCase.wantVars, variables, "variables mismatch")
- })
- }
-}
-
-func TestPath_SafeLiterals(t *testing.T) {
- t.Parallel()
- literalvalues := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._%25~"
- for _, r := range literalvalues {
- if !isLiteral(r) {
- t.Errorf("isLiteral(%q) = false, want true", r)
- }
- }
- unescaped, err := url.PathUnescape(literalvalues)
- require.NoError(t, err)
- escaped := url.PathEscape(unescaped)
- assert.Equal(t, literalvalues, escaped)
-}
-
-func TestPath_Escaping(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- input string
- mode pathEncoding
- want string
- wantEscaped string
- wantErr string
- }{{
- input: "foo",
- mode: pathEncodeSingle,
- want: "foo",
- }, {
- input: "foo%2Fbar",
- mode: pathEncodeSingle,
- want: "foo/bar",
- }, {
- input: "foo%252Fbar",
- mode: pathEncodeSingle,
- want: "foo%2Fbar",
- }, {
- input: "foo%2Fbar",
- mode: pathEncodeMulti,
- want: "foo%2Fbar",
- }, {
- input: "foo%2fbar",
- mode: pathEncodeMulti,
- want: "foo%2Fbar",
- wantEscaped: "foo%2Fbar",
- }}
- for _, testCase := range testCases {
- testCase := testCase
- t.Run(testCase.input, func(t *testing.T) {
- t.Parallel()
- dec, err := pathUnescape(testCase.input, testCase.mode)
- if err != nil {
- assert.EqualError(t, err, testCase.wantErr)
- return
- }
- require.NoError(t, err)
- assert.Equal(t, testCase.want, dec)
- enc := pathEscape(dec, testCase.mode)
- if testCase.wantEscaped != "" {
- assert.Equal(t, testCase.wantEscaped, enc)
- } else {
- assert.Equal(t, testCase.input, enc)
- }
- })
- }
-}
diff --git a/pkg/gateway/internal/routex/path_scanner.go b/pkg/gateway/internal/routex/path_scanner.go
deleted file mode 100644
index 6cd02f5eb..000000000
--- a/pkg/gateway/internal/routex/path_scanner.go
+++ /dev/null
@@ -1,110 +0,0 @@
-// Copyright 2023-2024 Buf Technologies, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package routex
-
-import (
- "unicode/utf8"
-)
-
-// pathScanner holds the state of the scanner.
-type pathScanner struct {
- input string // the string being scanned.
- start int // start position of this token.
- pos int // current position in the input.
- width int // width of last rune read from input.
-}
-
-const eof = -1
-
-func (s *pathScanner) next() rune {
- var char rune
- if s.pos >= len(s.input) {
- s.width = 0
- return eof
- }
- char, s.width = utf8.DecodeRuneInString(s.input[s.pos:])
- s.pos += s.width
- return char
-}
-
-func (s *pathScanner) current() rune {
- var char rune
- if s.width > 0 {
- char, _ = utf8.DecodeRuneInString(s.input[s.pos-s.width:])
- } else if s.pos > len(s.input) {
- char = eof
- }
- return char
-}
-
-func (s *pathScanner) backup() {
- s.pos -= s.width
- s.width = 0
-}
-
-func (s *pathScanner) captureRun(isValid func(r rune) bool) string {
- for isValid(s.next()) {
- continue
- }
- s.backup()
- return s.capture()
-}
-
-func (s *pathScanner) consume(expected rune) bool {
- if s.next() == expected {
- s.discard()
- return true
- }
- return false
-}
-
-func (s *pathScanner) discard() {
- s.start = s.pos
-}
-
-func (s *pathScanner) capture() string {
- value := s.input[s.start:s.pos]
- s.discard()
- return value
-}
-
-func isIdentStart(char rune) bool {
- return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || char == '_'
-}
-
-func isDigit(char rune) bool {
- return char >= '0' && char <= '9'
-}
-
-func isIdent(char rune) bool {
- return isIdentStart(char) || isDigit(char)
-}
-
-func isFieldPath(char rune) bool {
- return isIdent(char) || char == '.'
-}
-
-func isLiteral(char rune) bool {
- // Allow [-_.~0-9a-zA-Z] and % for escaped characters.
- return isVariable(char) || char == '%'
-}
-
-// isVariable is used to determine if a character is allowed in a single variable segment.
-//
-// See: https://github.com/googleapis/googleapis/blob/master/google/api/http.proto#L251C34-L251C38
-func isVariable(char rune) bool {
- // Allow [-_.~0-9a-zA-Z].
- return isFieldPath(char) || char == '-' || char == '~'
-}
diff --git a/pkg/gateway/internal/routex/router.go b/pkg/gateway/internal/routex/router.go
deleted file mode 100644
index 39c26685f..000000000
--- a/pkg/gateway/internal/routex/router.go
+++ /dev/null
@@ -1,464 +0,0 @@
-// Copyright 2023-2024 Buf Technologies, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package routex
-
-import (
- "fmt"
- "net/http"
- "strings"
-
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
-
- "google.golang.org/genproto/googleapis/api/annotations"
- "google.golang.org/protobuf/proto"
- "google.golang.org/protobuf/reflect/protoreflect"
-)
-
-func NewRouteTrie() *RouteTrie {
- return new(RouteTrie)
-}
-
-// RouteTrie is a prefix trie of valid REST URI paths to route targets.
-// It supports evaluation of variables as the HttpPath is matched, for
-// interpolating parts of the URI HttpPath into an RPC request field. The
-// map is keyed by the HttpPath component that corresponds to a given node.
-type RouteTrie struct {
- // Child nodes, keyed by the next segment in the HttpPath.
- children map[string]*RouteTrie
- // Final node in the HttpPath has a map of verbs to methods.
- // Verbs are either an empty string or a single literal.
- verbs map[string]RouteMethods
-}
-
-func (t *RouteTrie) GetRouteMethods() []*RouteTarget {
- var rr []*RouteTarget
- for _, verbs := range t.verbs {
- for _, target := range verbs {
- rr = append(rr, target)
- }
- }
-
- for _, child := range t.children {
- rr = append(rr, child.GetRouteMethods()...)
- }
-
- return rr
-}
-
-func (t *RouteTrie) Match(uriPath, httpMethod string) (*RouteTarget, []RouteTargetVarMatch, RouteMethods) {
- return t.match(uriPath, httpMethod)
-}
-
-func (t *RouteTrie) AddRoute(grpcMethod string, mth protoreflect.MethodDescriptor) error {
- httpRule := getExtensionHTTP(mth)
- return t.addRoute(&MethodConfig{Descriptor: mth, MethodPath: grpcMethod}, httpRule)
-}
-
-// addRoute adds a target to the router for the given HttpMethod and the given
-// HTTP rule. Only the rule itself is added. If the rule indicates additional
-// bindings, they are ignored. To add routes for all bindings, callers must
-// invoke this HttpMethod for each rule.
-func (t *RouteTrie) addRoute(config *MethodConfig, httpRule *annotations.HttpRule) error {
- if httpRule == nil {
- return nil
- }
-
- var method, template string
- switch pattern := httpRule.GetPattern().(type) {
- case *annotations.HttpRule_Get:
- method, template = http.MethodGet, pattern.Get
- case *annotations.HttpRule_Put:
- method, template = http.MethodPut, pattern.Put
- case *annotations.HttpRule_Post:
- method, template = http.MethodPost, pattern.Post
- case *annotations.HttpRule_Delete:
- method, template = http.MethodDelete, pattern.Delete
- case *annotations.HttpRule_Patch:
- method, template = http.MethodPatch, pattern.Patch
- case *annotations.HttpRule_Custom:
- method, template = pattern.Custom.GetKind(), pattern.Custom.GetPath()
- default:
- return fmt.Errorf("invalid type of pattern for HTTP httpRule: %T", pattern)
- }
-
- if method == "" {
- return errors.New("invalid HTTP httpRule: HttpMethod is blank")
- }
- if template == "" {
- return errors.New("invalid HTTP httpRule: HttpPath template is blank")
- }
- segments, variables, err := parsePathTemplate(template)
- if err != nil {
- return err
- }
-
- target, err := makeTarget(config, method, httpRule.GetBody(), httpRule.GetResponseBody(), segments, variables)
- if err != nil {
- return err
- }
- if err := t.insert(method, target, segments); err != nil {
- return err
- }
-
- for i, rule := range httpRule.GetAdditionalBindings() {
- if len(rule.GetAdditionalBindings()) > 0 {
- return fmt.Errorf("nested additional bindings are not supported (HttpMethod %s)", config.MethodPath)
- }
- if err = t.addRoute(config, rule); err != nil {
- return fmt.Errorf("failed to add REST route (add'l binding #%d) for HttpMethod %s: %w", i+1, config.MethodPath, err)
- }
- }
-
- return nil
-}
-
-func (t *RouteTrie) insertChild(segment string) *RouteTrie {
- child := t.children[segment]
- if child == nil {
- if t.children == nil {
- t.children = make(map[string]*RouteTrie, 1)
- }
- child = &RouteTrie{}
- t.children[segment] = child
- }
- return child
-}
-
-func (t *RouteTrie) insertVerb(verb string) RouteMethods {
- methods := t.verbs[verb]
- if methods == nil {
- if t.verbs == nil {
- t.verbs = make(map[string]RouteMethods, 1)
- }
- methods = make(RouteMethods, 1)
- t.verbs[verb] = methods
- }
- return methods
-}
-
-// insert the target into the trie using the given HttpMethod and segment HttpPath.
-// The HttpPath is followed until the final segment is reached.
-func (t *RouteTrie) insert(method string, target *RouteTarget, segments pathSegments) error {
- cursor := t
- for _, segment := range segments.path {
- cursor = cursor.insertChild(segment)
- }
- if existing := cursor.verbs[segments.verb][method]; existing != nil {
- return alreadyExistsError{
- existing: existing,
- pathPattern: segments.String(),
- method: method,
- }
- }
- cursor.insertVerb(segments.verb)[method] = target
- return nil
-}
-
-// match finds a route for the given request. If a match is found, the associated target and a map
-// of matched variable values is returned.
-func (t *RouteTrie) match(uriPath, httpMethod string) (*RouteTarget, []RouteTargetVarMatch, RouteMethods) {
- if len(uriPath) == 0 || uriPath[0] != '/' || uriPath[len(uriPath)-1] == ':' {
- // Must start with "/" or if it ends with ":" it won't match
- return nil, nil, nil
- }
- uriPath = uriPath[1:] // skip the leading slash
-
- path := strings.Split(uriPath, "/")
- var verb string
- if len(path) > 0 {
- lastElement := path[len(path)-1]
- if pos := strings.IndexRune(lastElement, ':'); pos >= 0 {
- path[len(path)-1] = lastElement[:pos]
- verb = lastElement[pos+1:]
- }
- }
- target, methods := t.findTarget(path, verb, httpMethod)
- if target == nil {
- return nil, nil, methods
- }
-
- vars, err := computeVarValues(path, target)
- if err != nil {
- log.Err(err).Msg("failed to compute var values")
- return nil, nil, nil
- }
-
- return target, vars, nil
-}
-
-// findTarget finds the target for the given HttpPath components, Verb, and HttpMethod.
-// The HttpMethod either returns a target OR the set of methods for the given HttpPath
-// and Verb. If the target is non-nil, the request was matched. If the target
-// is nil but methods are non-nil, the HttpPath and Verb matched a route, but not
-// the HttpMethod. This can be used to send back a well-formed "Allow" response
-// header. If both are nil, the HttpPath and Verb did not match.
-func (t *RouteTrie) findTarget(path []string, verb, method string) (*RouteTarget, RouteMethods) {
- if len(path) == 0 {
- return t.getTarget(verb, method)
- }
- current := path[0]
- path = path[1:]
-
- if child := t.children[current]; child != nil {
- target, methods := child.findTarget(path, verb, method)
- if target != nil || methods != nil {
- return target, methods
- }
- }
-
- if childAst := t.children["*"]; childAst != nil {
- target, methods := childAst.findTarget(path, verb, method)
- if target != nil || methods != nil {
- return target, methods
- }
- }
-
- // Double-asterisk must be the last element in pattern.
- // So it consumes all remaining HttpPath elements.
- if childDblAst := t.children["**"]; childDblAst != nil {
- return childDblAst.findTarget(nil, verb, method)
- }
- return nil, nil
-}
-
-// getTarget gets the target for the given Verb and HttpMethod from the
-// node trie. It is like findTarget, except that it does not use a
-// HttpPath to first descend into a sub-trie.
-func (t *RouteTrie) getTarget(verb, method string) (*RouteTarget, RouteMethods) {
- methods := t.verbs[verb]
- if target := methods[method]; target != nil {
- return target, methods
- }
- // See if a wildcard HttpMethod was used
- if target := methods["*"]; target != nil {
- return target, methods
- }
- return nil, methods
-}
-
-type RouteMethods map[string]*RouteTarget
-
-type RouteTarget struct {
- GrpcMethodName string
- HttpMethod string
- HttpPath []string
- Verb string
-
- RequestBodyFieldPath string
- RequestBodyFields []protoreflect.FieldDescriptor
- ResponseBodyFieldPath string
- ResponseBodyFields []protoreflect.FieldDescriptor
- Vars []RouteTargetVar
-}
-
-func makeTarget(config *MethodConfig, method, requestBody, responseBody string, segments pathSegments, variables []pathVariable) (*RouteTarget, error) {
- requestBodyFields, err := resolvePathToDescriptors(config.Descriptor.Input(), requestBody)
- if err != nil {
- return nil, err
- }
- if len(requestBodyFields) > 1 {
- return nil, fmt.Errorf(
- "unexpected request body HttpPath %q: must be a single field",
- requestBody,
- )
- }
-
- responseBodyFields, err := resolvePathToDescriptors(config.Descriptor.Output(), responseBody)
- if err != nil {
- return nil, err
- }
- if len(responseBodyFields) > 1 {
- return nil, fmt.Errorf(
- "unexpected response body HttpPath %q: must be a single field",
- requestBody,
- )
- }
-
- routeTargetVars := make([]RouteTargetVar, len(variables))
- for i, variable := range variables {
- fields, err := resolvePathToDescriptors(config.Descriptor.Input(), variable.FieldPath)
- if err != nil {
- return nil, err
- }
- if last := fields[len(fields)-1]; last.IsList() {
- return nil, fmt.Errorf(
- "unexpected HttpPath variable %q: cannot be a repeated field",
- variable.FieldPath,
- )
- }
-
- routeTargetVars[i] = RouteTargetVar{
- pathVariable: variable,
- fields: fields,
- }
- }
- return &RouteTarget{
- GrpcMethodName: config.MethodPath,
- HttpMethod: method,
- HttpPath: segments.path,
- Verb: segments.verb,
- RequestBodyFieldPath: requestBody,
- RequestBodyFields: requestBodyFields,
- ResponseBodyFieldPath: responseBody,
- ResponseBodyFields: responseBodyFields,
- Vars: routeTargetVars,
- }, nil
-}
-
-type RouteTargetVar struct {
- pathVariable
-
- fields []protoreflect.FieldDescriptor
-}
-
-func (v RouteTargetVar) size() int {
- if v.end == -1 {
- return -1
- }
- return v.end - v.start
-}
-
-func (v RouteTargetVar) index(segments []string) []string {
- start, end := v.start, v.end
- if end == -1 {
- if start >= len(segments) {
- return nil
- }
- return segments[start:]
- }
- return segments[start:end]
-}
-
-func (v RouteTargetVar) capture(segments []string) (string, error) {
- parts := v.index(segments)
- mode := pathEncodeSingle
- if v.end == -1 || v.start-v.end > 1 {
- mode = pathEncodeMulti
- }
-
- var sb strings.Builder
- for i, part := range parts {
- val, err := pathUnescape(part, mode)
- if err != nil {
- return "", err
- }
- if i > 0 {
- sb.WriteByte('/')
- }
- sb.WriteString(val)
- }
- return sb.String(), nil
-}
-
-type RouteTargetVarMatch struct {
- Fields []protoreflect.FieldDescriptor
- Value string
- Name string
-}
-
-func computeVarValues(path []string, target *RouteTarget) ([]RouteTargetVarMatch, error) {
- if len(target.Vars) == 0 {
- return nil, nil
- }
- vars := make([]RouteTargetVarMatch, len(target.Vars))
- for i, varDef := range target.Vars {
- val, err := varDef.capture(path)
- if err != nil {
- return nil, err
- }
- vars[i].Fields = varDef.fields
- vars[i].Value = val
- vars[i].Name = varDef.FieldPath
- }
- return vars, nil
-}
-
-// resolvePathToDescriptors translates the given HttpPath string, in the form of "ident.ident.ident",
-// into a HttpPath of FieldDescriptors, relative to the given msg.
-func resolvePathToDescriptors(msg protoreflect.MessageDescriptor, path string) ([]protoreflect.FieldDescriptor, error) {
- if path == "" {
- return nil, nil
- }
- if path == "*" {
- // non-nil, empty slice means use the whole thing
- return []protoreflect.FieldDescriptor{}, nil
- }
-
- fields := msg.Fields()
- parts := strings.Split(path, ".")
- result := make([]protoreflect.FieldDescriptor, len(parts))
- for i, part := range parts {
- field := fields.ByName(protoreflect.Name(part))
- if field == nil {
- return nil, fmt.Errorf("in field HttpPath %q: element %q does not correspond to any field of type %s",
- path, part, msg.FullName())
- }
- result[i] = field
- if i == len(parts)-1 {
- break
- }
- if field.Cardinality() == protoreflect.Repeated {
- return nil, fmt.Errorf("in field HttpPath %q: field %q of type %s should not be a list or map",
- path, part, msg.FullName())
- }
- msg = field.Message()
- if msg == nil {
- return nil, fmt.Errorf("in field HttpPath %q: field %q of type %s should be a message but is instead %s",
- path, part, msg.FullName(), field.Kind())
- }
- fields = msg.Fields()
- }
- return result, nil
-}
-
-// resolveFieldDescriptorsToPath translates the given HttpPath of FieldDescriptors into a string
-// of the form "ident.ident.ident".
-func resolveFieldDescriptorsToPath(fields []protoreflect.FieldDescriptor) string {
- if len(fields) == 0 {
- return ""
- }
- parts := make([]string, len(fields))
- for i, field := range fields {
- parts[i] = string(field.Name())
- }
- return strings.Join(parts, ".")
-}
-
-type alreadyExistsError struct {
- existing *RouteTarget
- pathPattern, method string
-}
-
-func (a alreadyExistsError) Error() string {
- return fmt.Sprintf("target for %s, HttpMethod %s already exists: %s", a.pathPattern, a.method, a.existing.GrpcMethodName)
-}
-
-// getExtensionHTTP
-func getExtensionHTTP(m protoreflect.MethodDescriptor) *annotations.HttpRule {
- if m == nil {
- return nil
- }
-
- if m.Options() == nil {
- return nil
- }
-
- ext, ok := proto.GetExtension(m.Options(), annotations.E_Http).(*annotations.HttpRule)
- if ok {
- return ext
- }
- return nil
-}
diff --git a/pkg/gateway/internal/routex/router_test.go b/pkg/gateway/internal/routex/router_test.go
deleted file mode 100644
index 41aac5c71..000000000
--- a/pkg/gateway/internal/routex/router_test.go
+++ /dev/null
@@ -1,332 +0,0 @@
-// Copyright 2023-2024 Buf Technologies, Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package routex
-
-import (
- "fmt"
- "net/http"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "google.golang.org/protobuf/reflect/protoreflect"
-)
-
-func TestRouteTrie_Insert(t *testing.T) {
- t.Parallel()
- _ = initTrie(t)
-}
-
-func TestRouteTrie_FindTarget(t *testing.T) {
- t.Parallel()
- testCases := []struct {
- path string
- expectedPath string // if blank, HttpPath is expected to NOT match
- expectedVars map[string]string
- }{
- {
- path: "/bob/lob/law",
- },
- {
- path: "/foo/bar/baz",
- expectedPath: "/foo/bar/{Name}",
- expectedVars: map[string]string{"Name": "baz"},
- },
- {
- path: "/foo/bob/lob/law",
- },
- {
- path: "/foo/bar/baz/buzz",
- expectedPath: "/foo/bar/baz/buzz",
- },
- {
- path: "/foo/bar/baz/baz/buzz",
- expectedPath: "/foo/bar/{Name}/baz/{child}",
- expectedVars: map[string]string{"Name": "baz", "child": "buzz"},
- },
- {
- path: "/foo/bar/1/baz/2/buzz/3",
- expectedPath: "/foo/bar/{Name}/baz/{child.id}/buzz/{child.thing.id}",
- expectedVars: map[string]string{"Name": "1", "child.id": "2", "child.thing.id": "3"},
- },
- {
- path: "/foo/bar/baz/123",
- },
- {
- path: "/foo/bar/baz/123/buzz",
- expectedPath: "/foo/bar/*/{thing.id}/{cat=**}",
- expectedVars: map[string]string{"thing.id": "123", "cat": "buzz"},
- },
- {
- path: "/foo/bar/baz/123/buzz/buzz",
- expectedPath: "/foo/bar/*/{thing.id}/{cat=**}",
- expectedVars: map[string]string{"thing.id": "123", "cat": "buzz/buzz"},
- },
- {
- path: "/foo/bar/baz/123/buzz/buzz:do",
- expectedPath: "/foo/bar/*/{thing.id}/{cat=**}:do",
- expectedVars: map[string]string{"thing.id": "123", "cat": "buzz/buzz"},
- },
- {
- path: "/foo/bar/baz/123/fizz/buzz/frob/nitz:do",
- expectedPath: "/foo/bar/*/{thing.id}/{cat=**}:do",
- expectedVars: map[string]string{"thing.id": "123", "cat": "fizz/buzz/frob/nitz"},
- },
- {
- path: "/foo/bar/baz/123/buzz/buzz:cancel",
- expectedPath: "/foo/bar/*/{thing.id}/{cat=**}:cancel",
- expectedVars: map[string]string{"thing.id": "123", "cat": "buzz/buzz"},
- },
- {
- path: "foo/bar/baz/123/buzz/buzz:blah",
- },
- {
- path: "/foo/bob/bar/baz/123/details",
- expectedPath: "/foo/bob/{book_id={author}/{isbn}/*}/details",
- expectedVars: map[string]string{"book_id": "bar/baz/123", "author": "bar", "isbn": "baz"},
- },
- {
- path: "/foo/bob/bar/baz/123/details:do",
- },
- {
- path: "/foo/blah/A/B/C/foo/D/E/F/G/foo/H/I/J/K/L/M:details",
- expectedPath: "/foo/blah/{longest_var={long_var.a={medium.a={short.aa}/*/{short.ab}/foo}/*}/{long_var.b={medium.b={short.ba}/*/{short.bb}/foo}/{last=**}}}:details",
- expectedVars: map[string]string{
- "longest_var": "A/B/C/foo/D/E/F/G/foo/H/I/J/K/L/M",
- "long_var.a": "A/B/C/foo/D",
- "medium.a": "A/B/C/foo",
- "short.aa": "A",
- "short.ab": "C",
- "long_var.b": "E/F/G/foo/H/I/J/K/L/M",
- "medium.b": "E/F/G/foo",
- "short.ba": "E",
- "short.bb": "G",
- "last": "H/I/J/K/L/M",
- },
- },
- {
- // No trailing slash in the HttpPath, so this should not match.
- path: "/trailing:slash",
- },
- {
- // Trailing slash in the HttpPath, so this should match.
- path: "/trailing/:slash",
- expectedPath: "/trailing/**:slash",
- },
- {
- // Trailing Verb, should not match.
- path: "/Verb:",
- },
- {
- // No trailing Verb, should match.
- path: "/Verb",
- expectedPath: "/Verb",
- },
- {
- // Var capture use HttpPath unescaping.
- path: "/foo/bar/baz/%2f/%2A/%2f",
- expectedPath: "/foo/bar/*/{thing.id}/{cat=**}",
- expectedVars: map[string]string{"thing.id": "/", "cat": "*/%2F"},
- },
- }
-
- trie := initTrie(t)
-
- for _, testCase := range testCases {
- testCase := testCase
- t.Run(testCase.path, func(t *testing.T) {
- t.Parallel()
- var present, absent []string
- if testCase.expectedPath != "" {
- present = []string{http.MethodGet, http.MethodPost}
- absent = []string{http.MethodDelete, http.MethodPut}
- } else {
- absent = []string{http.MethodGet, http.MethodPost, http.MethodDelete, http.MethodPut}
- }
- for _, method := range present {
- method := method
- t.Run(method, func(t *testing.T) {
- t.Parallel()
- target, vars, _ := trie.match(testCase.path, method)
- require.NotNil(t, target)
- require.Equal(t, len(testCase.expectedVars), len(vars))
- for _, varMatch := range vars {
- names := make([]string, len(varMatch.Fields))
- for i, fld := range varMatch.Fields {
- names[i] = string(fld.Name())
- }
- name := strings.Join(names, ".")
- expectedValue, ok := testCase.expectedVars[name]
- assert.True(t, ok, name)
- require.Equal(t, expectedValue, varMatch.Value, name)
- }
- })
- }
- for _, method := range absent {
- method := method
- t.Run(method, func(t *testing.T) {
- t.Parallel()
- target, _, _ := trie.match(testCase.path, method)
- require.Nil(t, target)
- })
- }
- })
- }
-}
-
-func BenchmarkTrieMatch(b *testing.B) {
- trie := initTrie(b)
- path := "/foo/blah/A/B/C/foo/D/E/F/G/foo/H/I/J/K/L/M:details"
- var (
- method *RouteTarget
- vars []RouteTargetVarMatch
- )
- b.ReportAllocs()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- method, vars, _ = trie.match(path, http.MethodPost)
- if method == nil {
- b.Fatal("HttpMethod not found")
- }
- }
- b.StopTimer()
- assert.NotNil(b, method)
- assert.Len(b, vars, 10)
-}
-
-func initTrie(tb testing.TB) *RouteTrie {
- tb.Helper()
- var trie RouteTrie
- for _, route := range []string{
- "/foo/bar/baz/buzz",
- "/foo/bar/{Name}",
- "/foo/bar/{Name}/baz/{child}",
- "/foo/bar/{Name}/baz/{child.id}/buzz/{child.thing.id}",
- "/foo/bar/*/{thing.id}/{cat=**}",
- "/foo/bar/*/{thing.id}/{cat=**}:do",
- "/foo/bar/*/{thing.id}/{cat=**}:cancel",
- "/foo/bob/{book_id={author}/{isbn}/*}/details",
- "/foo/blah/{longest_var={long_var.a={medium.a={short.aa}/*/{short.ab}/foo}/*}/{long_var.b={medium.b={short.ba}/*/{short.bb}/foo}/{last=**}}}:details",
- "/foo%2Fbar/%2A/%2A%2a/{starstar=%2A%2a/**}:%2c",
- "/trailing/**:slash",
- "/Verb",
- } {
- segments, variables, err := parsePathTemplate(route)
- require.NoError(tb, err)
-
- for _, method := range []string{http.MethodGet, http.MethodPost} {
- config := &MethodConfig{
- Descriptor: &fakeMethodDescriptor{
- name: fmt.Sprintf("%s %s", method, route),
- },
- }
- target, err := makeTarget(config, "POST", "*", "*", segments, variables)
- require.NoError(tb, err)
- err = trie.insert(method, target, segments)
- require.NoError(tb, err)
- }
- }
- return &trie
-}
-
-type fakeMethodDescriptor struct {
- protoreflect.MethodDescriptor
- name string
- in, out protoreflect.MessageDescriptor
-}
-
-func (f *fakeMethodDescriptor) Name() protoreflect.Name {
- return protoreflect.Name(f.name)
-}
-
-func (f *fakeMethodDescriptor) Input() protoreflect.MessageDescriptor {
- if f.in == nil {
- f.in = &fakeMessageDescriptor{}
- }
- return f.in
-}
-
-func (f *fakeMethodDescriptor) Output() protoreflect.MessageDescriptor {
- if f.out == nil {
- f.out = &fakeMessageDescriptor{}
- }
- return f.out
-}
-
-type fakeMessageDescriptor struct {
- protoreflect.MessageDescriptor
- fields protoreflect.FieldDescriptors
-}
-
-func (f *fakeMessageDescriptor) Fields() protoreflect.FieldDescriptors {
- if f.fields == nil {
- f.fields = &fakeFieldDescriptors{}
- }
- return f.fields
-}
-
-type fakeFieldDescriptors struct {
- protoreflect.FieldDescriptors
- fields map[protoreflect.Name]protoreflect.FieldDescriptor
-}
-
-func (f *fakeFieldDescriptors) ByName(name protoreflect.Name) protoreflect.FieldDescriptor {
- fld := f.fields[name]
- if fld == nil {
- if f.fields == nil {
- f.fields = map[protoreflect.Name]protoreflect.FieldDescriptor{}
- }
- fld = &fakeFieldDescriptor{name: name}
- f.fields[name] = fld
- }
- return fld
-}
-
-type fakeFieldDescriptor struct {
- name protoreflect.Name
- msg protoreflect.MessageDescriptor
- kind protoreflect.Kind
- protoreflect.FieldDescriptor
-}
-
-func (f *fakeFieldDescriptor) Name() protoreflect.Name {
- return f.name
-}
-
-func (f *fakeFieldDescriptor) Cardinality() protoreflect.Cardinality {
- return protoreflect.Optional
-}
-
-func (f *fakeFieldDescriptor) Kind() protoreflect.Kind {
- if f.kind > 0 {
- return f.kind
- }
- if f.msg != nil {
- return protoreflect.MessageKind
- }
- return protoreflect.StringKind
-}
-
-func (f *fakeFieldDescriptor) Message() protoreflect.MessageDescriptor {
- if f.msg == nil {
- f.msg = &fakeMessageDescriptor{}
- }
- return f.msg
-}
-
-func (f *fakeFieldDescriptor) IsList() bool {
- return false
-}
diff --git a/pkg/gateway/internal/routex/types.go b/pkg/gateway/internal/routex/types.go
deleted file mode 100644
index 76643ba9c..000000000
--- a/pkg/gateway/internal/routex/types.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package routex
-
-import (
- "google.golang.org/protobuf/reflect/protoreflect"
-)
-
-type MethodConfig struct {
- Descriptor protoreflect.MethodDescriptor
- MethodPath string
-}
diff --git a/pkg/gateway/mux.go b/pkg/gateway/mux.go
index 169cf53b7..deac6bd50 100644
--- a/pkg/gateway/mux.go
+++ b/pkg/gateway/mux.go
@@ -14,21 +14,25 @@ import (
"github.com/fullstorydev/grpchan/inprocgrpc"
"github.com/gofiber/adaptor/v2"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/result"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/gateway/internal/routertree"
- "github.com/pubgo/lava/pkg/httputil"
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/log/logfields"
+ "github.com/pubgo/funk/v2/result"
+ "github.com/rs/zerolog"
"github.com/samber/lo"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
+
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/gateway/internal"
+ "github.com/pubgo/lava/v2/pkg/gateway/routertree"
+ "github.com/pubgo/lava/v2/pkg/httputil"
)
type muxOptions struct {
@@ -79,7 +83,7 @@ var (
}
defaultCompressors = map[string]Compressor{
- "gzip": &CompressorGzip{},
+ "gzip": &internal.CompressorGzip{},
"identity": nil,
}
)
@@ -142,13 +146,13 @@ func (m *Mux) SetRequestDecoder(name protoreflect.FullName, f func(ctx *fiber.Ct
m.opts.requestInterceptors[name] = f
}
-func (m *Mux) MatchOperation(method string, path string) (r result.Result[*MatchOperation]) {
- restTarget, err := m.routerTree.Match(method, path)
- if err != nil {
- return r.WithErr(errors.Wrapf(err, "path not found, method=%s path=%s", method, path))
- }
-
- return r.WithVal(restTarget)
+func (m *Mux) MatchOperation(method, path string) (r result.Result[*MatchOperation]) {
+ return result.Wrap(m.routerTree.Match(method, path)).
+ Log(func(e *zerolog.Event) {
+ e.Str("method", method)
+ e.Str("path", path)
+ e.Str(logfields.Msg, "match operation failed")
+ })
}
func (m *Mux) GetOperationByName(name string) *GrpcMethod {
@@ -161,7 +165,7 @@ func (m *Mux) GetOperationByName(name string) *GrpcMethod {
}
func (m *Mux) GetOperation(operation string) *GrpcMethod {
- var opt = m.opts.handlers[operation]
+ opt := m.opts.handlers[operation]
if opt == nil {
return nil
}
@@ -186,7 +190,7 @@ func (m *Mux) Handler(ctx *fiber.Ctx) error {
mth := m.opts.handlers[matchOperation.Operation]
if mth == nil {
- return errors.Format("grpc method not found, method=%s", matchOperation.Operation)
+ return errors.Errorf("grpc method not found, method=%s", matchOperation.Operation)
}
md := metadata.MD{}
@@ -202,13 +206,13 @@ func (m *Mux) Handler(ctx *fiber.Ctx) error {
path: matchOperation,
}
- var in = mth.inputType.New().Interface()
+ in := mth.inputType.New().Interface()
err = stream.RecvMsg(in)
if err != nil {
return errors.WrapCaller(err)
}
- var out = mth.outputType.New().Interface()
+ out := mth.outputType.New().Interface()
var header metadata.MD
var trailer metadata.MD
err = m.Invoke(stream.ctx, mth.grpcFullMethod, in, out, grpc.Header(&header), grpc.Trailer(&trailer))
@@ -216,7 +220,7 @@ func (m *Mux) Handler(ctx *fiber.Ctx) error {
return errors.WrapCaller(err)
}
- var hh = make(metadata.MD)
+ hh := make(metadata.MD)
for k, v := range header {
hh.Set(k, v...)
}
@@ -310,7 +314,7 @@ func NewMux(opts ...MuxOption) *Mux {
mux := &Mux{
opts: &muxOpts,
localClient: new(inprocgrpc.Channel),
- routerTree: routertree.NewRouteTree(),
+ routerTree: routertree.New(),
}
return mux
@@ -334,8 +338,8 @@ func (m *Mux) RegisterProxy(sd *grpc.ServiceDesc, proxy lava.GrpcRouter, cli grp
}
// RegisterService satisfies grpc.ServiceRegistrar for generated service code hooks.
-func (m *Mux) RegisterService(sd *grpc.ServiceDesc, ss interface{}) {
- assert.If(generic.IsNil(ss), "ss params is nil")
+func (m *Mux) RegisterService(sd *grpc.ServiceDesc, ss any) {
+ assert.If(funk.IsNil(ss), "ss params is nil")
m.localClient.RegisterService(sd, ss)
@@ -368,7 +372,7 @@ func (m *Mux) registerRouter(rule *methodWrapper) {
)
}
-func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.ClientConnInterface) error {
+func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss any, cli grpc.ClientConnInterface) error {
d, err := m.opts.files.FindDescriptorByName(protoreflect.FullName(gsd.ServiceName))
if err != nil {
return errors.WrapCaller(err)
@@ -376,7 +380,7 @@ func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.Cl
sd, ok := d.(protoreflect.ServiceDescriptor)
if !ok {
- return errors.Format("invalid httpPathRule descriptor %T", d)
+ return errors.Errorf("invalid httpPathRule descriptor %T", d)
}
srv := &serviceWrapper{
@@ -408,7 +412,7 @@ func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.Cl
meta: getExtensionRpc(methodDesc),
})
- assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth string, path string, reqBody, rspBody string) error {
+ assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth, path, reqBody, rspBody string) error {
return errors.WrapCaller(m.routerTree.Add(mth, path, grpcMethod, resolveBodyDesc(methodDesc, reqBody, rspBody)))
}))
}
@@ -428,7 +432,7 @@ func (m *Mux) registerService(gsd *grpc.ServiceDesc, ss interface{}, cli grpc.Cl
meta: getExtensionRpc(methodDesc),
})
- assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth string, path string, reqBody, rspBody string) error {
+ assert.Exit(handlerHttpRoute(getExtensionHTTP(methodDesc), func(mth, path, reqBody, rspBody string) error {
return errors.WrapCaller(m.routerTree.Add(mth, path, grpcMethod, resolveBodyDesc(methodDesc, reqBody, rspBody)))
}))
}
diff --git a/pkg/gateway/routertree/lex.go b/pkg/gateway/routertree/lex.go
new file mode 100644
index 000000000..2dbf601ef
--- /dev/null
+++ b/pkg/gateway/routertree/lex.go
@@ -0,0 +1,44 @@
+package routertree
+
+import (
+ "github.com/alecthomas/participle/v2/lexer"
+)
+
+const (
+ doubleStar = "**"
+ star = "*"
+)
+
+// httpRule
+// Template = "/" Segments [ Verb ] ;
+// Segments = Segment { "/" Segment } ;
+// Segment = "*" | "**" | LITERAL | Variable ;
+// Variable = "{" FieldPath [ "=" Segments ] "}" ;
+// FieldPath = IDENT { "." IDENT } ;
+// Verb = ":" LITERAL ;
+type httpRule struct {
+ Pos lexer.Position
+ Slash string `parser:"@\"/\""`
+ Segments *segments `parser:"@@!"`
+ Verb *string `parser:"(\":\" @Ident)?"`
+}
+
+// nolint
+type segments struct {
+ Pos lexer.Position
+ Segments []*segment `parser:"@@ (\"/\" @@)*"`
+}
+
+// nolint
+type segment struct {
+ Pos lexer.Position
+ Path *string `parser:"@(\"*\" \"*\" | \"*\" | Ident)"`
+ Variable *variable `parser:"| @@*"`
+}
+
+// nolint
+type variable struct {
+ Pos lexer.Position
+ Fields []string `parser:"\"{\" @Ident (\".\" @Ident)*"`
+ Segments *segments `parser:"(\"=\" @@)? \"}\""`
+}
diff --git a/pkg/gateway/internal/routertree/parser.go b/pkg/gateway/routertree/parser.go
similarity index 54%
rename from pkg/gateway/internal/routertree/parser.go
rename to pkg/gateway/routertree/parser.go
index 5bf95dc02..e80813253 100644
--- a/pkg/gateway/internal/routertree/parser.go
+++ b/pkg/gateway/routertree/parser.go
@@ -1,4 +1,3 @@
-//nolint:all
package routertree
import (
@@ -6,54 +5,20 @@ import (
"github.com/alecthomas/participle/v2"
"github.com/alecthomas/participle/v2/lexer"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/generic"
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/samber/lo"
)
-const (
- doubleStar = "**"
- star = "*"
+var parser = participle.MustBuild[httpRule](
+ participle.Lexer(lexer.MustSimple([]lexer.SimpleRule{
+ {Name: "Ident", Pattern: `[a-zA-Z][\w\_\-\.]*`},
+ {Name: "Punct", Pattern: `[-[!@#$%^&*()+_={}\|:;"'<,>.?/]|]`},
+ })),
)
-var (
- parser = assert.Exit1(participle.Build[httpRule](
- participle.Lexer(assert.Exit1(lexer.NewSimple([]lexer.SimpleRule{
- {Name: "Ident", Pattern: `[a-zA-Z][\w\_\-\.]*`},
- {Name: "Punct", Pattern: `[-[!@#$%^&*()+_={}\|:;"'<,>.?/]|]`},
- }))),
- ))
-)
-
-// httpRule
-// Template = "/" Segments [ Verb ] ;
-// Segments = Segment { "/" Segment } ;
-// Segment = "*" | "**" | LITERAL | Variable ;
-// Variable = "{" FieldPath [ "=" Segments ] "}" ;
-// FieldPath = IDENT { "." IDENT } ;
-// Verb = ":" LITERAL ;
-type httpRule struct {
- Slash string `@"/"`
- Segments *segments `@@!`
- Verb *string `(":" @Ident)?`
-}
-
-type segments struct {
- Segments []*segment `@@ ("/" @@)*`
-}
-
-type segment struct {
- Path *string `@("*" "*" | "*" | Ident)`
- Variable *variable `| @@*`
-}
-
-type variable struct {
- Fields []string `"{" @Ident ("." @Ident)*`
- Segments *segments `("=" @@)? "}"`
-}
-
type pathVariable struct {
- Fields []string
+ fields []string
start, end int
}
@@ -74,7 +39,7 @@ func (r routePath) Match(urls []string, verb string) ([]PathFieldVar, error) {
}
if r.Verb != nil {
- if generic.FromPtr(r.Verb) != verb {
+ if lo.FromPtr(r.Verb) != verb {
return nil, errors.New("verb not match")
}
}
@@ -93,12 +58,12 @@ func (r routePath) Match(urls []string, verb string) ([]PathFieldVar, error) {
continue
}
- return nil, errors.New("path is not match")
+ return nil, errors.Errorf("path(%s) not match", path)
}
var vv []PathFieldVar
for _, v := range r.Vars {
- pathVar := PathFieldVar{Fields: v.Fields}
+ pathVar := PathFieldVar{Fields: v.fields}
if v.end > 0 {
pathVar.Value = strings.Join(urls[v.start:v.end+1], "/")
} else {
@@ -118,11 +83,11 @@ func (r routePath) String() string {
copy(paths, r.Paths)
for _, v := range r.Vars {
- varS := "{" + strings.Join(v.Fields, ".") + "="
- end := generic.Ternary(v.end == -1, len(paths)-1, v.end)
+ varS := "{" + strings.Join(v.fields, ".") + "="
+ end := funk.Ternary(v.end == -1, len(paths)-1, v.end)
for i := v.start; i <= end; i++ {
- varS += generic.Ternary(i == v.start, paths[i], "/"+paths[i])
+ varS += funk.Ternary(i == v.start, paths[i], "/"+paths[i])
if i > v.start {
paths[i] = ""
}
@@ -132,10 +97,10 @@ func (r routePath) String() string {
paths[v.start] = varS
}
- url += strings.Join(generic.Filter(paths, func(s string) bool { return s != "" }), "/")
+ url += strings.Join(funk.Filter(paths, func(s string) bool { return s != "" }), "/")
if r.Verb != nil {
- url += ":" + generic.FromPtr(r.Verb)
+ url += ":" + lo.FromPtr(r.Verb)
}
return url
@@ -147,7 +112,7 @@ func handleSegments(s *segment, rr *routePath) {
return
}
- vv := &pathVariable{Fields: s.Variable.Fields, start: len(rr.Paths)}
+ vv := &pathVariable{fields: s.Variable.Fields, start: len(rr.Paths)}
if s.Variable.Segments == nil {
rr.Paths = append(rr.Paths, star)
} else {
@@ -179,7 +144,10 @@ func parseToRoute(rule *httpRule) *routePath {
}
func parse(url string) (*httpRule, error) {
- return parser.ParseString("", url)
- //participle.AllowTrailing(true),
- //participle.Trace(os.Stdout),
+ return parser.ParseString(
+ "",
+ url,
+ // participle.AllowTrailing(true),
+ // participle.Trace(os.Stdout),
+ )
}
diff --git a/pkg/gateway/internal/routertree/parser_test.go b/pkg/gateway/routertree/parser_test.go
similarity index 93%
rename from pkg/gateway/internal/routertree/parser_test.go
rename to pkg/gateway/routertree/parser_test.go
index a371a0197..785330c22 100644
--- a/pkg/gateway/internal/routertree/parser_test.go
+++ b/pkg/gateway/routertree/parser_test.go
@@ -3,7 +3,7 @@ package routertree
import (
"testing"
- "github.com/pubgo/funk/assert"
+ "github.com/pubgo/funk/v2/assert"
)
func TestName(t *testing.T) {
diff --git a/pkg/gateway/routertree/router.go b/pkg/gateway/routertree/router.go
new file mode 100644
index 000000000..78163d5c0
--- /dev/null
+++ b/pkg/gateway/routertree/router.go
@@ -0,0 +1,212 @@
+package routertree
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/samber/lo"
+)
+
+var (
+ ErrPathNodeNotFound = errors.New("path node not found")
+ ErrOperationNotFound = errors.New("operation not found")
+)
+
+func New() *RouteTree {
+ return &RouteTree{nodeMap: make(map[string]*nodeTree)}
+}
+
+type RouteOperation struct {
+ Method string `json:"method,omitempty"`
+ Path string `json:"path,omitempty"`
+ Operation string `json:"operation,omitempty"`
+ Verb string `json:"verb,omitempty"`
+ Vars []string `json:"vars,omitempty"`
+ Extras map[string]any `json:"extras"`
+}
+
+type MatchOperation struct {
+ Method string `json:"method"`
+ Path string `json:"path"`
+ Operation string `json:"operation"`
+ Verb string `json:"verb"`
+ Vars []PathFieldVar `json:"vars"`
+ Extras map[string]any `json:"extras"`
+}
+
+type routeTarget struct {
+ Method string
+ Path string
+ Operation string
+ Verb *string
+ Vars []*pathVariable
+ extras map[string]any
+}
+
+type nodeTree struct {
+ nodeMap map[string]*nodeTree
+ verbMap map[string]*routeTarget
+}
+
+type RouteTree struct {
+ nodeMap map[string]*nodeTree
+}
+
+func (r *RouteTree) List() []RouteOperation {
+ return getOpt(r.nodeMap)
+}
+
+func (r *RouteTree) Add(method, path, operation string, extras map[string]any) error {
+ errMsg := func() string {
+ return fmt.Sprintf("method: %s, path: %s, operation: %s", method, path, operation)
+ }
+
+ rule, err := parse(path)
+ if err != nil {
+ return errors.Wrap(err, errMsg())
+ }
+
+ node := parseToRoute(rule)
+ if len(node.Paths) == 0 {
+ return errors.Errorf("node path is empty: %s", errMsg())
+ }
+
+ nodeMap := r.nodeMap
+ method = handlerMethod(method)
+ paths := node.Paths
+ for i, n := range paths {
+ lastNode := nodeMap[n]
+ if lastNode == nil {
+ lastNode = &nodeTree{nodeMap: make(map[string]*nodeTree), verbMap: make(map[string]*routeTarget)}
+ nodeMap[n] = lastNode
+ }
+ nodeMap = lastNode.nodeMap
+
+ if i == len(paths)-1 {
+ verbKey := fmt.Sprintf("%s:%s", method, lo.FromPtr(node.Verb))
+ lastNode.verbMap[verbKey] = &routeTarget{
+ Method: method,
+ Path: path,
+ Operation: operation,
+ extras: extras,
+ Verb: node.Verb,
+ Vars: node.Vars,
+ }
+ }
+ }
+ return nil
+}
+
+func (r *RouteTree) Match(method, url string) (*MatchOperation, error) {
+ pathNodes := strings.Split(strings.Trim(strings.TrimSpace(url), "/"), "/")
+ lastPath := strings.SplitN(pathNodes[len(pathNodes)-1], ":", 2)
+ errMsg := func(key string, value any) errors.Tags {
+ tt := errors.Tags{
+ "method": method,
+ "url": url,
+ }
+ if key != "" {
+ tt[key] = value
+ }
+ return tt
+ }
+ verb := ""
+
+ pathNodes[len(pathNodes)-1] = lastPath[0]
+ if len(lastPath) > 1 {
+ verb = lastPath[1]
+ }
+
+ method = handlerMethod(method)
+ verbKey := fmt.Sprintf("%s:%s", method, verb)
+
+ getVars := func(vars []*pathVariable, paths []string) []PathFieldVar {
+ vv := make([]PathFieldVar, 0, len(vars))
+ for _, v := range vars {
+ pathVar := PathFieldVar{Fields: v.fields}
+ if v.end > 0 {
+ pathVar.Value = strings.Join(paths[v.start:v.end+1], "/")
+ } else {
+ pathVar.Value = strings.Join(paths[v.start:], "/")
+ }
+
+ vv = append(vv, pathVar)
+ }
+ return vv
+ }
+
+ getPath := func(nodeMap map[string]*nodeTree, names ...string) (string, *nodeTree) {
+ for _, name := range names {
+ path := nodeMap[name]
+ if path != nil {
+ return name, path
+ }
+ }
+ return "", nil
+ }
+
+ nodeMap := r.nodeMap
+ lastIndex := len(pathNodes) - 1
+ for index, node := range pathNodes {
+ nodeName, path := getPath(nodeMap, node, star, doubleStar)
+ if path == nil {
+ return nil, errors.WrapTags(ErrPathNodeNotFound, errMsg("node", node))
+ }
+
+ nodeMap = path.nodeMap
+ switch nodeName {
+ case node:
+ if index != lastIndex {
+ continue
+ }
+ case star:
+ if index != lastIndex && len(path.nodeMap) != 0 {
+ nextPath := path.nodeMap[pathNodes[index+1]]
+ if nextPath != nil {
+ continue
+ }
+ }
+ case doubleStar:
+ }
+
+ vv := path.verbMap[verbKey]
+ if vv == nil {
+ return nil, errors.WrapTags(ErrOperationNotFound, errMsg("node", node))
+ }
+
+ return &MatchOperation{
+ Extras: vv.extras,
+ Method: vv.Method,
+ Path: vv.Path,
+ Operation: vv.Operation,
+ Verb: verb,
+ Vars: getVars(vv.Vars, pathNodes),
+ }, nil
+ }
+
+ return nil, errors.WrapTags(ErrOperationNotFound, errMsg("", nil))
+}
+
+func getOpt(nodes map[string]*nodeTree) []RouteOperation {
+ var sets []RouteOperation
+ for _, n := range nodes {
+ for _, v := range n.verbMap {
+ sets = append(sets, RouteOperation{
+ Method: v.Method,
+ Path: v.Path,
+ Operation: v.Operation,
+ Verb: lo.FromPtr(v.Verb),
+ Vars: funk.Map(v.Vars, func(v *pathVariable) string { return strings.Join(v.fields, ".") }),
+ Extras: v.extras,
+ })
+ }
+ sets = append(sets, getOpt(n.nodeMap)...)
+ }
+ return sets
+}
+
+func handlerMethod(method string) string {
+ return fmt.Sprintf("__%s__", strings.ToUpper(method))
+}
diff --git a/pkg/gateway/routertree/router_test.go b/pkg/gateway/routertree/router_test.go
new file mode 100644
index 000000000..c1bcdf93a
--- /dev/null
+++ b/pkg/gateway/routertree/router_test.go
@@ -0,0 +1,36 @@
+package routertree
+
+import (
+ "testing"
+
+ "github.com/pubgo/funk/v2/pretty"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRoute(t *testing.T) {
+ tree := New()
+ assert.NoError(t, tree.Add("get", "/user/user/{id}:get1", "get_user", nil))
+ assert.NoError(t, tree.Add("post", "/user/user/{id}", "post_user", nil))
+ assert.NoError(t, tree.Add("post", "/user/user1/{id=**}", "post_user1", nil))
+ assert.NoError(t, tree.Add("post", "/user/user/{id}/send_mail", "post_mail", nil))
+ opt, err := tree.Match("post", "/user/user/1/send_mail")
+ pretty.Println(opt)
+ assert.NoError(t, err)
+ assert.NotNil(t, opt)
+ assert.Equal(t, "post_mail", opt.Operation)
+ assert.Equal(t, "", opt.Verb)
+
+ opt, err = tree.Match("get", "/user/user/1:get1")
+ pretty.Println(opt)
+ assert.NoError(t, err)
+ assert.NotNil(t, opt)
+ assert.Equal(t, "get_user", opt.Operation)
+ assert.Equal(t, "get1", opt.Verb)
+
+ opt, err = tree.Match("post", "/user/user1/123/123456")
+ pretty.Println(opt)
+ assert.NoError(t, err)
+ assert.NotNil(t, opt)
+ assert.Equal(t, "post_user1", opt.Operation)
+ assert.Equal(t, "", opt.Verb)
+}
diff --git a/pkg/gateway/server_transport_stream.go b/pkg/gateway/stream.go
similarity index 96%
rename from pkg/gateway/server_transport_stream.go
rename to pkg/gateway/stream.go
index 31b084f95..413c75dde 100644
--- a/pkg/gateway/server_transport_stream.go
+++ b/pkg/gateway/stream.go
@@ -3,7 +3,7 @@ package gateway
import (
"context"
- "github.com/pubgo/funk/assert"
+ "github.com/pubgo/funk/v2/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
diff --git a/pkg/gateway/stream_grpc.go b/pkg/gateway/stream.grpc.go
similarity index 100%
rename from pkg/gateway/stream_grpc.go
rename to pkg/gateway/stream.grpc.go
diff --git a/pkg/gateway/stream_grpcweb.go b/pkg/gateway/stream.grpcweb.go
similarity index 100%
rename from pkg/gateway/stream_grpcweb.go
rename to pkg/gateway/stream.grpcweb.go
diff --git a/pkg/gateway/stream_http.go b/pkg/gateway/stream.http.go
similarity index 89%
rename from pkg/gateway/stream_http.go
rename to pkg/gateway/stream.http.go
index 229b9a98a..8e898e7a0 100644
--- a/pkg/gateway/stream_http.go
+++ b/pkg/gateway/stream.http.go
@@ -8,14 +8,15 @@ import (
"net/url"
"github.com/gofiber/fiber/v2"
- "github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/lava/pkg/gateway/internal/routertree"
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"
+
+ "github.com/pubgo/lava/v2/pkg/gateway/gatewayutils"
+ "github.com/pubgo/lava/v2/pkg/gateway/routertree"
)
type streamHTTP struct {
@@ -62,8 +63,8 @@ func (s *streamHTTP) Context() context.Context {
return NewContextWithServerTransportStream(s.ctx, s, s.method.grpcFullMethod)
}
-func (s *streamHTTP) SendMsg(m interface{}) error {
- if generic.IsNil(m) {
+func (s *streamHTTP) SendMsg(m any) error {
+ if funk.IsNil(m) {
return errors.New("stream http send msg got nil")
}
@@ -97,8 +98,8 @@ func (s *streamHTTP) SendMsg(m interface{}) error {
return errors.WrapCaller(err)
}
-func (s *streamHTTP) RecvMsg(m interface{}) error {
- if generic.IsNil(m) {
+func (s *streamHTTP) RecvMsg(m any) error {
+ if funk.IsNil(m) {
return errors.New("stream http recv msg got nil")
}
@@ -107,7 +108,7 @@ func (s *streamHTTP) RecvMsg(m interface{}) error {
return errors.New("stream http recv proto msg got unknown type message")
}
- var method = s.handler.Method()
+ method := s.handler.Method()
if method == http.MethodPut ||
method == http.MethodPost ||
@@ -152,7 +153,7 @@ func (s *streamHTTP) RecvMsg(m interface{}) error {
}
if len(s.params) > 0 {
- if err := PopulateQueryParameters(args, s.params, utilities.NewDoubleArray(nil)); err != nil {
+ if err := gatewayutils.PopulateQueryParameters(args, s.params, gatewayutils.NewDoubleArray(nil)); err != nil {
return errors.Wrapf(err, "failed to set query params, params=%v", s.params)
}
}
diff --git a/pkg/gateway/stream_inprocess.go b/pkg/gateway/stream.inprocess.go
similarity index 100%
rename from pkg/gateway/stream_inprocess.go
rename to pkg/gateway/stream.inprocess.go
diff --git a/pkg/gateway/stream_proxy.go b/pkg/gateway/stream.proxy.go
similarity index 94%
rename from pkg/gateway/stream_proxy.go
rename to pkg/gateway/stream.proxy.go
index a7b2e45f2..74cbd844c 100644
--- a/pkg/gateway/stream_proxy.go
+++ b/pkg/gateway/stream.proxy.go
@@ -7,21 +7,20 @@ import (
"context"
"io"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/lava/internal/logutil"
+ "github.com/pubgo/funk/v2/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/reflect/protoreflect"
-)
-var (
- clientStreamDescForProxying = &grpc.StreamDesc{
- ServerStreams: true,
- ClientStreams: true,
- }
+ "github.com/pubgo/lava/v2/internal/logutil"
)
+var clientStreamDescForProxying = &grpc.StreamDesc{
+ ServerStreams: true,
+ ClientStreams: true,
+}
+
func TransparentHandler(cli grpc.ClientConnInterface, inType, outType protoreflect.MessageType, opts ...grpc.CallOption) grpc.StreamHandler {
streamer := &handler{cli: cli, opts: opts, inType: inType, outType: outType}
return streamer.handler
@@ -36,7 +35,7 @@ type handler struct {
// handler is where the real magic of proxying happens.
// It is invoked like any gRPC server stream and uses the emptypb.Empty type server
// to proxy calls between the input and output streams.
-func (s *handler) handler(srv interface{}, serverStream grpc.ServerStream) error {
+func (s *handler) handler(srv any, serverStream grpc.ServerStream) error {
// little bit of gRPC internals never hurt anyone
fullMethodName, ok := grpc.MethodFromServerStream(serverStream)
if !ok {
diff --git a/pkg/gateway/stream_websocket.go b/pkg/gateway/stream.websocket.go
similarity index 100%
rename from pkg/gateway/stream_websocket.go
rename to pkg/gateway/stream.websocket.go
diff --git a/pkg/gateway/util.go b/pkg/gateway/util.go
index 2eebc9b69..9a210031f 100644
--- a/pkg/gateway/util.go
+++ b/pkg/gateway/util.go
@@ -9,14 +9,15 @@ import (
"strconv"
"strings"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/lava/pkg/gateway/internal/routertree"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/errors"
"google.golang.org/genproto/googleapis/api/annotations"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protoreflect"
+
+ "github.com/pubgo/lava/v2/pkg/gateway/routertree"
+ "github.com/pubgo/lava/v2/pkg/proto/lavapbv1"
)
func getReqBodyDesc(path *routertree.MatchOperation) []protoreflect.FieldDescriptor {
@@ -170,7 +171,7 @@ func newIncomingContext(ctx context.Context, header http.Header) (context.Contex
return metadata.NewIncomingContext(ctx, md), md
}
-func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path string, reqBody, rspBody string) error) error {
+func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth, path, reqBody, rspBody string) error) error {
if httpRule == nil {
return nil
}
@@ -190,7 +191,7 @@ func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path s
case *annotations.HttpRule_Custom:
method, template = pattern.Custom.GetKind(), pattern.Custom.GetPath()
default:
- return errors.Format("invalid type of pattern for HTTP httpRule: %T", pattern)
+ return errors.Errorf("invalid type of pattern for HTTP httpRule: %T", pattern)
}
if method == "" {
@@ -201,13 +202,13 @@ func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path s
return errors.New("invalid HTTP httpRule: HttpPath template is blank")
}
- var reqBody = httpRule.GetBody()
+ reqBody := httpRule.GetBody()
switch reqBody {
case "", "*":
reqBody = "*"
}
- var rspBody = httpRule.GetResponseBody()
+ rspBody := httpRule.GetResponseBody()
switch rspBody {
case "", "*":
rspBody = "*"
@@ -223,7 +224,7 @@ func handlerHttpRoute(httpRule *annotations.HttpRule, cb func(mth string, path s
}
if err := handlerHttpRoute(rule, cb); err != nil {
- return errors.Format("failed to add REST route (add binding #%d): %v", i+1, err)
+ return errors.Errorf("failed to add REST route (add binding #%d): %v", i+1, err)
}
}
@@ -245,7 +246,7 @@ func resolvePathToDescriptors(msg protoreflect.MessageDescriptor, path string) (
for i, part := range parts {
field := fields.ByName(protoreflect.Name(part))
if field == nil {
- return nil, errors.Format("in field HttpPath %q: element %q does not correspond to any field of type %s",
+ return nil, errors.Errorf("in field HttpPath %q: element %q does not correspond to any field of type %s",
path, part, msg.FullName())
}
@@ -255,7 +256,7 @@ func resolvePathToDescriptors(msg protoreflect.MessageDescriptor, path string) (
}
if field.Cardinality() == protoreflect.Repeated {
- return nil, errors.Format("in field HttpPath %q: field %q of type %s should not be a list or map", path, part, msg.FullName())
+ return nil, errors.Errorf("in field HttpPath %q: field %q of type %s should not be a list or map", path, part, msg.FullName())
}
msg = field.Message()
diff --git a/pkg/gateway/wrapper.go b/pkg/gateway/wrapper.go
index aa55fdfd2..c2e6ee7bf 100644
--- a/pkg/gateway/wrapper.go
+++ b/pkg/gateway/wrapper.go
@@ -3,10 +3,11 @@ package gateway
import (
"context"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
+ "github.com/pubgo/funk/v2/errors"
"google.golang.org/grpc"
"google.golang.org/protobuf/reflect/protoreflect"
+
+ "github.com/pubgo/lava/v2/pkg/proto/lavapbv1"
)
type serviceWrapper struct {
@@ -47,7 +48,7 @@ type methodWrapper struct {
// if h.grpcMethodDesc != nil {
// ctx := stream.Context()
//
-// reply, err := h.grpcMethodDesc.Handler(h.srv.srv, ctx, stream.RecvMsg, h.srv.opts.unaryInterceptor)
+// reply, err := h.grpcMethodDesc.Exec(h.srv.srv, ctx, stream.RecvMsg, h.srv.opts.unaryInterceptor)
// if err != nil {
// return errors.WrapCaller(err)
// }
@@ -61,24 +62,24 @@ type methodWrapper struct {
// }
//
// if h.srv.opts.streamInterceptor != nil {
-// return errors.WrapCaller(h.srv.opts.streamInterceptor(h.srv.srv, stream, info, h.grpcStreamDesc.Handler))
+// return errors.WrapCaller(h.srv.opts.streamInterceptor(h.srv.srv, stream, info, h.grpcStreamDesc.Exec))
// } else {
-// return errors.WrapCaller(h.grpcStreamDesc.Handler(h.srv.srv, stream))
+// return errors.WrapCaller(h.grpcStreamDesc.Exec(h.srv.srv, stream))
// }
// } else {
// return errors.Format("cannot find server handler")
// }
//}
-func grpcMethodHandlerWrapper(mth *methodWrapper, opts ...grpc.CallOption) GrpcMethodHandler {
+func grpcMethodHandlerWrapper(mth *methodWrapper, opts ...grpc.CallOption) MethodHandler {
return func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {
- var in = mth.inputType.New().Interface()
+ in := mth.inputType.New().Interface()
if err := dec(in); err != nil {
return nil, errors.WrapCaller(err)
}
- var h = func(ctx context.Context, req any) (any, error) {
- var out = mth.outputType.New().Interface()
+ h := func(ctx context.Context, req any) (any, error) {
+ out := mth.outputType.New().Interface()
err := mth.srv.remoteProxyCli.Invoke(ctx, mth.grpcFullMethod, req, out, opts...)
if err != nil {
return nil, err
diff --git a/pkg/grpc_builder/config.go b/pkg/grpcbuilder/config.go
similarity index 84%
rename from pkg/grpc_builder/config.go
rename to pkg/grpcbuilder/config.go
index e7fe170d0..5da0078bb 100644
--- a/pkg/grpc_builder/config.go
+++ b/pkg/grpcbuilder/config.go
@@ -1,14 +1,14 @@
-package grpc_builder
+package grpcbuilder
import (
"time"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/result"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/result"
"google.golang.org/grpc"
"google.golang.org/grpc/keepalive"
- "github.com/pubgo/lava/pkg/grpcutil"
+ "github.com/pubgo/lava/v2/pkg/grpcutil"
)
// default config: google.golang.org/grpc/internal/transport/defaults.go
@@ -63,22 +63,27 @@ type Config struct {
}
func (t *Config) Build(opts ...grpc.ServerOption) (r result.Result[*grpc.Server]) {
- defer recovery.Result(&r)
+ defer result.Recovery(&r)
- if t.KeepalivePolicy != nil {
- opts = append(opts, t.KeepalivePolicy.ToOpts())
+ cfg := t
+ if cfg == nil {
+ cfg = GetDefaultCfg()
}
- if t.KeepaliveParams != nil {
- opts = append(opts, t.KeepaliveParams.ToOpts())
+ if cfg.KeepalivePolicy != nil {
+ opts = append(opts, cfg.KeepalivePolicy.ToOpts())
+ }
+
+ if cfg.KeepaliveParams != nil {
+ opts = append(opts, cfg.KeepaliveParams.ToOpts())
}
srv := grpc.NewServer(opts...)
grpcutil.EnableReflection(srv)
- grpcutil.EnableHealth("", srv)
+ grpcutil.EnableHealth(version.Project(), srv)
grpcutil.EnableDebug(srv)
- return r.WithVal(srv)
+ return r.WithValue(srv)
}
func GetDefaultCfg() *Config {
diff --git a/pkg/grpcutil/util.go b/pkg/grpcutil/util.go
index 6f81ad5ae..2970ebcc7 100644
--- a/pkg/grpcutil/util.go
+++ b/pkg/grpcutil/util.go
@@ -6,7 +6,7 @@ import (
"net/http"
"strings"
- "github.com/pubgo/funk/assert"
+ "github.com/pubgo/funk/v2/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/admin"
"google.golang.org/grpc/channelz/service"
diff --git a/pkg/httputil/fiber.go b/pkg/httputil/fiber.go
index 381cc57b9..0b31939fe 100644
--- a/pkg/httputil/fiber.go
+++ b/pkg/httputil/fiber.go
@@ -196,9 +196,9 @@ func handlerFunc(h fasthttp.RequestHandler) http.HandlerFunc {
h(&ctx)
// Convert fasthttp Ctx > net/http
- ctx.Response.Header.VisitAll(func(k, v []byte) {
+ for k, v := range ctx.Response.Header.All() {
w.Header().Add(string(k), string(v))
- })
+ }
w.WriteHeader(ctx.Response.StatusCode())
_, _ = w.Write(ctx.Response.Body())
}
diff --git a/pkg/httputil/header.go b/pkg/httputil/header.go
index 5af8f005b..58b86896f 100644
--- a/pkg/httputil/header.go
+++ b/pkg/httputil/header.go
@@ -1,40 +1,17 @@
package httputil
+import "github.com/gofiber/fiber/v2"
+
// HTTP Headers were copied from net/http.
const (
- HeaderAuthorization = "Authorization"
- HeaderProxyAuthenticate = "Proxy-Authenticate"
- HeaderProxyAuthorization = "Proxy-Authorization"
- HeaderWWWAuthenticate = "WWW-Authenticate"
- HeaderAge = "Age"
- HeaderCacheControl = "Cache-Control"
- HeaderClearSiteData = "Clear-Site-Data"
- HeaderExpires = "Expires"
- HeaderPragma = "Pragma"
- HeaderWarning = "Warning"
- HeaderAcceptCH = "Accept-CH"
- HeaderAcceptCHLifetime = "Accept-CH-Lifetime"
- HeaderContentDPR = "Content-DPR"
- HeaderDPR = "DPR"
- HeaderEarlyData = "Early-Data"
- HeaderSaveData = "Save-Data"
- HeaderViewportWidth = "Viewport-Width"
- HeaderWidth = "Width"
- HeaderETag = "ETag"
- HeaderIfMatch = "If-Match"
- HeaderIfModifiedSince = "If-Modified-Since"
- HeaderIfNoneMatch = "If-None-Match"
- HeaderIfUnmodifiedSince = "If-Unmodified-Since"
- HeaderLastModified = "Last-Modified"
- HeaderVary = "Vary"
- HeaderConnection = "Connection"
- HeaderKeepAlive = "Keep-Alive"
+ HeaderAuthorization = fiber.HeaderAuthorization
+ HeaderConnection = fiber.HeaderConnection
+ HeaderKeepAlive = fiber.HeaderKeepAlive
HeaderAccept = "Accept"
HeaderAcceptCharset = "Accept-Charset"
HeaderAcceptEncoding = "Accept-Encoding"
HeaderAcceptLanguage = "Accept-Language"
HeaderCookie = "Cookie"
- HeaderExpect = "Expect"
HeaderMaxForwards = "Max-Forwards"
HeaderSetCookie = "Set-Cookie"
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
@@ -47,9 +24,6 @@ const (
HeaderAccessControlRequestMethod = "Access-Control-Request-Method"
HeaderOrigin = "Origin"
HeaderTimingAllowOrigin = "Timing-Allow-Origin"
- HeaderXPermittedCrossDomainPolicies = "X-Permitted-Cross-Domain-Policies"
- HeaderDNT = "DNT"
- HeaderTk = "Tk"
HeaderContentDisposition = "Content-Disposition"
HeaderContentEncoding = "Content-Encoding"
HeaderContentLanguage = "Content-Language"
@@ -57,7 +31,6 @@ const (
HeaderContentLocation = "Content-Location"
HeaderContentType = "Content-Type"
HeaderForwarded = "Forwarded"
- HeaderVia = "Via"
HeaderXForwardedFor = "X-Forwarded-For"
HeaderXForwardedHost = "X-Forwarded-Host"
HeaderXForwardedProto = "X-Forwarded-Proto"
@@ -79,10 +52,7 @@ const (
HeaderContentSecurityPolicy = "Content-Security-Policy"
HeaderContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only"
HeaderCrossOriginResourcePolicy = "Cross-Origin-Resource-Policy"
- HeaderExpectCT = "Expect-CT"
HeaderFeaturePolicy = "Feature-Policy"
- HeaderPublicKeyPins = "Public-Key-Pins"
- HeaderPublicKeyPinsReportOnly = "Public-Key-Pins-Report-Only"
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderUpgradeInsecureRequests = "Upgrade-Insecure-Requests"
HeaderXContentTypeOptions = "X-Content-Type-Options"
@@ -91,11 +61,6 @@ const (
HeaderXPoweredBy = "X-Powered-By"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderLastEventID = "Last-Event-ID"
- HeaderNEL = "NEL"
- HeaderPingFrom = "Ping-From"
- HeaderPingTo = "Ping-To"
- HeaderReportTo = "Report-To"
- HeaderTE = "TE"
HeaderTrailer = "Trailer"
HeaderTransferEncoding = "Transfer-Encoding"
HeaderSecWebSocketAccept = "Sec-WebSocket-Accept"
@@ -106,27 +71,19 @@ const (
HeaderAcceptPatch = "Accept-Patch"
HeaderAcceptPushPolicy = "Accept-Push-Policy"
HeaderAcceptSignature = "Accept-Signature"
- HeaderAltSvc = "Alt-Svc"
HeaderDate = "Date"
- HeaderIndex = "index"
HeaderLargeAllocation = "Large-Allocation"
HeaderLink = "Link"
HeaderPushPolicy = "Push-Policy"
HeaderRetryAfter = "Backoff-After"
HeaderServerTiming = "Server-Timing"
HeaderSignature = "Signature"
- HeaderSignedHeaders = "Signed-Headers"
- HeaderSourceMap = "SourceMap"
- HeaderUpgrade = "Upgrade"
- HeaderXDNSPrefetchControl = "X-DNS-Prefetch-Control"
- HeaderXPingback = "X-Pingback"
- HeaderXRequestID = "X-Request-ID"
+ HeaderXRequestID = fiber.HeaderXRequestID
HeaderXRequestProject = "X-Request-Project"
HeaderXRequestVersion = "X-Request-Version"
HeaderXRequestOperation = "X-Request-Operation"
HeaderAuthAct = "X-Request-Auth-Act"
HeaderXRequestedWith = "X-Requested-With"
- HeaderXRobotsTag = "X-Robots-Tag"
HeaderXUACompatible = "X-UA-Compatible"
HeaderVersion = "X-Api-Version"
HeaderTrace = "X-Api-Trace"
diff --git a/pkg/httputil/util.go b/pkg/httputil/util.go
index d7ef807a1..7be59be63 100644
--- a/pkg/httputil/util.go
+++ b/pkg/httputil/util.go
@@ -1,11 +1,69 @@
package httputil
import (
+ "log/slog"
"strings"
+ "dario.cat/mergo"
+ "github.com/gofiber/fiber/v2"
+ "github.com/gofiber/fiber/v2/middleware/cors"
+ "github.com/pubgo/funk/v2"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/errors/errcode"
+ "github.com/pubgo/funk/v2/proto/errorpb"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/samber/lo"
"github.com/valyala/fasthttp"
+ "google.golang.org/grpc/codes"
+
+ "github.com/pubgo/lava/v2/pkg/fiberbuilder"
)
+type Config struct {
+ Http *fiberbuilder.Config `yaml:"http"`
+ EnablePrintRouter bool `yaml:"enable_print_router"`
+ BaseUrl string `yaml:"base_url"`
+ HttpPort *int `yaml:"http_port"`
+}
+
+func DefaultCfg(config ...*Config) Config {
+ cfg := Config{
+ Http: &fiberbuilder.Config{
+ ServerHeader: "lava",
+ EnableIPValidation: true,
+ ETag: true,
+ ErrorHandler: ErrHandler,
+ BodyLimit: 1024 * 1024 * 500,
+ },
+ EnablePrintRouter: true,
+ BaseUrl: version.Project(),
+ HttpPort: lo.ToPtr(running.HttpPort()),
+ }
+
+ for _, t := range config {
+ if t == nil {
+ continue
+ }
+
+ err := mergo.Merge(&cfg, t, mergo.WithOverride, mergo.WithAppendSlice)
+ if err != nil {
+ slog.Error("failed to merge config to default config", "err", err, "source", t, "target", cfg)
+ panic(err)
+ }
+ }
+
+ cfg.Http.EnablePrintRoutes = cfg.EnablePrintRouter
+ cfg.BaseUrl = funk.DoFunc(func() string {
+ baseUrl := cfg.BaseUrl
+ if baseUrl == "" {
+ baseUrl = "/" + version.Project()
+ }
+ return "/" + strings.Trim(baseUrl, "/")
+ })
+ return cfg
+}
+
func IsWebsocket(h *fasthttp.RequestHeader) bool {
if strings.Contains(strings.ToLower(string(h.Peek("Connection"))), "upgrade") &&
strings.EqualFold(string(h.Peek("Upgrade")), "websocket") {
@@ -13,3 +71,56 @@ func IsWebsocket(h *fasthttp.RequestHeader) bool {
}
return false
}
+
+func ErrHandler(ctx *fiber.Ctx, err error) error {
+ if err == nil {
+ return nil
+ }
+
+ var errPb *errorpb.ErrCode
+ var fiberErr *fiber.Error
+ if errors.As(err, &fiberErr) && fiberErr != nil {
+ errPb = &errorpb.ErrCode{
+ Name: "lava.error",
+ StatusCode: errorpb.Code(errcode.Http2GrpcCode(int32(fiberErr.Code))),
+ Code: int32(fiberErr.Code),
+ Message: fiberErr.Message,
+ Details: errcode.MustTagsToAny(errors.Tags{
+ "path": ctx.Route().Path,
+ "version": running.Version(),
+ "instance": running.InstanceID,
+ }),
+ }
+ } else {
+ errPb = errcode.ParseError(err)
+ }
+
+ if errPb == nil || errPb.StatusCode == 0 {
+ return nil
+ }
+
+ code := errcode.GrpcCodeToHTTP(codes.Code(errPb.StatusCode))
+ ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON)
+ return ctx.Status(code).JSON(errPb)
+}
+
+func Cors() fiber.Handler {
+ return cors.New(cors.Config{
+ AllowOriginsFunc: func(origin string) bool {
+ return true
+ },
+ AllowMethods: strings.Join([]string{
+ fiber.MethodGet,
+ fiber.MethodPost,
+ fiber.MethodPut,
+ fiber.MethodDelete,
+ fiber.MethodPatch,
+ fiber.MethodHead,
+ fiber.MethodOptions,
+ }, ","),
+ // AllowHeaders: "",
+ AllowCredentials: true,
+ // ExposeHeaders: "",
+ MaxAge: 0,
+ })
+}
diff --git a/pkg/netutil/addr.go b/pkg/netutil/addr.go
index 872ddcf78..d7f688f4e 100644
--- a/pkg/netutil/addr.go
+++ b/pkg/netutil/addr.go
@@ -21,7 +21,7 @@ func AuthorityAddr(scheme, authority string) (addr string) {
return host + ":" + port
}
addr = net.JoinHostPort(host, port)
- return
+ return addr
}
func AuthorityHostPort(scheme, authority string) (host, port string) {
@@ -36,5 +36,5 @@ func AuthorityHostPort(scheme, authority string) (host, port string) {
if a, err := idna.ToASCII(host); err == nil {
host = a
}
- return
+ return host, port
}
diff --git a/pkg/netutil/conn.go b/pkg/netutil/conn.go
index 9e484b951..5a140ac02 100644
--- a/pkg/netutil/conn.go
+++ b/pkg/netutil/conn.go
@@ -9,8 +9,8 @@ import (
"strconv"
"strings"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/recovery"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/recovery"
)
type sockOpts struct {
@@ -110,12 +110,12 @@ func WithChmod(mask os.FileMode) SockOpt {
}
}
-func MustGetPort(addrOrNet interface{}) int {
+func MustGetPort(addrOrNet any) int {
return assert.Must1(GetPort(addrOrNet))
}
// GetPort returns the port of an endpoint address.
-func GetPort(addrOrNet interface{}) (int, error) {
+func GetPort(addrOrNet any) (int, error) {
var addr string
switch addrNet := addrOrNet.(type) {
case net.Addr:
diff --git a/pkg/netutil/util.go b/pkg/netutil/util.go
index dc3f2c62e..d65d002b6 100644
--- a/pkg/netutil/util.go
+++ b/pkg/netutil/util.go
@@ -1,6 +1,7 @@
package netutil
import (
+ "context"
"errors"
"fmt"
"io"
@@ -11,7 +12,7 @@ import (
"syscall"
"time"
- "github.com/pubgo/funk/assert"
+ "github.com/pubgo/funk/v2/assert"
)
var localIp = assert.Exit1(regexp.Compile(`\d+\.\d+\.\d+\.\d+`))
@@ -173,9 +174,18 @@ func getIP(r *http.Request) (string, error) {
if err != nil {
return "", err
}
+
netIP = net.ParseIP(ip)
if netIP != nil {
return ip, nil
}
- return "", fmt.Errorf("No valid ip found")
+
+ return "", fmt.Errorf("no valid ip found: %s", r.RemoteAddr)
+}
+
+func IsErrServerClosed(err error) bool {
+ return err == nil ||
+ errors.Is(err, http.ErrServerClosed) ||
+ errors.Is(err, net.ErrClosed) ||
+ errors.Is(err, context.Canceled)
}
diff --git a/pkg/proto/lavapbv1/form_file.pb.go b/pkg/proto/lavapbv1/form_file.pb.go
index b4a3be475..d8b062432 100644
--- a/pkg/proto/lavapbv1/form_file.pb.go
+++ b/pkg/proto/lavapbv1/form_file.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.2
-// protoc v5.28.2
+// protoc v5.29.3
// source: lava/form_file.proto
package lavapbv1
diff --git a/pkg/proto/lavapbv1/rpc.pb.go b/pkg/proto/lavapbv1/rpc.pb.go
index a212f63d8..3ac07c673 100644
--- a/pkg/proto/lavapbv1/rpc.pb.go
+++ b/pkg/proto/lavapbv1/rpc.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.2
-// protoc v5.28.2
+// protoc v5.29.3
// source: lava/rpc.proto
package lavapbv1
@@ -32,8 +32,6 @@ type RpcMeta struct {
Version *string `protobuf:"bytes,2,opt,name=version,proto3,oneof" json:"version,omitempty"`
// rpc tags
Tags map[string]string `protobuf:"bytes,3,rep,name=tags,proto3" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
- // grpc method name
- Method *string `protobuf:"bytes,4,opt,name=method,proto3,oneof" json:"method,omitempty"`
}
func (x *RpcMeta) Reset() {
@@ -87,13 +85,6 @@ func (x *RpcMeta) GetTags() map[string]string {
return nil
}
-func (x *RpcMeta) GetMethod() string {
- if x != nil && x.Method != nil {
- return *x.Method
- }
- return ""
-}
-
var file_lava_rpc_proto_extTypes = []protoimpl.ExtensionInfo{
{
ExtendedType: (*descriptorpb.MethodOptions)(nil),
@@ -117,30 +108,27 @@ var file_lava_rpc_proto_rawDesc = []byte{
0x0a, 0x0e, 0x6c, 0x61, 0x76, 0x61, 0x2f, 0x72, 0x70, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x12, 0x08, 0x6c, 0x61, 0x76, 0x61, 0x2e, 0x72, 0x70, 0x63, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63,
- 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xda, 0x01, 0x0a,
+ 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb2, 0x01, 0x0a,
0x07, 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x07,
0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x2f, 0x0a, 0x04, 0x74,
0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x6c, 0x61, 0x76, 0x61,
0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x2e, 0x54, 0x61, 0x67,
- 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x1b, 0x0a, 0x06,
- 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x06,
- 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x88, 0x01, 0x01, 0x1a, 0x37, 0x0a, 0x09, 0x54, 0x61, 0x67,
- 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
- 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
- 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02,
- 0x38, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x09,
- 0x0a, 0x07, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x3a, 0x4d, 0x0a, 0x07, 0x6f, 0x70, 0x74,
- 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
- 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74,
- 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa4, 0x8d, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c,
- 0x61, 0x76, 0x61, 0x2e, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x52,
- 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68,
- 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x62, 0x67, 0x6f, 0x2f, 0x6c, 0x61, 0x76,
- 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61,
- 0x70, 0x62, 0x76, 0x31, 0x3b, 0x6c, 0x61, 0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x62, 0x06, 0x70,
- 0x72, 0x6f, 0x74, 0x6f, 0x33,
+ 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x37, 0x0a, 0x09,
+ 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79,
+ 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76,
+ 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75,
+ 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
+ 0x6e, 0x3a, 0x4d, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1e, 0x2e, 0x67,
+ 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d,
+ 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xa4, 0x8d, 0x06,
+ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6c, 0x61, 0x76, 0x61, 0x2e, 0x72, 0x70, 0x63, 0x2e,
+ 0x52, 0x70, 0x63, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+ 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70,
+ 0x75, 0x62, 0x67, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72,
+ 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x61, 0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x3b, 0x6c, 0x61, 0x76,
+ 0x61, 0x70, 0x62, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
diff --git a/pkg/proto/lavapbv1/service.pb.go b/pkg/proto/lavapbv1/service.pb.go
index 2cb6e83b7..3d8645fec 100644
--- a/pkg/proto/lavapbv1/service.pb.go
+++ b/pkg/proto/lavapbv1/service.pb.go
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.35.2
-// protoc v5.28.2
+// protoc v5.29.3
// source: lava/service.proto
package lavapbv1
@@ -97,6 +97,67 @@ func (x *ServiceInfo) GetIp() string {
return ""
}
+type HttpResponse struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
+ StatusCode int32 `protobuf:"varint,2,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
+ Headers map[string]string `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *HttpResponse) Reset() {
+ *x = HttpResponse{}
+ mi := &file_lava_service_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *HttpResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HttpResponse) ProtoMessage() {}
+
+func (x *HttpResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_lava_service_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use HttpResponse.ProtoReflect.Descriptor instead.
+func (*HttpResponse) Descriptor() ([]byte, []int) {
+ return file_lava_service_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HttpResponse) GetBody() string {
+ if x != nil {
+ return x.Body
+ }
+ return ""
+}
+
+func (x *HttpResponse) GetStatusCode() int32 {
+ if x != nil {
+ return x.StatusCode
+ }
+ return 0
+}
+
+func (x *HttpResponse) GetHeaders() map[string]string {
+ if x != nil {
+ return x.Headers
+ }
+ return nil
+}
+
var File_lava_service_proto protoreflect.FileDescriptor
var file_lava_service_proto_rawDesc = []byte{
@@ -109,7 +170,19 @@ var file_lava_service_proto_rawDesc = []byte{
0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1a,
0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x70,
- 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69,
+ 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x70, 0x22, 0xbd, 0x01, 0x0a, 0x0c, 0x48,
+ 0x74, 0x74, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x62,
+ 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12,
+ 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02,
+ 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65,
+ 0x12, 0x3c, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
+ 0x0b, 0x32, 0x22, 0x2e, 0x6c, 0x61, 0x76, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x74, 0x74, 0x70,
+ 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73,
+ 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x3a,
+ 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10,
+ 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79,
+ 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+ 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x75, 0x62, 0x67, 0x6f, 0x2f, 0x6c,
0x61, 0x76, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x6c, 0x61,
0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x3b, 0x6c, 0x61, 0x76, 0x61, 0x70, 0x62, 0x76, 0x31, 0x62,
@@ -128,16 +201,19 @@ func file_lava_service_proto_rawDescGZIP() []byte {
return file_lava_service_proto_rawDescData
}
-var file_lava_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_lava_service_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_lava_service_proto_goTypes = []any{
- (*ServiceInfo)(nil), // 0: lava.v1.ServiceInfo
+ (*ServiceInfo)(nil), // 0: lava.v1.ServiceInfo
+ (*HttpResponse)(nil), // 1: lava.v1.HttpResponse
+ nil, // 2: lava.v1.HttpResponse.HeadersEntry
}
var file_lava_service_proto_depIdxs = []int32{
- 0, // [0:0] is the sub-list for method output_type
- 0, // [0:0] is the sub-list for method input_type
- 0, // [0:0] is the sub-list for extension type_name
- 0, // [0:0] is the sub-list for extension extendee
- 0, // [0:0] is the sub-list for field type_name
+ 2, // 0: lava.v1.HttpResponse.headers:type_name -> lava.v1.HttpResponse.HeadersEntry
+ 1, // [1:1] is the sub-list for method output_type
+ 1, // [1:1] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
}
func init() { file_lava_service_proto_init() }
@@ -151,7 +227,7 @@ func file_lava_service_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_lava_service_proto_rawDesc,
NumEnums: 0,
- NumMessages: 1,
+ NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
diff --git a/pkg/fiber_builder/websocket.go b/pkg/wsbuilder/websocket.go
similarity index 60%
rename from pkg/fiber_builder/websocket.go
rename to pkg/wsbuilder/websocket.go
index 19107be48..12db4020b 100644
--- a/pkg/fiber_builder/websocket.go
+++ b/pkg/wsbuilder/websocket.go
@@ -1,8 +1,6 @@
-package fiber_builder
+package wsbuilder
import (
- "errors"
- "io"
"sync"
"time"
@@ -79,7 +77,7 @@ func NewWs(handler func(*fiber.Ctx, *Conn), config ...WsCfg) fiber.Handler {
return func(c *fiber.Ctx) error {
conn := acquireConn()
// locals
- c.Context().VisitUserValues(func(key []byte, value interface{}) {
+ c.Context().VisitUserValues(func(key []byte, value any) {
conn.locals[string(key)] = value
})
@@ -90,14 +88,14 @@ func NewWs(handler func(*fiber.Ctx, *Conn), config ...WsCfg) fiber.Handler {
}
// queries
- c.Context().QueryArgs().VisitAll(func(key, value []byte) {
+ for key, value := range c.Context().QueryArgs().All() {
conn.queries[string(key)] = string(value)
- })
+ }
// cookies
- c.Context().Request.Header.VisitAllCookie(func(key, value []byte) {
+ for key, value := range c.Context().Request.Header.All() {
conn.cookies[string(key)] = string(value)
- })
+ }
if err := upgrader.Upgrade(c.Context(), func(fconn *websocket.Conn) {
conn.Conn = fconn
@@ -114,7 +112,7 @@ func NewWs(handler func(*fiber.Ctx, *Conn), config ...WsCfg) fiber.Handler {
// Conn https://godoc.org/github.com/gorilla/websocket#pkg-index
type Conn struct {
*websocket.Conn
- locals map[string]interface{}
+ locals map[string]any
params map[string]string
cookies map[string]string
queries map[string]string
@@ -122,7 +120,7 @@ type Conn struct {
// Conn pool
var poolConn = sync.Pool{
- New: func() interface{} {
+ New: func() any {
return new(Conn)
},
}
@@ -130,7 +128,7 @@ var poolConn = sync.Pool{
// Acquire Conn from pool
func acquireConn() *Conn {
conn := poolConn.Get().(*Conn)
- conn.locals = make(map[string]interface{})
+ conn.locals = make(map[string]any)
conn.params = make(map[string]string)
conn.queries = make(map[string]string)
conn.cookies = make(map[string]string)
@@ -145,7 +143,7 @@ func releaseConn(conn *Conn) {
// Locals makes it possible to pass interface{} values under string keys scoped to the request
// and therefore available to all following routes that match the request.
-func (conn *Conn) Locals(key string) interface{} {
+func (conn *Conn) Locals(key string) any {
return conn.locals[key]
}
@@ -185,79 +183,79 @@ func (conn *Conn) Cookies(key string, defaultValue ...string) string {
// Constants are taken from https://github.com/fasthttp/websocket/blob/master/conn.go#L43
// Close codes defined in RFC 6455, section 11.7.
-const (
- CloseNormalClosure = 1000
- CloseGoingAway = 1001
- CloseProtocolError = 1002
- CloseUnsupportedData = 1003
- CloseNoStatusReceived = 1005
- CloseAbnormalClosure = 1006
- CloseInvalidFramePayloadData = 1007
- ClosePolicyViolation = 1008
- CloseMessageTooBig = 1009
- CloseMandatoryExtension = 1010
- CloseInternalServerErr = 1011
- CloseServiceRestart = 1012
- CloseTryAgainLater = 1013
- CloseTLSHandshake = 1015
-)
-
-// The message types are defined in RFC 6455, section 11.8.
-const (
- // TextMessage denotes a text data message. The text message payload is
- // interpreted as UTF-8 encoded text data.
- TextMessage = 1
-
- // BinaryMessage denotes a binary data message.
- BinaryMessage = 2
-
- // CloseMessage denotes a close control message. The optional message
- // payload contains a numeric code and text. Use the FormatCloseMessage
- // function to format a close message payload.
- CloseMessage = 8
-
- // PingMessage denotes a ping control message. The optional message payload
- // is UTF-8 encoded text.
- PingMessage = 9
-
- // PongMessage denotes a pong control message. The optional message payload
- // is UTF-8 encoded text.
- PongMessage = 10
-)
-
-var (
- ErrBadHandshake = errors.New("websocket: bad handshake")
- ErrCloseSent = errors.New("websocket: close sent")
- ErrReadLimit = errors.New("websocket: read limit exceeded")
-)
-
-// FormatCloseMessage formats closeCode and text as a WebSocket close message.
-// An empty message is returned for code CloseNoStatusReceived.
-func FormatCloseMessage(closeCode int, text string) []byte {
- return websocket.FormatCloseMessage(closeCode, text)
-}
-
-// IsCloseError returns boolean indicating whether the error is a *CloseError
-// with one of the specified codes.
-func IsCloseError(err error, codes ...int) bool {
- return websocket.IsCloseError(err, codes...)
-}
-
-// IsUnexpectedCloseError returns boolean indicating whether the error is a
-// *CloseError with a code not in the list of expected codes.
-func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
- return websocket.IsUnexpectedCloseError(err, expectedCodes...)
-}
-
-// IsWebSocketUpgrade returns true if the client requested upgrade to the
-// WebSocket protocol.
-func IsWebSocketUpgrade(c *fiber.Ctx) bool {
- return websocket.FastHTTPIsWebSocketUpgrade(c.Context())
-}
-
-// JoinMessages concatenates received messages to create a single io.Reader.
-// The string term is appended to each message. The returned reader does not
-// support concurrent calls to the Read method.
-func JoinMessages(c *websocket.Conn, term string) io.Reader {
- return websocket.JoinMessages(c, term)
-}
+//const (
+// CloseNormalClosure = 1000
+// CloseGoingAway = 1001
+// CloseProtocolError = 1002
+// CloseUnsupportedData = 1003
+// CloseNoStatusReceived = 1005
+// CloseAbnormalClosure = 1006
+// CloseInvalidFramePayloadData = 1007
+// ClosePolicyViolation = 1008
+// CloseMessageTooBig = 1009
+// CloseMandatoryExtension = 1010
+// CloseInternalServerErr = 1011
+// CloseServiceRestart = 1012
+// CloseTryAgainLater = 1013
+// CloseTLSHandshake = 1015
+//)
+//
+//// The message types are defined in RFC 6455, section 11.8.
+//const (
+// // TextMessage denotes a text data message. The text message payload is
+// // interpreted as UTF-8 encoded text data.
+// TextMessage = 1
+//
+// // BinaryMessage denotes a binary data message.
+// BinaryMessage = 2
+//
+// // CloseMessage denotes a close control message. The optional message
+// // payload contains a numeric code and text. Use the FormatCloseMessage
+// // function to format a close message payload.
+// CloseMessage = 8
+//
+// // PingMessage denotes a ping control message. The optional message payload
+// // is UTF-8 encoded text.
+// PingMessage = 9
+//
+// // PongMessage denotes a pong control message. The optional message payload
+// // is UTF-8 encoded text.
+// PongMessage = 10
+//)
+//
+//var (
+// ErrBadHandshake = errors.New("websocket: bad handshake")
+// ErrCloseSent = errors.New("websocket: close sent")
+// ErrReadLimit = errors.New("websocket: read limit exceeded")
+//)
+//
+//// FormatCloseMessage formats closeCode and text as a WebSocket close message.
+//// An empty message is returned for code CloseNoStatusReceived.
+//func FormatCloseMessage(closeCode int, text string) []byte {
+// return websocket.FormatCloseMessage(closeCode, text)
+//}
+//
+//// IsCloseError returns boolean indicating whether the error is a *CloseError
+//// with one of the specified codes.
+//func IsCloseError(err error, codes ...int) bool {
+// return websocket.IsCloseError(err, codes...)
+//}
+//
+//// IsUnexpectedCloseError returns boolean indicating whether the error is a
+//// *CloseError with a code not in the list of expected codes.
+//func IsUnexpectedCloseError(err error, expectedCodes ...int) bool {
+// return websocket.IsUnexpectedCloseError(err, expectedCodes...)
+//}
+//
+//// IsWebSocketUpgrade returns true if the client requested upgrade to the
+//// WebSocket protocol.
+//func IsWebSocketUpgrade(c *fiber.Ctx) bool {
+// return websocket.FastHTTPIsWebSocketUpgrade(c.Context())
+//}
+//
+//// JoinMessages concatenates received messages to create a single io.Reader.
+//// The string term is appended to each message. The returned reader does not
+//// support concurrent calls to the Read method.
+//func JoinMessages(c *websocket.Conn, term string) io.Reader {
+// return websocket.JoinMessages(c, term)
+//}
diff --git a/pkg/wsutil/ws.go b/pkg/wsbuilder/ws.go
similarity index 99%
rename from pkg/wsutil/ws.go
rename to pkg/wsbuilder/ws.go
index 70ebba759..a608005e0 100644
--- a/pkg/wsutil/ws.go
+++ b/pkg/wsbuilder/ws.go
@@ -1,4 +1,4 @@
-package wsutil
+package wsbuilder
import (
"errors"
diff --git a/pkg/wsproxy/websocket_proxy.go b/pkg/wsproxy/websocket_proxy.go
index 95476b0e6..bd492b8f2 100644
--- a/pkg/wsproxy/websocket_proxy.go
+++ b/pkg/wsproxy/websocket_proxy.go
@@ -12,9 +12,10 @@ import (
"time"
"github.com/gorilla/websocket"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/lava/internal/logutil"
+ "github.com/pubgo/funk/v2/log"
"golang.org/x/net/context"
+
+ "github.com/pubgo/lava/v2/internal/logutil"
)
const (
@@ -369,7 +370,7 @@ func (ws WsConn) WritePreparedMessage(pm *websocket.PreparedMessage) error {
return ws.Conn.WritePreparedMessage(pm)
}
-func (ws WsConn) WriteJSON(v interface{}) error {
+func (ws WsConn) WriteJSON(v any) error {
ws.mu.Lock()
defer ws.mu.Unlock()
return ws.Conn.WriteJSON(v)
diff --git a/proto/lava/event.proto b/proto/lava/event.proto
deleted file mode 100644
index 548b76079..000000000
--- a/proto/lava/event.proto
+++ /dev/null
@@ -1,12 +0,0 @@
-syntax = "proto3";
-
-package lava.v1;
-
-option go_package = "github.com/pubgo/lava/pkg/proto/lavapbv1;lavapbv1";
-
-enum EventType {
- UNKNOWN = 0;
- CREATE = 1;
- UPDATE = 2;
- DELETE = 3;
-}
diff --git a/proto/lava/service.proto b/proto/lava/service.proto
index 93c38be62..c6e0d9faa 100644
--- a/proto/lava/service.proto
+++ b/proto/lava/service.proto
@@ -11,3 +11,9 @@ message ServiceInfo {
string hostname = 4;
string ip = 5;
}
+
+message HttpResponse {
+ string body = 1;
+ int32 status_code = 2;
+ map headers = 3;
+}
diff --git a/protobuf.yaml b/protobuf.yaml
index 53ecf6327..ef4d3cbad 100644
--- a/protobuf.yaml
+++ b/protobuf.yaml
@@ -1,4 +1,4 @@
-checksum: 2cb52bc4143a48e6062590082a7310ff425eff96
+checksum: c988bccc50f12264b8f61e5b8ebcc25be4e869b5
vendor: .proto
base:
out: ./pkg
@@ -15,13 +15,17 @@ deps:
version: v0.0.0-20220224004616-3c171936039b
- name: google/protobuf
url: /usr/local/include/google/protobuf
+ optional: true
+ - name: google/protobuf
+ url: /opt/homebrew/include/google/protobuf
+ optional: true
- name: errorpb
- url: github.com/pubgo/funk
+ url: github.com/pubgo/funk/v2
path: /proto/errorpb
- version: v0.5.56
+ version: v2.0.0-alpha.12
plugins:
- name: go
- - name: go-errors
+ - name: go-errors2
- name: go-grpc
opt:
- require_unimplemented_servers=false
diff --git a/servers/grpcs/codec.go b/servers/grpcs/codec.go
index 6372cb805..9f3af5c54 100644
--- a/servers/grpcs/codec.go
+++ b/servers/grpcs/codec.go
@@ -3,7 +3,7 @@ package grpcs
import (
"google.golang.org/grpc/encoding"
- codec "github.com/pubgo/lava/core/encoding"
+ codec "github.com/pubgo/lava/v2/core/encoding"
)
func init() {
diff --git a/servers/grpcs/config.go b/servers/grpcs/config.go
index e6009ee9f..a9b6be994 100644
--- a/servers/grpcs/config.go
+++ b/servers/grpcs/config.go
@@ -1,8 +1,8 @@
package grpcs
import (
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/pkg/grpc_builder"
+ "github.com/pubgo/lava/v2/pkg/fiberbuilder"
+ "github.com/pubgo/lava/v2/pkg/grpcbuilder"
)
const (
@@ -14,24 +14,12 @@ type GrpcServerConfigLoader struct {
}
type Config struct {
- EnablePrintRoutes bool `yaml:"enable_print_routes"`
+ Http *fiberbuilder.Config `yaml:"http"`
+ GrpcConfig *grpcbuilder.Config `yaml:"grpc"`
+ EnablePrintRouter bool `yaml:"enable_print_router"`
BaseUrl string `yaml:"base_url"`
- GrpcConfig *grpc_builder.Config `yaml:"grpc_config"`
- EnableCors bool `yaml:"enable_cors"`
- EnablePingPong bool `yaml:"enable_ping_pong"`
-
- // unix seconds
- PingPongTime int32 `yaml:"ping_pong_time"`
- GrpcPort *int `yaml:"grpc_port"`
- HttpPort *int `yaml:"http_port"`
-
- WsReadLimit *int `yaml:"ws_read_limit"`
}
func defaultCfg() *Config {
- return &Config{
- EnablePrintRoutes: true,
- BaseUrl: version.Project(),
- GrpcConfig: grpc_builder.GetDefaultCfg(),
- }
+ return &Config{}
}
diff --git a/servers/grpcs/inner_server.go b/servers/grpcs/inner_server.go
deleted file mode 100644
index ec6baa79d..000000000
--- a/servers/grpcs/inner_server.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package grpcs
-
-import (
- "context"
- "fmt"
-
- "github.com/fullstorydev/grpchan/inprocgrpc"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/lava/clients/grpcc"
- "github.com/pubgo/lava/clients/grpcc/grpcc_config"
- "github.com/pubgo/lava/core/metrics"
- "github.com/pubgo/lava/internal/middlewares/middleware_accesslog"
- "github.com/pubgo/lava/internal/middlewares/middleware_metric"
- "github.com/pubgo/lava/internal/middlewares/middleware_recovery"
- "github.com/pubgo/lava/internal/middlewares/middleware_service_info"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/gateway"
- "google.golang.org/grpc"
- "google.golang.org/grpc/metadata"
- "google.golang.org/protobuf/reflect/protoreflect"
- "google.golang.org/protobuf/reflect/protoregistry"
-)
-
-// NewInner grpc 服务内部通信
-func NewInner(handlers []lava.GrpcRouter, grpcProxy []lava.GrpcProxy, dixMiddlewares []lava.Middleware, metric metrics.Metric, log log.Logger) *lava.InnerServer {
- middlewares := lava.Middlewares{
- middleware_service_info.New(),
- middleware_metric.New(metric),
- middleware_accesslog.New(log),
- middleware_recovery.New(),
- }
- middlewares = append(middlewares, dixMiddlewares...)
-
- cc := new(inprocgrpc.Channel)
- srvMidMap := make(map[string][]lava.Middleware)
- for _, h := range handlers {
- desc := h.ServiceDesc()
- assert.If(desc == nil, "desc is nil")
-
- srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], middlewares...)
- srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...)
- cc.RegisterService(h.ServiceDesc(), h)
- }
-
- for _, h := range grpcProxy {
- desc := h.ServiceDesc()
- assert.If(desc == nil, "desc is nil")
-
- srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], middlewares...)
- srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...)
-
- cli := grpcc.New(
- &grpcc_config.Cfg{
- Service: &grpcc_config.ServiceCfg{
- Name: h.Proxy().Name,
- Addr: h.Proxy().Addr,
- Scheme: h.Proxy().Resolver,
- },
- },
- grpcc.Params{
- Log: log,
- Metric: metric,
- },
- h.Middlewares()...,
- )
-
- for i := range desc.Methods {
- var fullPath = fmt.Sprintf("/%s/%s", desc.ServiceName, desc.Methods[i].MethodName)
- inT, outT := getMthType(desc.ServiceName, desc.Methods[i].MethodName)
- desc.Methods[i].Handler = grpcMethodHandlerWrapper(cli, fullPath, inT, outT)
- }
-
- for i := range desc.Streams {
- inT, outT := getMthType(desc.ServiceName, desc.Methods[i].MethodName)
- desc.Streams[i].Handler = grpcMethodStreamWrapper(cli, inT, outT)
- }
- cc.RegisterService(h.ServiceDesc(), h)
- }
-
- cc = cc.WithServerUnaryInterceptor(handlerUnaryMiddle(srvMidMap))
- cc = cc.WithServerStreamInterceptor(handlerStreamMiddle(srvMidMap))
- return &lava.InnerServer{ClientConnInterface: cc}
-}
-
-func grpcMethodHandlerWrapper(cli grpc.ClientConnInterface, fullPath string, inType, outType protoreflect.MessageType) gateway.GrpcMethodHandler {
- return func(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) {
- var in = inType.New().Interface()
- if err := dec(in); err != nil {
- return nil, errors.WrapCaller(err)
- }
-
- var h = func(ctx context.Context, req any) (any, error) {
- var out = outType.New().Interface()
- var header metadata.MD
- var trailer metadata.MD
- err := cli.Invoke(ctx, fullPath, req, out, append([]grpc.CallOption{}, grpc.Header(&header), grpc.Trailer(&trailer))...)
- if err != nil {
- return nil, errors.WrapCaller(err)
- }
- return out, nil
- }
-
- // 获取 server header 并转换成 client header
- if interceptor == nil {
- return h(ctx, in)
- }
-
- return interceptor(ctx, in, &grpc.UnaryServerInfo{FullMethod: fullPath}, h)
- }
-}
-
-func getMthType(srvName string, mthName string) (protoreflect.MessageType, protoreflect.MessageType) {
- d := assert.Must1(protoregistry.GlobalFiles.FindDescriptorByName(protoreflect.FullName(srvName)))
-
- sd, ok := d.(protoreflect.ServiceDescriptor)
- if !ok {
- assert.Must(errors.Format("invalid httpPathRule descriptor %T", d))
- }
-
- findMethodDesc := func(methodName string) protoreflect.MethodDescriptor {
- md := sd.Methods().ByName(protoreflect.Name(methodName))
- assert.If(md == nil, "missing protobuf descriptor for %v", methodName)
- return md
- }
-
- mthDesc := findMethodDesc(mthName)
-
- inputType := assert.Must1(protoregistry.GlobalTypes.FindMessageByName(mthDesc.Input().FullName()))
- outputType := assert.Must1(protoregistry.GlobalTypes.FindMessageByName(mthDesc.Output().FullName()))
- return inputType, outputType
-}
-
-func grpcMethodStreamWrapper(cli grpc.ClientConnInterface, inType, outType protoreflect.MessageType) gateway.GrpcStreamHandler {
- return gateway.TransparentHandler(cli, inType, outType)
-}
diff --git a/servers/grpcs/middleware.go b/servers/grpcs/middleware.go
index 1136f425d..c759d571a 100644
--- a/servers/grpcs/middleware.go
+++ b/servers/grpcs/middleware.go
@@ -2,28 +2,29 @@ package grpcs
import (
"context"
- "fmt"
"time"
"github.com/gofiber/fiber/v2"
grpcMiddle "github.com/grpc-ecosystem/go-grpc-middleware"
- "github.com/pubgo/funk/convert"
- "github.com/pubgo/funk/errors/errutil"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/proto/errorpb"
- "github.com/pubgo/funk/strutil"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/core/lavacontexts"
- "github.com/pubgo/lava/pkg/proto/lavapbv1"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/convert"
+ "github.com/pubgo/funk/v2/errors"
+ "github.com/pubgo/funk/v2/errors/errcode"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/proto/errorpb"
+ "github.com/pubgo/funk/v2/strutil"
"github.com/rs/xid"
"github.com/valyala/fasthttp"
"google.golang.org/grpc"
+ "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/grpcutil"
- "github.com/pubgo/lava/pkg/httputil"
+ "github.com/pubgo/lava/v2/core/lavacontexts"
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/grpcutil"
+ "github.com/pubgo/lava/v2/pkg/httputil"
+ "github.com/pubgo/lava/v2/pkg/proto/lavapbv1"
)
func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServerInterceptor {
@@ -36,7 +37,7 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ
return &rpcResponse{header: req.(*rpcRequest).rspHeader, dt: dt}, nil
}
- return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
+ return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
reqMetadata, ok := metadata.FromIncomingContext(ctx)
if !ok {
reqMetadata = make(metadata.MD)
@@ -65,6 +66,11 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ
clientInfo.Name = p
}
+ // get peer from context
+ if p, ok := peer.FromContext(ctx); ok {
+ reqMetadata.Set("remote", p.Addr.String())
+ }
+
// timeout for server deadline
to := reqMetadata.Get("timeout")
delete(reqMetadata, "timeout")
@@ -74,7 +80,7 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ
if dur, err := time.ParseDuration(to[0]); err == nil {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, dur)
- _ = cancel
+ defer cancel()
}
}
@@ -118,52 +124,34 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ
reqMetadata.Set(httputil.HeaderXRequestID, reqId)
reqMetadata.Set(httputil.HeaderXRequestVersion, version.Version())
reqMetadata.Set(httputil.HeaderXRequestOperation, info.FullMethod)
- rpcReq.rspHeader.VisitAll(func(key, value []byte) {
+ for key, value := range rpcReq.rspHeader.All() {
reqMetadata.Set(convert.BtoS(key), convert.BtoS(value))
- })
-
- if err := grpc.SetHeader(ctx, reqMetadata); err != nil {
- log.Err(err, ctx).Msg("grpc send trailer failed")
}
if err := grpc.SendHeader(ctx, reqMetadata); err != nil {
- log.Err(err, ctx).Msg("grpc send trailer failed")
+ log.Err(err, ctx).
+ Str("grpc-method", info.FullMethod).
+ Msg("grpc send trailer header failed")
}
-
}()
ctx = lavacontexts.CreateReqHeader(ctx, reqHeader)
ctx = lavacontexts.CreateRspHeader(ctx, rpcReq.rspHeader)
rsp, err := lava.Chain(middlewares[srvName]...).Middleware(unaryWrapper)(ctx, rpcReq)
if err != nil {
- pb := errutil.ParseError(err)
- if pb.Trace == nil {
- pb.Trace = new(errorpb.ErrTrace)
- }
- pb.Trace.Operation = rpcReq.Operation()
- pb.Trace.Service = rpcReq.Service()
- pb.Trace.Version = version.Version()
+ pb := errcode.ParseError(err)
+ pb.Details = append(pb.Details, errcode.MustTagsToAny(errors.Tags{"reqHeader": string(rpcReq.Header().Header())})...)
- if pb.Msg != nil {
- pb.Msg = new(errorpb.ErrMsg)
- }
- pb.Msg.Msg = err.Error()
- pb.Msg.Detail = fmt.Sprintf("%#v", err)
- if pb.Msg.Tags == nil {
- pb.Msg.Tags = make(map[string]string)
+ if pb.Message == "" {
+ pb.Message = err.Error()
}
- pb.Msg.Tags["reqHeader"] = string(rpcReq.Header().Header())
- if pb.Code.Message == "" {
- pb.Code.Message = err.Error()
+ if pb.Code == 0 {
+ pb.StatusCode = errorpb.Code_Internal
+ pb.Code = int32(errcode.GrpcCodeToHTTP(codes.Code(errorpb.Code_Internal)))
}
- if pb.Code.Code == 0 {
- pb.Code.StatusCode = errorpb.Code_Internal
- pb.Code.Code = int32(errorpb.Code_Internal)
- }
-
- return nil, errutil.ConvertErr2Status(pb).Err()
+ return nil, errcode.ConvertErr2Status(pb).Err()
}
return rsp.(*rpcResponse).dt, nil
@@ -173,10 +161,7 @@ func handlerUnaryMiddle(middlewares map[string][]lava.Middleware) grpc.UnaryServ
func handlerStreamMiddle(middlewares map[string][]lava.Middleware) grpc.StreamServerInterceptor {
streamWrapper := func(ctx context.Context, req lava.Request) (lava.Response, error) {
reqCtx := req.(*rpcRequest)
- wrap := &grpcMiddle.WrappedServerStream{
- WrappedContext: ctx,
- ServerStream: reqCtx.stream,
- }
+ wrap := &grpcMiddle.WrappedServerStream{WrappedContext: ctx, ServerStream: reqCtx.stream}
if err := reqCtx.handlerStream(reqCtx.srv, wrap); err != nil {
return nil, err
}
@@ -184,7 +169,7 @@ func handlerStreamMiddle(middlewares map[string][]lava.Middleware) grpc.StreamSe
return &rpcResponse{stream: reqCtx.stream, header: new(lava.ResponseHeader)}, nil
}
- return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
+ return func(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
ctx := stream.Context()
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
@@ -244,48 +229,39 @@ func handlerStreamMiddle(middlewares map[string][]lava.Middleware) grpc.StreamSe
func() string { return xid.New().String() },
)
rpcReq.Header().Set(httputil.HeaderXRequestID, reqId)
- ctx = lavacontexts.CreateCtxWithReqID(ctx, reqId)
+ ctx = lavacontexts.CreateCtxWithReqID(ctx, reqId)
ctx = lavacontexts.CreateReqHeader(ctx, header)
ctx = lavacontexts.CreateRspHeader(ctx, rpcReq.rspHeader)
rsp, err := lava.Chain(middlewares[srvName]...).Middleware(streamWrapper)(ctx, rpcReq)
if err != nil {
- pb := errutil.ParseError(err)
- pb.Trace.Operation = rpcReq.Operation()
- pb.Trace.Service = rpcReq.Service()
- pb.Trace.Version = version.Version()
- pb.Msg.Msg = err.Error()
- pb.Msg.Detail = fmt.Sprintf("%v", err)
- if pb.Msg.Tags == nil {
- pb.Msg.Tags = make(map[string]string)
- }
-
- if pb.Code.Message == "" {
- pb.Code.Message = err.Error()
+ pb := errcode.ParseError(err)
+ if pb.Message == "" {
+ pb.Message = err.Error()
}
- if pb.Code.Code == 0 {
- pb.Code.Code = int32(errorpb.Code_Internal)
- pb.Code.StatusCode = errorpb.Code_Internal
+ if pb.Code == 0 {
+ pb.StatusCode = errorpb.Code_Internal
+ pb.Code = int32(errcode.GrpcCodeToHTTP(codes.Code(errorpb.Code_Internal)))
}
- return errutil.ConvertErr2Status(pb).Err()
+ return errcode.ConvertErr2Status(pb).Err()
}
h := rsp.Header()
md = make(metadata.MD)
- h.VisitAll(func(key, value []byte) {
+ for key, value := range h.All() {
md.Append(convert.BtoS(key), convert.BtoS(value))
- })
- return grpc.SetTrailer(ctx, md)
+ }
+ return grpc.SendHeader(ctx, md)
}
}
func handlerHttpMiddle(middlewares []lava.Middleware) func(fbCtx *fiber.Ctx) error {
h := func(ctx context.Context, req lava.Request) (lava.Response, error) {
- reqCtx := req.(*httpRequest)
- reqCtx.ctx.SetUserContext(ctx)
- return &httpResponse{ctx: reqCtx.ctx}, reqCtx.ctx.Next()
+ reqCtx := req.(*httpRequest).ctx
+ reqCtx.SetUserContext(ctx)
+ return &httpResponse{ctx: reqCtx}, reqCtx.Next()
}
h = lava.Chain(middlewares...).Middleware(h)
diff --git a/servers/grpcs/request.go b/servers/grpcs/request.go
index 272a3a0ee..b7e0d0667 100644
--- a/servers/grpcs/request.go
+++ b/servers/grpcs/request.go
@@ -2,11 +2,11 @@ package grpcs
import (
"fmt"
- "strings"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/lava/lava"
"google.golang.org/grpc"
+
+ "github.com/pubgo/lava/v2/lava"
)
var _ lava.Request = (*rpcRequest)(nil)
@@ -15,20 +15,20 @@ type rpcRequest struct {
handler grpc.UnaryHandler
handlerStream grpc.StreamHandler
stream grpc.ServerStream
- srv interface{}
+ srv any
service string
method string
url string
contentType string
header *lava.RequestHeader
rspHeader *lava.ResponseHeader
- payload interface{}
+ payload any
}
-func (r *rpcRequest) Kind() string { return "grpc" }
+func (r *rpcRequest) Kind() string { return lava.RequestKindGrpc }
func (r *rpcRequest) Client() bool { return false }
func (r *rpcRequest) Header() *lava.RequestHeader { return r.header }
-func (r *rpcRequest) Payload() interface{} { return r.payload }
+func (r *rpcRequest) Payload() any { return r.payload }
func (r *rpcRequest) ContentType() string { return r.contentType }
func (r *rpcRequest) Service() string { return r.service }
func (r *rpcRequest) Operation() string { return r.method }
@@ -41,18 +41,14 @@ type httpRequest struct {
ctx *fiber.Ctx
}
-func (r *httpRequest) Kind() string { return "http" }
+func (r *httpRequest) Kind() string { return lava.RequestKindHttp }
func (r *httpRequest) Operation() string {
- return fmt.Sprintf("%s %s", strings.TrimSpace(r.ctx.Method()), strings.TrimSpace(r.ctx.Route().Path))
+ return fmt.Sprintf("%s %s", r.ctx.Method(), r.ctx.Route().Path)
}
func (r *httpRequest) Client() bool { return false }
func (r *httpRequest) Header() *lava.RequestHeader { return &r.ctx.Request().Header }
-func (r *httpRequest) Payload() interface{} { return r.ctx.Body() }
-
-func (r *httpRequest) ContentType() string {
- return string(r.ctx.Request().Header.ContentType())
-}
-
-func (r *httpRequest) Service() string { return r.ctx.OriginalURL() }
-func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) }
-func (r *httpRequest) Stream() bool { return false }
+func (r *httpRequest) Payload() any { return r.ctx.Body() }
+func (r *httpRequest) ContentType() string { return string(r.ctx.Request().Header.ContentType()) }
+func (r *httpRequest) Service() string { return r.ctx.Route().Path }
+func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) }
+func (r *httpRequest) Stream() bool { return r.ctx.Request().IsBodyStream() }
diff --git a/servers/grpcs/response.go b/servers/grpcs/response.go
index a19297d68..17bae82db 100644
--- a/servers/grpcs/response.go
+++ b/servers/grpcs/response.go
@@ -2,8 +2,9 @@ package grpcs
import (
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/lava/lava"
"google.golang.org/grpc"
+
+ "github.com/pubgo/lava/v2/lava"
)
var _ lava.Response = (*rpcResponse)(nil)
@@ -11,11 +12,11 @@ var _ lava.Response = (*rpcResponse)(nil)
type rpcResponse struct {
stream grpc.ServerStream
header *lava.ResponseHeader
- dt interface{}
+ dt any
}
func (h *rpcResponse) Header() *lava.ResponseHeader { return h.header }
-func (h *rpcResponse) Payload() interface{} { return h.dt }
+func (h *rpcResponse) Payload() any { return h.dt }
func (h *rpcResponse) Stream() bool { return h.stream != nil }
var _ lava.Response = (*httpResponse)(nil)
@@ -25,5 +26,5 @@ type httpResponse struct {
}
func (h *httpResponse) Header() *lava.ResponseHeader { return &h.ctx.Response().Header }
-func (h *httpResponse) Payload() interface{} { return h.ctx.Response().Body() }
-func (h *httpResponse) Stream() bool { return false }
+func (h *httpResponse) Payload() any { return h.ctx.Response().Body() }
+func (h *httpResponse) Stream() bool { return h.ctx.Response().IsBodyStream() }
diff --git a/servers/grpcs/server.go b/servers/grpcs/server.go
index 7277fa711..2f3b1fdf9 100644
--- a/servers/grpcs/server.go
+++ b/servers/grpcs/server.go
@@ -2,117 +2,116 @@ package grpcs
import (
"context"
- "errors"
"fmt"
- "io"
"net"
- "net/http"
"net/url"
- "path/filepath"
"strings"
"github.com/fullstorydev/grpchan/inprocgrpc"
"github.com/gofiber/fiber/v2"
- "github.com/gofiber/fiber/v2/middleware/cors"
- "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/async"
- "github.com/pubgo/funk/config"
- "github.com/pubgo/funk/errors/errutil"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/proto/errorpb"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/stack"
- "github.com/pubgo/funk/vars"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/clients/grpcc"
- "github.com/pubgo/lava/clients/grpcc/grpcc_config"
- "github.com/pubgo/lava/core/debug"
- "github.com/pubgo/lava/core/lifecycle"
- "github.com/pubgo/lava/core/metrics"
- "github.com/pubgo/lava/core/signal"
- "github.com/pubgo/lava/internal/consts"
- "github.com/pubgo/lava/internal/logutil"
- "github.com/pubgo/lava/internal/middlewares/middleware_accesslog"
- "github.com/pubgo/lava/internal/middlewares/middleware_metric"
- "github.com/pubgo/lava/internal/middlewares/middleware_recovery"
- "github.com/pubgo/lava/internal/middlewares/middleware_service_info"
- "github.com/pubgo/lava/lava"
- "github.com/pubgo/lava/pkg/gateway"
- "github.com/pubgo/lava/pkg/httputil"
- "github.com/pubgo/lava/pkg/wsproxy"
- "github.com/rs/xid"
+ "github.com/pubgo/funk/v2/assert"
+ "github.com/pubgo/funk/v2/async"
+ "github.com/pubgo/funk/v2/buildinfo/version"
+ "github.com/pubgo/funk/v2/config"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/pubgo/funk/v2/vars"
"google.golang.org/grpc"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/grpclog"
- "google.golang.org/grpc/metadata"
- "google.golang.org/grpc/status"
- "google.golang.org/protobuf/encoding/protojson"
-)
-func New() lava.Service { return newService() }
+ "github.com/pubgo/lava/v2/clients/grpcc"
+ "github.com/pubgo/lava/v2/clients/grpcc/grpccconfig"
+ "github.com/pubgo/lava/v2/core/debug"
+ "github.com/pubgo/lava/v2/core/metrics"
+ "github.com/pubgo/lava/v2/core/supervisor"
+ "github.com/pubgo/lava/v2/internal/logutil"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_accesslog"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_metric"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_recovery"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_serviceinfo"
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/gateway"
+ "github.com/pubgo/lava/v2/pkg/httputil"
+ "github.com/pubgo/lava/v2/pkg/netutil"
+)
-func newService() *serviceImpl {
- return &serviceImpl{
- cc: new(inprocgrpc.Channel),
- }
+type Params struct {
+ GrpcRouters []lava.GrpcRouter
+ HttpRouters []lava.HttpRouter
+ GrpcHttpRouters []lava.GrpcHttpRouter
+ GrpcProxy []lava.GrpcProxy
+ DixMiddlewares []lava.Middleware
+ Metric metrics.Metric
+ Log log.Logger
+ Conf *Config
+ Gw []*gateway.Mux
}
-var _ lava.Service = (*serviceImpl)(nil)
+func New(params Params) supervisor.Service { return newService(params) }
+
+func newService(params Params) supervisor.Service {
+ s := &serviceImpl{cc: new(inprocgrpc.Channel)}
+ s.init(
+ params.GrpcRouters,
+ params.HttpRouters,
+ params.GrpcHttpRouters,
+ params.GrpcProxy,
+ params.DixMiddlewares,
+ params.Metric,
+ params.Log,
+ params.Conf,
+ params.Gw,
+ )
+
+ return supervisor.NewService("grpc-server", s.Serve)
+}
type serviceImpl struct {
- lc lifecycle.Getter
httpServer *fiber.App
grpcServer *grpc.Server
log log.Logger
cc *inprocgrpc.Channel
- initList []func()
conf *Config
}
-func (s *serviceImpl) Run() {
- defer s.stop()
- s.start()
- signal.Wait()
+func (s *serviceImpl) String() string {
+ return "grpc-server"
}
-func (s *serviceImpl) Start() { s.start() }
-func (s *serviceImpl) Stop() { s.stop() }
+func (s *serviceImpl) Serve(ctx context.Context) error {
+ defer s.stop(ctx)
+ err := s.start(ctx)
+ if err != nil {
+ return err
+ }
+
+ <-ctx.Done()
+ return nil
+}
-func (s *serviceImpl) DixInject(
+func (s *serviceImpl) init(
grpcRouters []lava.GrpcRouter,
httpRouters []lava.HttpRouter,
+ grpcHttpRouters []lava.GrpcHttpRouter,
grpcProxy []lava.GrpcProxy,
dixMiddlewares []lava.Middleware,
- getLifecycle lifecycle.Getter,
- lifecycle lifecycle.Lifecycle,
metric metrics.Metric,
log log.Logger,
conf *Config,
gw []*gateway.Mux,
) {
- s.conf = conf
- if conf.HttpPort == nil {
- conf.HttpPort = generic.Ptr(running.HttpPort)
- }
-
- if conf.GrpcPort == nil {
- conf.GrpcPort = generic.Ptr(running.GrpcPort)
- }
-
- if conf.BaseUrl == "" {
- conf.BaseUrl = "/" + version.Project()
- }
-
- s.lc = getLifecycle
+ cfg := httputil.DefaultCfg(&httputil.Config{
+ BaseUrl: conf.BaseUrl,
+ EnablePrintRouter: conf.EnablePrintRouter,
+ Http: conf.Http,
+ })
+ conf.BaseUrl = cfg.BaseUrl
+ conf.Http = cfg.Http
- conf = config.MergeR(defaultCfg(), conf).Unwrap()
- conf.BaseUrl = "/" + strings.Trim(conf.BaseUrl, "/")
+ s.conf = config.MergeR(defaultCfg(), conf).Unwrap()
globalMiddlewares := lava.Middlewares{
- middleware_service_info.New(),
+ middleware_serviceinfo.New(),
middleware_metric.New(metric),
middleware_accesslog.New(log),
middleware_recovery.New(),
@@ -122,195 +121,34 @@ func (s *serviceImpl) DixInject(
log = log.WithName("grpc-server")
s.log = log
- httpServer := fiber.New(fiber.Config{
- EnableIPValidation: true,
- EnablePrintRoutes: conf.EnablePrintRoutes,
- AppName: version.Project(),
- BodyLimit: 500 * 1024 * 1024,
- ErrorHandler: func(ctx *fiber.Ctx, err error) error {
- if err == nil {
- return nil
- }
-
- errPb := errutil.ParseError(err)
- if errPb == nil || errPb.Code.Code == 0 {
- return nil
- }
-
- errPb.Trace.Operation = ctx.Route().Path
- code := errutil.GrpcCodeToHTTP(codes.Code(errPb.Code.StatusCode))
- ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON)
- return ctx.Status(code).JSON(errPb.Code)
- },
- })
-
- if conf.EnableCors {
- httpServer.Use(cors.New(cors.Config{
- AllowOriginsFunc: func(origin string) bool {
- return true
- },
- AllowMethods: strings.Join([]string{
- fiber.MethodGet,
- fiber.MethodPost,
- fiber.MethodPut,
- fiber.MethodDelete,
- fiber.MethodPatch,
- fiber.MethodHead,
- fiber.MethodOptions,
- }, ","),
- //AllowHeaders: "",
- AllowCredentials: true,
- //ExposeHeaders: "",
- MaxAge: 0,
- }))
- }
-
- app := fiber.New()
- app.Group("/debug", httputil.StripPrefix(filepath.Join(conf.BaseUrl, "/debug"), debug.Handler))
-
- //app.Use(handlerHttpMiddle(globalMiddlewares))
- for _, h := range httpRouters {
- //srv := doc.WithService()
- //for _, an := range h.Annotation() {
- // switch a := an.(type) {
- // case *annotation.Openapi:
- // if a.ServiceName != "" {
- // srv.SetName(a.ServiceName)
- // }
- // }
- //}
-
- if h.Prefix() == "" {
- panic("http handler prefix is required")
- }
-
- g := app.Group(h.Prefix(), handlerHttpMiddle(append(globalMiddlewares, h.Middlewares()...)))
- h.Router(g)
+ httpServer := fiber.New(conf.Http.Build().Unwrap())
+ httpServer.Use(httputil.Cors())
- if m, ok := h.(lava.Close); ok {
- lifecycle.BeforeStop(m.Close)
- }
-
- if m, ok := h.(lava.Init); ok {
- s.initList = append(s.initList, m.Init)
- }
- }
-
- for _, handler := range grpcRouters {
- h, ok := handler.(lava.HttpRouter)
+ for _, h := range grpcRouters {
+ r, ok := h.(lava.HttpRouter)
if !ok {
continue
}
- if h.Prefix() == "" {
- panic("http handler prefix is required")
- }
-
- g := app.Group(h.Prefix(), handlerHttpMiddle(append(globalMiddlewares, h.Middlewares()...)))
- h.Router(g)
+ httpRouters = append(httpRouters, r)
+ }
- if m, ok := h.(lava.Close); ok {
- lifecycle.BeforeStop(m.Close)
+ for _, h := range grpcHttpRouters {
+ if r, ok := h.(lava.HttpRouter); ok {
+ httpRouters = append(httpRouters, r)
}
- if m, ok := h.(lava.Init); ok {
- s.initList = append(s.initList, m.Init)
+ if r, ok := h.(lava.GrpcRouter); ok {
+ grpcRouters = append(grpcRouters, r)
}
}
- httpServer.Mount(conf.BaseUrl, app)
+ httpApp := fiber.New()
+ for _, h := range httpRouters {
+ assert.If(h.Prefix() == "", "http router prefix required")
- grpcGateway := runtime.NewServeMux(
- runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
- Marshaler: &runtime.JSONPb{
- MarshalOptions: protojson.MarshalOptions{
- EmitUnpopulated: true,
- },
- UnmarshalOptions: protojson.UnmarshalOptions{
- DiscardUnknown: true,
- },
- },
- }),
- runtime.SetQueryParameterParser(new(DefaultQueryParser)),
- runtime.WithIncomingHeaderMatcher(func(s string) (string, bool) {
- return strings.ToLower(s), true
- }),
- runtime.WithOutgoingHeaderMatcher(func(s string) (string, bool) {
- return strings.ToUpper(s), true
- }),
- runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
- path, ok := runtime.HTTPPathPattern(ctx)
- if !ok {
- return nil
- }
- return metadata.Pairs("http_path", path, "http_method", request.Method, "http_url", request.URL.Path)
- }),
- runtime.WithErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, marshal runtime.Marshaler, w http.ResponseWriter, request *http.Request, err error) {
- md, ok := runtime.ServerMetadataFromContext(ctx)
- if ok && w != nil {
- for k, v := range md.HeaderMD {
- for i := range v {
- w.Header().Add(k, v[i])
- }
- }
-
- for k, v := range md.TrailerMD {
- for i := range v {
- w.Header().Add(k, v[i])
- }
- }
- }
-
- var pb *errorpb.ErrCode
- sts, ok := status.FromError(err)
- if !ok || sts == nil {
- w.Header().Set("Content-Type", "application/json")
- pb = &errorpb.ErrCode{
- Message: err.Error(),
- StatusCode: errorpb.Code_Internal,
- Code: int32(errorpb.Code_Internal),
- Name: "lava.grpc.status",
- }
- } else {
- w.Header().Set("Content-Type", marshal.ContentType(sts))
- if len(sts.Details()) > 0 {
- if code, ok := sts.Details()[0].(*errorpb.Error); ok {
- pb = code.Code
- }
- } else {
- pb = &errorpb.ErrCode{
- Message: sts.Message(),
- Code: int32(errorpb.Code(sts.Code())),
- StatusCode: errorpb.Code(sts.Code()),
- Name: "lava.grpc.status",
- Details: sts.Proto().Details,
- }
- }
- }
-
- const fallback = `{"code":13, "name":"lava.grpc.status", "status_code": 500, "message": "failed to marshal error message"}`
-
- // skip error
- if pb.StatusCode == errorpb.Code_OK {
- return
- }
-
- buf, mErr := marshal.Marshal(pb)
- if mErr != nil {
- grpclog.Infof("Failed to marshal error message %q: %v", pb, mErr)
- w.WriteHeader(http.StatusInternalServerError)
- if _, err := io.WriteString(w, fallback); err != nil {
- grpclog.Infof("Failed to write response: %v", err)
- }
- return
- }
-
- w.WriteHeader(runtime.HTTPStatusFromCode(codes.Code(pb.StatusCode)))
- if _, err := w.Write(buf); err != nil {
- grpclog.Infof("Failed to write response: %v", err)
- }
- }),
- )
+ h.Router(httpApp.Group(h.Prefix(), handlerHttpMiddle(append(globalMiddlewares, h.Middlewares()...))))
+ }
mux := gateway.NewMux()
if len(gw) > 0 {
@@ -320,52 +158,25 @@ func (s *serviceImpl) DixInject(
srvMidMap := make(map[string][]lava.Middleware)
for _, h := range grpcRouters {
desc := h.ServiceDesc()
- assert.If(desc == nil, "desc is nil")
+ assert.If(desc == nil, "service desc is nil")
srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], globalMiddlewares...)
srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...)
- if m, ok := h.(lava.Close); ok {
- lifecycle.BeforeStop(m.Close)
- }
-
- if m, ok := h.(lava.Initializer); ok {
- s.initList = append(s.initList, m.Initialize)
- }
-
- if m, ok := h.(lava.Init); ok {
- s.initList = append(s.initList, m.Init)
- }
-
mux.RegisterService(desc, h)
s.cc.RegisterService(desc, h)
- if m, ok := h.(lava.GrpcGatewayRouter); ok {
- assert.Exit(m.RegisterGateway(context.Background(), grpcGateway, s.cc))
- }
}
for _, h := range grpcProxy {
desc := h.ServiceDesc()
- assert.If(desc == nil, "desc is nil")
+ assert.If(desc == nil, "service desc is nil")
srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], globalMiddlewares...)
srvMidMap[desc.ServiceName] = append(srvMidMap[desc.ServiceName], h.Middlewares()...)
- if m, ok := h.(lava.Close); ok {
- lifecycle.BeforeStop(m.Close)
- }
-
- if m, ok := h.(lava.Initializer); ok {
- s.initList = append(s.initList, m.Initialize)
- }
-
- if m, ok := h.(lava.Init); ok {
- s.initList = append(s.initList, m.Init)
- }
-
cli := grpcc.New(
- &grpcc_config.Cfg{
- Service: &grpcc_config.ServiceCfg{
+ &grpccconfig.Cfg{
+ Service: &grpccconfig.ServiceCfg{
Name: h.Proxy().Name,
Addr: h.Proxy().Addr,
Scheme: h.Proxy().Resolver,
@@ -396,140 +207,93 @@ func (s *serviceImpl) DixInject(
grpcServer.RegisterService(h.ServiceDesc(), h)
}
- apiPrefix1 := assert.Must1(url.JoinPath(conf.BaseUrl, "gw"))
- s.log.Info().Str("path", apiPrefix1).Msg("service grpc gateway base path")
- httpServer.Group(apiPrefix1, httputil.StripPrefix(apiPrefix1, mux.Handler))
+ for _, h := range grpcHttpRouters {
+ grpcServer.RegisterService(h.ServiceDesc(), h)
+ }
+
+ for _, h := range grpcProxy {
+ grpcServer.RegisterService(h.ServiceDesc(), h)
+ }
+
+ grpcGatewayApiPrefix := assert.Must1(url.JoinPath(conf.BaseUrl, "api"))
+ s.log.Info().Msgf("service grpc gateway base path: %s", grpcGatewayApiPrefix)
+
for _, m := range mux.GetRouteMethods() {
log.Info().
Str("operation", m.Operation).
Any("rpc-meta", mux.GetOperation(m.Operation).Meta).
- Str("http-method", m.Method).
- Str("http-path", "/"+strings.Trim(apiPrefix1, "/")+m.Path).
Str("verb", m.Verb).
Any("path-vars", m.Vars).
Str("extras", fmt.Sprintf("%v", m.Extras)).
- Msg("grpc gateway router info")
+ Msgf("grpc gateway router info: %s %s", m.Method, "/"+strings.Trim(grpcGatewayApiPrefix, "/")+m.Path)
}
- apiPrefix := assert.Must1(url.JoinPath(conf.BaseUrl, "api"))
- s.log.Info().Str("path", apiPrefix).Msg("service grpc gateway base path")
- httpServer.Group(apiPrefix, httputil.HTTPHandler(http.StripPrefix(apiPrefix, wsproxy.WebsocketProxy(grpcGateway,
- wsproxy.WithPingPong(conf.EnablePingPong),
- wsproxy.WithTimeWait(conf.PingPongTime),
- wsproxy.WithReadLimit(int64(generic.FromPtr(conf.WsReadLimit))),
- ))))
+ httpServer.Mount("/debug", debug.App())
+ httpServer.Mount(conf.BaseUrl, httpApp)
+ httpServer.Group(grpcGatewayApiPrefix, httputil.StripPrefix(grpcGatewayApiPrefix, mux.Handler))
s.httpServer = httpServer
s.grpcServer = grpcServer
- vars.RegisterValue(fmt.Sprintf("%s-grpc-server-config-%s", version.Project(), xid.New()), &conf)
- // vars.RegisterValue(fmt.Sprintf("%s-grpc-server-router-%s", version.Project(), xid.New()), mux.App().Stack())
-}
-
-func (s *serviceImpl) start() {
- defer recovery.Exit()
-
- logutil.OkOrFailed(s.log, "running before service starts", func() error {
- defer recovery.Exit()
- for _, run := range s.lc.GetBeforeStarts() {
- s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler))
- run.Handler()
+ vars.Register(vars.UniqueName(version.Project(), "grpc-server-info"), func() any {
+ return map[string]any{
+ "config": conf,
+ "method": mux.GetRouteMethods(),
+ "desc": grpcServer.GetServiceInfo(),
+ "router": httpServer.Stack(),
}
- return nil
})
+}
- logutil.OkOrFailed(s.log, "init handler before service starts", func() error {
- defer recovery.Exit()
- for _, ii := range s.initList {
- s.log.Info().Msgf("init handler %s", stack.CallerWithFunc(ii))
- ii()
- }
- return nil
- })
+func (s *serviceImpl) start(ctx context.Context) (gErr error) {
+ defer recovery.Exit()
s.log.Info().
- Int("grpc-port", *s.conf.GrpcPort).
- Int("http-port", *s.conf.HttpPort).
+ Int("grpc-port", running.GrpcPort()).
+ Int("http-port", running.HttpPort()).
Msg("create network listener")
- grpcLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", *s.conf.GrpcPort)))
- httpLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", *s.conf.HttpPort)))
-
- logutil.OkOrFailed(s.log, "service starts", func() error {
- // 启动grpc服务
- async.GoDelay(func() error {
- s.log.Info().Msg("[grpc] Server Starting")
- logutil.LogOrErr(s.log, "[grpc] Server Stop", func() error {
- defer recovery.Exit()
- if err := s.grpcServer.Serve(grpcLn); err != nil &&
- !errors.Is(err, http.ErrServerClosed) &&
- !errors.Is(err, net.ErrClosed) {
- return err
- }
- return nil
- })
- return nil
- })
-
- // 启动grpc网关
- async.GoDelay(func() error {
- s.log.Info().Msg("[http] Server Starting")
- logutil.LogOrErr(s.log, "[http] Server Stop", func() error {
- defer recovery.Exit()
- if err := s.httpServer.Listener(httpLn); err != nil &&
- !errors.Is(err, http.ErrServerClosed) &&
- !errors.Is(err, net.ErrClosed) {
- return err
- }
- return nil
- })
+ grpcLn := assert.Exit1(net.Listen("tcp", fmt.Sprintf(":%d", running.GrpcPort())))
+ httpLn := assert.Exit1(net.Listen("tcp", fmt.Sprintf(":%d", running.HttpPort())))
+
+ async.GoDelay(func() error {
+ s.log.Info().Msg("grpc server starting")
+ defer recovery.DebugPrint()
+ err := s.grpcServer.Serve(grpcLn)
+ if netutil.IsErrServerClosed(err) {
return nil
- })
- return nil
+ }
+
+ return err
})
- logutil.OkOrFailed(s.log, "running after service starts", func() error {
- for _, run := range s.lc.GetAfterStarts() {
- logutil.LogOrErr(
- s.log,
- fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)),
- func() error { run.Handler(); return nil },
- )
+ // 启动grpc网关
+ async.GoDelay(func() error {
+ s.log.Info().Msg("http server starting")
+ defer recovery.DebugPrint()
+ err := s.httpServer.Listener(httpLn)
+ if netutil.IsErrServerClosed(err) {
+ return nil
}
- return nil
+
+ return err
})
+
+ return nil
}
-func (s *serviceImpl) stop() {
- defer recovery.Exit()
+func (s *serviceImpl) stop(ctx context.Context) {
+ defer recovery.DebugPrint()
- logutil.OkOrFailed(s.log, "running before service stops", func() error {
- for _, run := range s.lc.GetBeforeStops() {
- logutil.LogOrErr(
- s.log,
- fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)),
- func() error { run.Handler(); return nil },
- )
- }
- return nil
- })
-
- logutil.LogOrErr(s.log, "[grpc] Server GracefulStop", func() error {
+ logutil.LogOrErr(s.log, "grpc server graceful stop", func() error {
s.grpcServer.GracefulStop()
return nil
})
- logutil.LogOrErr(s.log, "[http] Server Shutdown", func() error {
- return s.httpServer.ShutdownWithTimeout(consts.DefaultTimeout)
- })
-
- logutil.OkOrFailed(s.log, "running after service stops", func() error {
- for _, run := range s.lc.GetAfterStops() {
- logutil.LogOrErr(
- s.log,
- fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)),
- func() error { run.Handler(); return nil },
- )
+ logutil.LogOrErr(s.log, "http server shutdown", func() error {
+ err := s.httpServer.ShutdownWithContext(ctx)
+ if netutil.IsErrServerClosed(err) {
+ return nil
}
- return nil
+ return err
})
}
diff --git a/servers/grpcs/util.go b/servers/grpcs/util.go
index 7e3a640eb..bbfea8353 100644
--- a/servers/grpcs/util.go
+++ b/servers/grpcs/util.go
@@ -1,12 +1,7 @@
package grpcs
import (
- "net/url"
"strings"
-
- "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
- "github.com/grpc-ecosystem/grpc-gateway/v2/utilities"
- "google.golang.org/protobuf/proto"
)
// serviceFromMethod returns the service
@@ -18,23 +13,3 @@ func serviceFromMethod(m string) string {
return strings.Split(strings.Trim(m, "/"), "/")[0]
}
-
-type DefaultQueryParser struct{}
-
-// Parse populates "values" into "msg".
-// A value is ignored if its key starts with one of the elements in "filter".
-func (*DefaultQueryParser) Parse(msg proto.Message, values url.Values, filter *utilities.DoubleArray) error {
- for key, v := range values {
- if len(v) == 0 {
- delete(values, key)
- continue
- }
-
- if len(v) == 1 && v[0] == "" {
- delete(values, key)
- continue
- }
- }
-
- return new(runtime.DefaultQueryParser).Parse(msg, values, filter)
-}
diff --git a/servers/https/config.go b/servers/https/config.go
index 324b2db71..1f562f2fb 100644
--- a/servers/https/config.go
+++ b/servers/https/config.go
@@ -1,25 +1,11 @@
package https
import (
- "github.com/pubgo/funk/version"
- "github.com/pubgo/lava/pkg/fiber_builder"
+ "github.com/pubgo/lava/v2/pkg/httputil"
)
-// DefaultMaxBodyBytes is the maximum allowed size of a request body in bytes.
-const DefaultMaxBodyBytes = 256 * 1024
+type Config = httputil.Config
-type Config struct {
- Http *fiber_builder.Config `yaml:"http"`
- Ws *fiber_builder.WsCfg `yaml:"ws"`
- EnablePrintRouter bool `yaml:"enable_print_router"`
- BaseUrl string `yaml:"base_url"`
-}
-
-func DefaultCfg() Config {
- return Config{
- Http: &fiber_builder.Config{},
- Ws: &fiber_builder.WsCfg{},
- EnablePrintRouter: true,
- BaseUrl: version.Project(),
- }
+type HttpServerConfigLoader struct {
+ HttpServer *Config `yaml:"http_server"`
}
diff --git a/servers/https/httprouter/router.go b/servers/https/httphandler/router.go
similarity index 69%
rename from servers/https/httprouter/router.go
rename to servers/https/httphandler/router.go
index 6fc458d1e..2effbd329 100644
--- a/servers/https/httprouter/router.go
+++ b/servers/https/httphandler/router.go
@@ -1,4 +1,4 @@
-package httprouter
+package httphandler
import (
"fmt"
@@ -12,20 +12,20 @@ type Handler[Req any, Rsp any] func(ctx *fiber.Ctx, req *Req) (rsp *Rsp, err err
var validate = validator.New()
-func WrapHandler[Req, Rsp any](handle func(ctx *fiber.Ctx, req *Req) (rsp *Rsp, err error)) func(ctx *fiber.Ctx) error {
+func WrapHandler[Req, Rsp any](handler Handler[Req, Rsp]) func(ctx *fiber.Ctx) error {
return func(ctx *fiber.Ctx) error {
var req Req
if err := ctx.ParamsParser(&req); err != nil {
- return fmt.Errorf("failed to parse params, err:%w", err)
+ return fmt.Errorf("failed to parse params, params:%v err:%w", ctx.AllParams(), err)
}
if err := ctx.QueryParser(&req); err != nil {
- return fmt.Errorf("failed to parse query, err:%w", err)
+ return fmt.Errorf("failed to parse query, query:%v err:%w", ctx.Queries(), err)
}
if err := ctx.ReqHeaderParser(&req); err != nil {
- return fmt.Errorf("failed to parse req header, err:%w", err)
+ return fmt.Errorf("failed to parse header, header:%q err:%w", ctx.GetReqHeaders(), err)
}
switch ctx.Method() {
@@ -39,7 +39,7 @@ func WrapHandler[Req, Rsp any](handle func(ctx *fiber.Ctx, req *Req) (rsp *Rsp,
return fmt.Errorf("failed to validate request, err:%w", err)
}
- rsp, err := handle(ctx, &req)
+ rsp, err := handler(ctx, &req)
if err != nil {
return err
}
diff --git a/servers/https/middleware.go b/servers/https/middleware.go
index 8759a21df..1e8b66b9d 100644
--- a/servers/https/middleware.go
+++ b/servers/https/middleware.go
@@ -2,26 +2,32 @@ package https
import (
"context"
- "reflect"
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/lava/lava"
+
+ "github.com/pubgo/lava/v2/lava"
)
-var parserTypes []fiber.ParserType
+func init() {
+ fiber.SetParserDecoder(fiber.ParserConfig{
+ IgnoreUnknownKeys: true,
+ ZeroEmpty: true,
+ })
+}
-func RegParser(customType interface{}, converter func(string) reflect.Value) {
- parserTypes = append(parserTypes, fiber.ParserType{
- Customtype: customType,
- Converter: converter,
+func RegParser(parsers []fiber.ParserType) {
+ fiber.SetParserDecoder(fiber.ParserConfig{
+ IgnoreUnknownKeys: true,
+ ZeroEmpty: true,
+ ParserType: parsers,
})
}
func handlerHttpMiddle(middlewares []lava.Middleware) func(fbCtx *fiber.Ctx) error {
h := func(ctx context.Context, req lava.Request) (lava.Response, error) {
- reqCtx := req.(*httpRequest)
- reqCtx.ctx.SetUserContext(ctx)
- return &httpResponse{ctx: reqCtx.ctx}, reqCtx.ctx.Next()
+ reqCtx := req.(*httpRequest).ctx
+ reqCtx.SetUserContext(ctx)
+ return &httpResponse{ctx: reqCtx}, reqCtx.Next()
}
h = lava.Chain(middlewares...).Middleware(h)
diff --git a/servers/https/request.go b/servers/https/request.go
index b1c5042ba..e1489db61 100644
--- a/servers/https/request.go
+++ b/servers/https/request.go
@@ -1,9 +1,11 @@
package https
import (
+ "fmt"
+
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/lava"
)
var _ lava.Request = (*httpRequest)(nil)
@@ -12,16 +14,14 @@ type httpRequest struct {
ctx *fiber.Ctx
}
-func (r *httpRequest) Kind() string { return "http" }
-func (r *httpRequest) Operation() string { return r.ctx.Route().Path }
+func (r *httpRequest) Kind() string { return lava.RequestKindHttp }
+func (r *httpRequest) Operation() string {
+ return fmt.Sprintf("%s %s", r.ctx.Method(), r.ctx.Route().Path)
+}
func (r *httpRequest) Client() bool { return false }
func (r *httpRequest) Header() *lava.RequestHeader { return &r.ctx.Request().Header }
-func (r *httpRequest) Payload() interface{} { return r.ctx.Body() }
-
-func (r *httpRequest) ContentType() string {
- return string(r.ctx.Request().Header.ContentType())
-}
-
-func (r *httpRequest) Service() string { return r.ctx.OriginalURL() }
-func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) }
-func (r *httpRequest) Stream() bool { return false }
+func (r *httpRequest) Payload() any { return r.ctx.Body() }
+func (r *httpRequest) ContentType() string { return string(r.ctx.Request().Header.ContentType()) }
+func (r *httpRequest) Service() string { return r.ctx.Route().Path }
+func (r *httpRequest) Endpoint() string { return string(r.ctx.Request().RequestURI()) }
+func (r *httpRequest) Stream() bool { return r.ctx.Request().IsBodyStream() }
diff --git a/servers/https/response.go b/servers/https/response.go
index a32edee0d..eac0a6ff3 100644
--- a/servers/https/response.go
+++ b/servers/https/response.go
@@ -3,7 +3,7 @@ package https
import (
"github.com/gofiber/fiber/v2"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/lava/v2/lava"
)
var _ lava.Response = (*httpResponse)(nil)
@@ -13,5 +13,5 @@ type httpResponse struct {
}
func (h *httpResponse) Header() *lava.ResponseHeader { return &h.ctx.Response().Header }
-func (h *httpResponse) Payload() interface{} { return h.ctx.Response().Body() }
+func (h *httpResponse) Payload() any { return h.ctx.Response().Body() }
func (h *httpResponse) Stream() bool { return false }
diff --git a/servers/https/server.go b/servers/https/server.go
index e9eaaeab2..1adc2899a 100644
--- a/servers/https/server.go
+++ b/servers/https/server.go
@@ -1,45 +1,47 @@
package https
import (
- "errors"
+ "context"
"fmt"
- "net"
- "net/http"
- "strings"
- "time"
"github.com/gofiber/fiber/v2"
- "github.com/gofiber/fiber/v2/middleware/cors"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/async"
- "github.com/pubgo/funk/errors/errutil"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/stack"
- "github.com/pubgo/funk/version"
- "github.com/pubgo/opendoc/opendoc"
- "github.com/valyala/fasthttp"
- "google.golang.org/grpc/codes"
-
- "github.com/pubgo/lava/core/debug"
- "github.com/pubgo/lava/core/lifecycle"
- "github.com/pubgo/lava/core/metrics"
- "github.com/pubgo/lava/core/signal"
- "github.com/pubgo/lava/internal/logutil"
- "github.com/pubgo/lava/internal/middlewares/middleware_accesslog"
- "github.com/pubgo/lava/internal/middlewares/middleware_metric"
- "github.com/pubgo/lava/internal/middlewares/middleware_recovery"
- "github.com/pubgo/lava/lava"
+ "github.com/pubgo/funk/v2/async"
+ "github.com/pubgo/funk/v2/log"
+ "github.com/pubgo/funk/v2/recovery"
+ "github.com/pubgo/funk/v2/running"
+ "github.com/pubgo/funk/v2/vars"
+ "github.com/samber/lo"
+
+ "github.com/pubgo/lava/v2/core/debug"
+ "github.com/pubgo/lava/v2/core/lifecycle"
+ "github.com/pubgo/lava/v2/core/metrics"
+ "github.com/pubgo/lava/v2/core/supervisor"
+ "github.com/pubgo/lava/v2/internal/logutil"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_accesslog"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_metric"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_recovery"
+ "github.com/pubgo/lava/v2/internal/middlewares/middleware_serviceinfo"
+ "github.com/pubgo/lava/v2/lava"
+ "github.com/pubgo/lava/v2/pkg/httputil"
+ "github.com/pubgo/lava/v2/pkg/netutil"
)
-func New() lava.Service { return newService() }
-
-func newService() *serviceImpl {
- return &serviceImpl{}
+type Params struct {
+ Handlers []lava.HttpRouter
+ Middlewares []lava.Middleware
+ M metrics.Metric
+ Log log.Logger
+ Cfg *Config
}
-var _ lava.Service = (*serviceImpl)(nil)
+func New(params Params) supervisor.Service { return newService(params) }
+
+func newService(params Params) supervisor.Service {
+ s := &serviceImpl{}
+ s.init(params)
+
+ return supervisor.NewService(s.String(), s.Serve)
+}
type serviceImpl struct {
lc lifecycle.Getter
@@ -47,183 +49,67 @@ type serviceImpl struct {
log log.Logger
}
-func (s *serviceImpl) Run() {
- defer s.stop()
- s.start()
- signal.Wait()
-
- fasthttp.AcquireArgs()
+func (s *serviceImpl) String() string { return "http-server" }
+func (s *serviceImpl) Serve(ctx context.Context) error {
+ defer s.stop(ctx)
+ s.start(ctx)
+ <-ctx.Done()
+ return nil
}
-func (s *serviceImpl) Start() { s.start() }
-func (s *serviceImpl) Stop() { s.stop() }
-
-func (s *serviceImpl) DixInject(
- handlers []lava.HttpRouter,
- middlewares []lava.Middleware,
- getLifecycle lifecycle.Getter,
- lifecycle lifecycle.Lifecycle,
- m metrics.Metric,
- log log.Logger,
- cfg *Config,
- docs []*opendoc.Swagger,
-) {
- if cfg.BaseUrl == "" {
- cfg.BaseUrl = "/" + version.Project()
- }
-
- fiber.SetParserDecoder(fiber.ParserConfig{
- IgnoreUnknownKeys: true,
- ZeroEmpty: true,
- ParserType: parserTypes,
- })
+func (s *serviceImpl) init(params Params) {
+ cfg := lo.ToPtr(httputil.DefaultCfg(params.Cfg))
- log = log.WithName("http-server")
-
- s.lc = getLifecycle
- s.log = log
-
- s.httpServer = fiber.New(fiber.Config{
- EnableIPValidation: true,
- ETag: true,
- ErrorHandler: func(ctx *fiber.Ctx, err error) error {
- if err == nil {
- return nil
- }
-
- errPb := errutil.ParseError(err)
- if errPb == nil || errPb.Code.Code == 0 {
- return nil
- }
-
- errPb.Trace.Operation = ctx.Route().Path
- code := errutil.GrpcCodeToHTTP(codes.Code(errPb.Code.Code))
- ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON)
- return ctx.Status(code).JSON(errPb)
- },
- })
-
- app := fiber.New()
- app.Use(cors.New(cors.Config{
- AllowOriginsFunc: func(origin string) bool {
- return true
- },
- AllowOrigins: "*",
- AllowMethods: strings.Join([]string{
- fiber.MethodGet,
- fiber.MethodPost,
- fiber.MethodPut,
- fiber.MethodDelete,
- fiber.MethodPatch,
- fiber.MethodHead,
- fiber.MethodOptions,
- }, ","),
- AllowHeaders: "",
- AllowCredentials: true,
- ExposeHeaders: "",
- MaxAge: 0,
- }))
+ s.log = params.Log.WithName(s.String())
+ s.httpServer = fiber.New(cfg.Http.Build().Unwrap())
+ s.httpServer.Use(httputil.Cors())
+ s.httpServer.Mount("/debug", debug.App())
defaultMiddlewares := []lava.Middleware{
- middleware_metric.New(m),
- middleware_accesslog.New(log),
+ middleware_serviceinfo.New(),
+ middleware_metric.New(params.M),
+ middleware_accesslog.New(s.log),
middleware_recovery.New(),
}
- app.Use(handlerHttpMiddle(append(defaultMiddlewares, middlewares...)))
-
- for _, h := range handlers {
- g := app.Group("", handlerHttpMiddle(h.Middlewares()))
+ middlewares := append(defaultMiddlewares, params.Middlewares...)
- //for _, an := range h.Annotation() {
- // switch a := an.(type) {
- // case *annotation.Openapi:
- // if a.ServiceName != "" {
- // srv.SetName(a.ServiceName)
- // }
- // }
- //}
-
- h.Router(g)
-
- if m, ok := h.(lava.Close); ok {
- lifecycle.BeforeStop(m.Close)
- }
+ for _, h := range params.Handlers {
+ h.Router(s.httpServer.Group(h.Prefix(), handlerHttpMiddle(append(middlewares, h.Middlewares()...))))
}
- s.httpServer.Mount("/debug", debug.App())
- s.httpServer.Mount(cfg.BaseUrl, app)
-
- // 网关初始化
- if cfg.EnablePrintRouter {
- for _, stacks := range s.httpServer.Stack() {
- for _, route := range stacks {
- s.log.Info().
- Str("name", route.Name).
- Str("path", route.Path).
- Str("method", route.Method).
- Msg("service route")
- }
+ vars.Register(vars.UniqueName(running.Project(), "http_server_info"), func() any {
+ return map[string]any{
+ "config": cfg,
+ "router": s.httpServer.Stack(),
}
- }
+ })
}
-func (s *serviceImpl) start() {
- logutil.OkOrFailed(s.log, "service before-start", func() error {
+func (s *serviceImpl) start(ctx context.Context) {
+ defer recovery.Exit()
+
+ addr := fmt.Sprintf(":%d", running.HttpPort())
+ async.GoDelay(func() error {
defer recovery.Exit()
- for _, run := range s.lc.GetBeforeStarts() {
- s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler))
- run.Handler()
- }
- return nil
- })
- httpLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", running.HttpPort)))
-
- logutil.OkOrFailed(s.log, "service start", func() error {
- async.GoDelay(func() error {
- s.log.Info().Msg("[http-server] Server Starting")
- logutil.LogOrErr(s.log, "[http-server] Server Stop", func() error {
- defer recovery.Exit()
- if err := s.httpServer.Listener(httpLn); err != nil &&
- !errors.Is(err, http.ErrServerClosed) &&
- !errors.Is(err, net.ErrClosed) {
- return err
- }
- return nil
- })
+ s.log.Info().Msg("http server starting")
+ err := s.httpServer.Listen(addr)
+ if netutil.IsErrServerClosed(err) {
return nil
- })
- return nil
- })
-
- logutil.OkOrFailed(s.log, "service after-start", func() error {
- defer recovery.Exit()
- for _, run := range s.lc.GetAfterStarts() {
- s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler))
- run.Handler()
}
- return nil
+
+ return err
})
}
-func (s *serviceImpl) stop() {
- logutil.OkOrFailed(s.log, "service before-stop", func() error {
- for _, run := range s.lc.GetBeforeStops() {
- s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler))
- run.Handler()
+func (s *serviceImpl) stop(ctx context.Context) {
+ defer recovery.DebugPrint()
+ logutil.LogOrErr(s.log, "http server shutdown", func() error {
+ err := s.httpServer.ShutdownWithContext(ctx)
+ if netutil.IsErrServerClosed(err) {
+ return nil
}
- return nil
- })
- logutil.LogOrErr(s.log, "[http-server] Shutdown", func() error {
- return s.httpServer.ShutdownWithTimeout(time.Second * 5)
- })
-
- logutil.OkOrFailed(s.log, "service after-stop", func() error {
- for _, run := range s.lc.GetAfterStops() {
- s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler))
- run.Handler()
- }
- return nil
+ return err
})
}
diff --git a/servers/tasks/config.go b/servers/tasks/config.go
deleted file mode 100644
index 27cc8bec1..000000000
--- a/servers/tasks/config.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package tasks
-
-type Config struct {
- HttpPort *int `yaml:"http_port"`
-}
diff --git a/servers/tasks/server.go b/servers/tasks/server.go
deleted file mode 100644
index 587f010ad..000000000
--- a/servers/tasks/server.go
+++ /dev/null
@@ -1,164 +0,0 @@
-package tasks
-
-import (
- "fmt"
- "net"
- "net/http"
- "time"
-
- "github.com/gofiber/fiber/v2"
- "github.com/pubgo/funk/assert"
- "github.com/pubgo/funk/async"
- "github.com/pubgo/funk/errors"
- "github.com/pubgo/funk/errors/errutil"
- "github.com/pubgo/funk/generic"
- "github.com/pubgo/funk/log"
- "github.com/pubgo/funk/recovery"
- "github.com/pubgo/funk/running"
- "github.com/pubgo/funk/stack"
- "github.com/pubgo/lava/core/debug"
- "github.com/pubgo/lava/core/lifecycle"
- "github.com/pubgo/lava/core/signal"
- "github.com/pubgo/lava/internal/logutil"
- "github.com/pubgo/lava/lava"
- "google.golang.org/grpc/codes"
-)
-
-func New(srv lava.Service) *Server {
- assert.If(srv == nil, "service is nil")
-
- return &Server{srv: srv}
-}
-
-type Server struct {
- srv lava.Service
- log log.Logger
- lc lifecycle.Getter
- httpServer *fiber.App
- conf *Config
-}
-
-func (s *Server) Run() {
- defer s.stop()
- s.start()
- signal.Wait()
-}
-
-func (s *Server) DixInject(
- getLifecycle lifecycle.Getter,
- log log.Logger,
- conf []*Config,
-) {
- s.lc = getLifecycle
- s.log = log.WithName("task-server")
-
- if len(conf) > 0 {
- s.conf = conf[0]
- } else {
- s.conf = &Config{HttpPort: generic.Ptr(running.HttpPort)}
- }
-
- s.httpServer = fiber.New(fiber.Config{
- EnableIPValidation: true,
- ETag: true,
- ErrorHandler: func(ctx *fiber.Ctx, err error) error {
- if err == nil {
- return nil
- }
-
- errPb := errutil.ParseError(err)
- if errPb == nil || errPb.Code.Code == 0 {
- return nil
- }
- errPb.Trace.Operation = ctx.Route().Path
- code := errutil.GrpcCodeToHTTP(codes.Code(errPb.Code.Code))
- ctx.Set(fiber.HeaderContentType, fiber.MIMEApplicationJSON)
- return ctx.Status(code).JSON(errPb)
- },
- })
-
- s.httpServer.Mount("/debug", debug.App())
-}
-
-func (s *Server) start() {
- defer recovery.Exit()
-
- logutil.OkOrFailed(s.log, "running before service starts", func() error {
- defer recovery.Exit()
- for _, run := range s.lc.GetBeforeStarts() {
- s.log.Info().Msgf("running %s", stack.CallerWithFunc(run.Handler))
- run.Handler()
- }
- return nil
- })
-
- httpLn := assert.Must1(net.Listen("tcp", fmt.Sprintf(":%d", generic.DePtr(s.conf.HttpPort))))
-
- logutil.OkOrFailed(s.log, "service starts", func() error {
- async.GoDelay(func() error {
- s.log.Info().Msg("[http-debug-server] Server Starting")
- logutil.LogOrErr(s.log, "[http-debug-server] Server Stop", func() error {
- defer recovery.Exit()
- if err := s.httpServer.Listener(httpLn); err != nil &&
- !errors.Is(err, http.ErrServerClosed) &&
- !errors.Is(err, net.ErrClosed) {
- return err
- }
- return nil
- })
- return nil
- })
-
- async.GoSafe(func() error {
- s.srv.Start()
- return nil
- })
- return nil
- })
-
- logutil.OkOrFailed(s.log, "running after service starts", func() error {
- for _, run := range s.lc.GetAfterStarts() {
- logutil.LogOrErr(
- s.log,
- fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)),
- func() error { run.Handler(); return nil },
- )
- }
- return nil
- })
-}
-
-func (s *Server) stop() {
- defer recovery.Exit()
-
- logutil.OkOrFailed(s.log, "running before service stops", func() error {
- for _, run := range s.lc.GetBeforeStops() {
- logutil.LogOrErr(
- s.log,
- fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)),
- func() error { run.Handler(); return nil },
- )
- }
- return nil
- })
-
- logutil.LogOrErr(s.log, "[http-debug-server] Shutdown", func() error {
- return s.httpServer.ShutdownWithTimeout(time.Second * 5)
- })
-
- logutil.LogOrErr(s.log, "[task] Server Stop", func() error {
- s.srv.Stop()
- return nil
- })
-
- logutil.OkOrFailed(s.log, "running after service stops", func() error {
- for _, run := range s.lc.GetAfterStops() {
- logutil.LogOrErr(
- s.log,
- fmt.Sprintf("running %s", stack.CallerWithFunc(run.Handler)),
- func() error { run.Handler(); return nil },
- )
- }
- return nil
- })
-}
diff --git a/servers/wss/_docs.go b/servers/wss/_docs.go
deleted file mode 100644
index 01fe4a6df..000000000
--- a/servers/wss/_docs.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package wss
-
-// https://github.com/gofiber/contrib/tree/main/websocket
-// https://github.com/klauspost/compress
-// https://github.com/andybalholm/brotli
-// https://github.com/philhofer/fwd
-// https://github.com/fasthttp/websocket/tree/master/_examples
-// https://github.com/fasthttp/websocket/blob/master/_examples/filewatch/main.go
-// https://github.com/tinylib/msgp
-// https://github.com/gofiber/contrib/tree/main/socketio
diff --git a/servers/wss/aaa.go b/servers/wss/aaa.go
deleted file mode 100644
index ddf3048b8..000000000
--- a/servers/wss/aaa.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package wss
-
-import (
- "context"
-
- "github.com/fasthttp/websocket"
-)
-
-// Connection for Websocket Server 2.0
-type Connection interface {
- ID() string
- Node() string
- AuthIdentity() string
- Start(ctx context.Context, readMsgHandler func(msgType int, bytes []byte)) error
- Write(msg []byte) error
- Close() error
-}
-
-// Factory for Connection.
-type Factory interface {
- // NewConnection for WS Server 2.0.
- NewConnection(args ConstructorArgs, conn *websocket.Conn, opts ...Option) Connection
-}
diff --git a/servers/wss/config.go b/servers/wss/config.go
deleted file mode 100644
index c4004307c..000000000
--- a/servers/wss/config.go
+++ /dev/null
@@ -1 +0,0 @@
-package wss
diff --git a/servers/wss/factory.go b/servers/wss/factory.go
deleted file mode 100644
index f6541ddc5..000000000
--- a/servers/wss/factory.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package wss
-
-import "github.com/fasthttp/websocket"
-
-// NewFactory for WSConnection. Saves and passes appropriate option.
-func NewFactory(opts ...Option) Factory {
- return &factory{
- savedOptions: opts,
- }
-}
-
-type factory struct {
- savedOptions []Option
-}
-
-func (f factory) NewConnection(args ConstructorArgs, conn *websocket.Conn, opts ...Option) Connection {
- combinedOptions := append(f.savedOptions, opts...)
- return New(args, conn, combinedOptions...)
-}
diff --git a/servers/wss/option.go b/servers/wss/option.go
deleted file mode 100644
index bac2b8006..000000000
--- a/servers/wss/option.go
+++ /dev/null
@@ -1,63 +0,0 @@
-package wss
-
-import (
- "time"
-
- "github.com/pubgo/funk/log"
-)
-
-// configuration of WebsocketConnection.
-type configuration struct {
- WriteDeadline time.Duration
- ReadDeadline *time.Duration
- PingInterval time.Duration
- MaxInactiveDuration time.Duration
-}
-
-func defaultConfiguration() *configuration {
- return &configuration{
- WriteDeadline: 3 * time.Second,
- ReadDeadline: nil,
- PingInterval: 30 * time.Second,
- MaxInactiveDuration: 1 * time.Minute,
- }
-}
-
-func (c *configuration) ensureValidated() {
- if c.MaxInactiveDuration <= 2*c.PingInterval {
- log.Warn().Msgf("websocket max_inactive_duration <= 2 * ping_interval, max_inactive_duration change to 3 * ping_interval")
- c.MaxInactiveDuration = 3 * c.PingInterval
- }
-}
-
-// Option is the function signature that applies configurable option for UILoadWebsocket.
-type Option func(*configuration)
-
-// ReadDeadline for how long it should wait until reading a message. Default infinite.
-func ReadDeadline(d time.Duration) Option {
- return func(c *configuration) {
- c.ReadDeadline = &d
- }
-}
-
-// WriteDeadline for how long it should try before giving up writing a message. Default 3s.
-func WriteDeadline(d time.Duration) Option {
- return func(c *configuration) {
- c.WriteDeadline = d
- }
-}
-
-// MaxInactiveDuration means the max time a connection can be 'quiet' before server will deem it as inactive and dead.
-// Idea is that pongs and client-based pings would suffice. Default 1m.
-func MaxInactiveDuration(d time.Duration) Option {
- return func(c *configuration) {
- c.MaxInactiveDuration = d
- }
-}
-
-// PingInterval sets frequency to write ping messages. Default 30s.
-func PingInterval(d time.Duration) Option {
- return func(c *configuration) {
- c.PingInterval = d
- }
-}
diff --git a/servers/wss/server.go b/servers/wss/server.go
deleted file mode 100644
index 405ba9927..000000000
--- a/servers/wss/server.go
+++ /dev/null
@@ -1,53 +0,0 @@
-package wss
-
-import (
- "log"
-
- "github.com/gofiber/fiber/v2"
- "github.com/gofiber/websocket/v2"
-)
-
-func init() {
- app := fiber.New()
-
- app.Use("/ws", func(c *fiber.Ctx) error {
- // IsWebSocketUpgrade returns true if the client
- // requested upgrade to the WebSocket protocol.
- if websocket.IsWebSocketUpgrade(c) {
- c.Locals("allowed", true)
- return c.Next()
- }
- return fiber.ErrUpgradeRequired
- })
-
- app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) {
- // c.Locals is added to the *websocket.Conn
- log.Println(c.Locals("allowed")) // true
- log.Println(c.Params("id")) // 123
- log.Println(c.Query("v")) // 1.0
- log.Println(c.Cookies("session")) // ""
-
- // websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index
- var (
- mt int
- msg []byte
- err error
- )
- for {
- if mt, msg, err = c.ReadMessage(); err != nil {
- log.Println("read:", err)
- break
- }
- log.Printf("recv: %s", msg)
-
- if err = c.WriteMessage(mt, msg); err != nil {
- log.Println("write:", err)
- break
- }
- }
- }))
-
- log.Fatal(app.Listen(":3000"))
- // Access the websocket server: ws://localhost:3000/ws/123?v=1.0
- // https://www.websocket.org/echo.html
-}
diff --git a/servers/wss/wsconnection.go b/servers/wss/wsconnection.go
deleted file mode 100644
index 58927c3f4..000000000
--- a/servers/wss/wsconnection.go
+++ /dev/null
@@ -1,204 +0,0 @@
-package wss
-
-import (
- "context"
- "errors"
- "net"
- "strings"
- "sync"
- "time"
-
- "github.com/fasthttp/websocket"
- "github.com/pubgo/funk/log"
-)
-
-// ConstructorArgs for WebsocketConnection
-type ConstructorArgs struct {
- ID string
- Node string
- AuthIdentity string
-}
-
-// New will return a new Websocket connection that would bridge to a message queue. For now, underlying connection is stemmed from gorilla.websocket
-func New(args ConstructorArgs, conn *websocket.Conn, opts ...Option) Connection {
- cfg := defaultConfiguration()
- for _, applyOption := range opts {
- applyOption(cfg)
- }
- cfg.ensureValidated()
-
- return &connection{
- ConstructorArgs: args,
- Conn: conn,
- configuration: cfg,
- closeLock: sync.Mutex{},
- writeLock: sync.Mutex{},
- isClosed: false,
- stopConnection: func() {},
- }
-}
-
-type connection struct {
- ConstructorArgs
- *websocket.Conn
- *configuration
- closeLock sync.Mutex
- writeLock sync.Mutex
- isClosed bool
- stopConnection func()
- log log.Logger
-}
-
-func (c *connection) Write(msg []byte) error {
- c.writeLock.Lock()
- defer c.writeLock.Unlock()
- err := c.Conn.SetWriteDeadline(time.Now().Add(c.WriteDeadline))
- if err != nil {
- return err
- }
- return c.Conn.WriteMessage(websocket.TextMessage, msg)
-}
-
-func (c *connection) ID() string {
- return c.ConstructorArgs.ID
-}
-
-func (c *connection) Node() string {
- return c.ConstructorArgs.Node
-}
-
-func (c *connection) AuthIdentity() string {
- return c.ConstructorArgs.AuthIdentity
-}
-
-func (c *connection) Close() error {
- var err error
- c.closeLock.Lock()
- defer func() {
- if err == nil && !c.isClosed {
- c.isClosed = true
- }
- c.closeLock.Unlock()
- }()
- if c.isClosed {
- return nil
- }
- c.stopConnection()
- return c.Conn.Close()
-}
-
-func (c *connection) Start(ctx context.Context, readMsgHandler func(int, []byte)) error {
- ctxWithCancel, cancel := context.WithCancel(ctx)
- c.stopConnection = cancel
- lastMsgCh := make(chan struct{}, 1)
- go c.startMonitorForZombieConns(ctxWithCancel, lastMsgCh)
- c.Conn.SetPongHandler(func(_ string) error {
- c.log.Debug().Msgf("received pong for conn %v", c.ID())
- c.notifyLatestMessage(lastMsgCh)
- return nil
- })
- c.Conn.SetPingHandler(func(message string) error {
- c.log.Debug().Msgf("received ping for conn %v", c.ID())
- c.notifyLatestMessage(lastMsgCh)
- err := c.WriteControl(websocket.PongMessage, []byte(message), time.Now().Add(c.WriteDeadline))
- if errors.Is(err, websocket.ErrCloseSent) {
- return nil
- }
-
- var e net.Error
- if errors.As(err, &e) && e.Timeout() {
- return nil
- }
- return err
- })
-
- for {
- select {
- case <-ctxWithCancel.Done():
- return nil
- default:
- }
- var readDeadline time.Time
- if c.ReadDeadline != nil {
- readDeadline = time.Now().Add(*c.ReadDeadline)
- }
- err := c.Conn.SetReadDeadline(readDeadline)
- if err != nil {
- return err
- }
- msgType, bytes, err := c.Conn.ReadMessage()
- if err != nil {
- if websocket.IsCloseError(err, websocket.CloseNormalClosure) {
- c.log.Err(err).Msgf("failed to read msg for %v", c.ID())
- return nil
- }
- if websocket.IsUnexpectedCloseError(err) || isClosedConnectionReadErr(err) {
- c.log.Err(err).Msgf("failed to read msg for %v", c.ID())
- return nil
- }
- c.log.Err(err).Msgf("failed to read msg for %v", c.ID())
- return nil
- }
- c.notifyLatestMessage(lastMsgCh)
- readMsgHandler(msgType, bytes)
- }
-}
-
-func (c *connection) startMonitorForZombieConns(ctx context.Context, anyMsgReceived <-chan struct{}) {
- inactiveCheckTicker := time.NewTicker(c.MaxInactiveDuration)
- defer inactiveCheckTicker.Stop()
- lastMsgReceived := time.Now()
-
- pingTicker := time.NewTicker(c.PingInterval)
- defer pingTicker.Stop()
-
- for {
- select {
- case <-ctx.Done():
- return
- case <-pingTicker.C:
- timeSinceLastMsg := time.Since(lastMsgReceived)
- if timeSinceLastMsg >= c.PingInterval {
- err := c.writePing([]byte(""))
- if err != nil {
- c.log.Err(err).Msgf("failed to write ping msg for conn %v", c.ID())
- }
- } else {
- c.log.Debug().Msgf("conn %v has received msg before send next ping", c.ID())
- }
- continue
- case <-inactiveCheckTicker.C:
- timeSinceLastMsg := time.Since(lastMsgReceived)
- if timeSinceLastMsg > c.MaxInactiveDuration {
- c.log.Warn().Msgf("killing connection %v because haven't received any msg [ping, pong, application] in %v seconds",
- c.ID(), timeSinceLastMsg.Seconds())
- if err := c.Close(); err != nil {
- c.log.Err(err).Msgf("failed to properly close %v", c.ID())
- }
- return
- }
- continue
-
- case <-anyMsgReceived:
- lastMsgReceived = time.Now()
- pingTicker.Reset(c.PingInterval)
- continue
- }
- }
-}
-
-func (c *connection) writePing(msg []byte) error {
- return c.Conn.WriteControl(websocket.PingMessage, msg, time.Now().Add(c.WriteDeadline))
-}
-
-func (c *connection) notifyLatestMessage(ch chan<- struct{}) {
- select {
- case ch <- struct{}{}:
- default:
- c.log.Warn().Msgf("notify latest msg channel blocked for connection %v", c.ID())
- }
-}
-
-func isClosedConnectionReadErr(err error) bool {
- return strings.Contains(err.Error(), "use of closed network connection")
-}
diff --git a/taskfile.yml b/taskfile.yml
new file mode 100644
index 000000000..3ad168217
--- /dev/null
+++ b/taskfile.yml
@@ -0,0 +1,71 @@
+# https://taskfile.dev
+
+version: '3'
+
+dotenv: [ '.env' ]
+vars:
+ Project: "lava"
+ Base: "github.com/pubgo/funk/v2"
+ VERSION:
+ sh: "git tag --sort=committerdate | tail -n 1"
+ BUILD_TIME:
+ sh: "date -u '+%Y-%m-%d %I:%M:%S %Z'"
+ GIT_COMMIT:
+ sh: "git rev-parse --short=8 HEAD"
+ BRANCH_NAME:
+ sh: "git branch ch --show-current"
+ GIT_VERSION:
+ sh: "git describe --always --abbrev=7 --dirty"
+
+includes:
+ scheduler: ./internal/examples/scheduler
+
+tasks:
+ default:
+ desc: "default task"
+ cmds:
+ - task -a
+
+ info:
+ cmds:
+ - echo "Project {{.Project}}"
+ - echo "VERSION {{.VERSION}}"
+ - echo "GIT_VERSION {{.GIT_VERSION}}"
+ silent: true
+
+ tools:
+ cmds:
+ - go install github.com/cortesi/modd/cmd/modd@latest
+ - go install mvdan.cc/sh/v3/cmd/shfmt@latest
+ - go install mvdan.cc/sh/v3/cmd/gosh@latest
+ - go install github.com/DarthSim/overmind/v2@latest
+ - go install github.com/a-h/templ/cmd/templ@latest
+ - go install golang.org/x/tools/cmd/goimports@latest
+
+ generate:
+ cmds:
+ - go generate ./internal/...
+
+ proto:fmt:
+ cmds:
+ - protobuild format
+
+ proto:gen:
+ cmds:
+ - protobuild vendor
+ - protobuild gen
+
+ proto:lint:
+ cmds:
+ - protobuild lint
+
+ vet:
+ desc: "go vet"
+ cmds:
+ - go vet ./...
+ test:
+ cmds:
+ - go test -short -race -v ./... -cover
+ lint:
+ cmds:
+ - golangci-lint run --verbose ./...
diff --git a/tools/_doc.go b/tools/_doc.go
index 596c8c265..16c5d3e26 100644
--- a/tools/_doc.go
+++ b/tools/_doc.go
@@ -29,3 +29,8 @@ package tools
// CPU feature identification for Go
// https://github.com/klauspost/cpuid
+
+// Run command periodically and expose latest STDOUT as HTTP endpoint
+// https://github.com/nikolaydubina/watchhttp/tree/master
+// https://github.com/nikolaydubina/htmljson
+// https://github.com/nikolaydubina/treemap/tree/main
diff --git a/tools/fileserver/main.go b/tools/fileserver/main.go
new file mode 100644
index 000000000..378f39a20
--- /dev/null
+++ b/tools/fileserver/main.go
@@ -0,0 +1,41 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+
+ "github.com/pubgo/funk/v2/log"
+ "github.com/samber/lo"
+ "github.com/valyala/fasthttp"
+)
+
+var port = flag.Int("port", 8080, "http port")
+
+func main() {
+ flag.Parse()
+
+ wd := lo.Must1(os.Getwd())
+ if len(os.Args) > 0 {
+ wd = flag.Arg(1)
+ }
+
+ log.Info().Msgf("file dir: %s", wd)
+ log.Info().Msgf("http://localhost:%v", lo.FromPtr(port))
+
+ fs := &fasthttp.FS{
+ Root: wd,
+ IndexNames: []string{"index.html"},
+ GenerateIndexPages: true,
+ Compress: false,
+ AcceptByteRange: true,
+ // PathRewrite: fasthttp.NewVHostPathRewriter(0),
+ }
+
+ s := &fasthttp.Server{
+ Handler: fs.NewRequestHandler(),
+ Logger: log.NewStd(log.GetLogger("fileserver")),
+ }
+
+ fmt.Println(s.ListenAndServe(fmt.Sprintf(":%v", lo.FromPtr(port))))
+}
diff --git a/tools/tools.go b/tools/tools.go
index 669fca575..bbfe2c6fe 100644
--- a/tools/tools.go
+++ b/tools/tools.go
@@ -6,7 +6,6 @@ package tools
import (
_ "github.com/ecordell/optgen"
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
- _ "github.com/pubgo/protobuild/pkg/retag"
_ "golang.org/x/tools/cmd/stringer"
_ "golang.org/x/vuln/cmd/govulncheck"
)