Compare commits

..

52 Commits

Author SHA1 Message Date
dependabot[bot] b2e3aa1587 chore(deps): bump github.com/pelletier/go-toml/v2 from 2.4.1 to 2.4.2 (#5660) 2026-06-27 11:27:59 +08:00
dependabot[bot] 726cd0141a chore(deps): bump github.com/pelletier/go-toml/v2 from 2.4.0 to 2.4.1 (#5655)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 23:07:20 +08:00
dependabot[bot] c057ecd50a chore(deps): bump actions/checkout from 6 to 7 (#5656)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 22:57:35 +08:00
dependabot[bot] 760e60c364 chore(deps): bump github.com/redis/go-redis/v9 from 9.20.1 to 9.21.0 (#5657)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-23 22:32:31 +08:00
Jeonghyeon Kim 48ca7f03b5 docs: add Korean translations (#5579)
Co-authored-by: kevin <wanjunfeng@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-21 13:49:20 +00:00
Sai Asish Y d4882c1da0 fix(stringx): reject start > stop in Substr to prevent slice panic (#5616)
Signed-off-by: Sai Asish Y <say.apm35@gmail.com>
2026-06-21 04:13:44 +00:00
Kevin Wan 1aecffc306 test(mcp): avoid relying on header map order (#5652) 2026-06-21 10:36:45 +08:00
SapirBaruch 18305200d2 fix(stringx): return empty string from FirstN when n is negative (#5620) 2026-06-21 02:30:48 +00:00
dependabot[bot] d9cb246301 chore(deps): bump github.com/pelletier/go-toml/v2 from 2.3.1 to 2.4.0 (#5650)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-21 10:04:23 +08:00
Henry 4dd372914e feat: add XGroupSetID method to Redis API (#5637)
Co-authored-by: kevin <wanjunfeng@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-20 13:52:06 +00:00
dependabot[bot] a306964403 chore(deps): bump go.mongodb.org/mongo-driver/v2 from 2.6.0 to 2.7.0 (#5651)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-20 19:10:08 +08:00
dependabot[bot] 0431bce2b1 chore(deps): bump github.com/redis/go-redis/v9 from 9.19.0 to 9.20.1 (#5643)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-19 15:42:38 +08:00
dependabot[bot] 6f46837c05 chore(deps): bump github.com/zeromicro/go-zero from 1.10.1 to 1.10.2 in /tools/goctl (#5626) 2026-06-12 08:13:03 +08:00
dependabot[bot] ca8b50a7a5 chore(deps): bump codecov/codecov-action from 6 to 7 (#5633) 2026-06-09 09:45:19 +08:00
dependabot[bot] 0c79f5562e chore(deps): bump github.com/pelletier/go-toml/v2 from 2.3.0 to 2.3.1 (#5589)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 19:05:39 +08:00
dependabot[bot] eb33a8bb36 chore(deps): bump github.com/go-sql-driver/mysql from 1.9.3 to 1.10.0 (#5590)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-25 14:52:46 +08:00
dependabot[bot] 34f16a857e chore(deps): bump github.com/alicebob/miniredis/v2 from 2.37.0 to 2.38.0 (#5592) 2026-05-24 18:53:53 +08:00
caltechustc 6f45879f1b chore: fix some function names in comment (#5596)
Signed-off-by: caltechustc <caltechustc@outlook.com>
2026-05-24 03:54:56 +00:00
Kevin Wan 7b5e7b1c26 fix(discov): prevent unbounded memory growth on duplicate etcd PUT events (#5580) 2026-05-16 12:35:05 +08:00
dependabot[bot] 4ad4fd43b7 chore(deps): bump github.com/redis/go-redis/v9 from 9.18.0 to 9.19.0 (#5585)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-16 12:00:28 +08:00
dependabot[bot] 3f3dca6f72 chore(deps): bump github.com/grafana/pyroscope-go from 1.2.8 to 1.3.0 (#5586)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-16 10:16:09 +08:00
dependabot[bot] 4ea0d9e9ea chore(deps): bump github.com/gookit/color from 1.6.0 to 1.6.1 in /tools/goctl (#5572)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 22:30:06 +08:00
dependabot[bot] 52d2bdadcd chore(deps): bump github.com/go-sql-driver/mysql from 1.9.3 to 1.10.0 in /tools/goctl (#5574)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-13 09:28:17 +08:00
dependabot[bot] 3738be1945 chore(deps): bump go.mongodb.org/mongo-driver/v2 from 2.5.0 to 2.6.0 (#5558)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-11 23:17:45 +08:00
Kevin Wan 5b74b9ab7b feat(mcp): add opt-in request metadata bridge for tool handlers (#5550) 2026-04-25 17:11:04 +08:00
Kevin Wan 4a67261b7b fix(discov): move etcd hosts from URI authority to path for Go 1.26 compatibility (#5548) 2026-04-25 10:48:28 +08:00
dependabot[bot] 22bdae0787 chore(deps): bump codecov/codecov-action from 5 to 6 (#5521) 2026-04-11 15:41:58 +08:00
dependabot[bot] e8675d6a9a chore(deps): bump google.golang.org/grpc from 1.79.3 to 1.80.0 (#5523) 2026-04-11 15:06:08 +08:00
dependabot[bot] e441c44975 chore(deps): bump google.golang.org/grpc from 1.79.3 to 1.80.0 in /tools/goctl (#5524) 2026-04-11 10:46:11 +08:00
Kevin Wan 3f91a79a2b chore: update goctl version to v1.10.1 and bump go-zero dependency (#5518) 2026-03-28 23:01:48 +08:00
Name 8c47c01739 fix(rest/httpc): reject request body for HEAD method in buildRequest (#5457)
Co-authored-by: 1911860538 <alxps1911@gmail.com>
2026-03-28 14:16:53 +00:00
dependabot[bot] f59a1cb0de chore(deps): bump github.com/grafana/pyroscope-go from 1.2.7 to 1.2.8 (#5513)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 08:49:57 +08:00
dependabot[bot] d44ff6ddc8 chore(deps): bump github.com/pelletier/go-toml/v2 from 2.2.4 to 2.3.0 (#5512)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-28 08:35:12 +08:00
Kevin Wan 6ffa9cabec chore: reorder Eval/EvalCtx after Do/DoCtx in redis.go for consistent method ordering (#5502) 2026-03-22 20:39:30 +08:00
Ran丶 0069721586 feat(redis): add Do/DoCtx for generic command execution #5417 (#5442) 2026-03-22 12:26:53 +00:00
Kevin Wan ba9c275853 chore: upgrade Go version to 1.24 and update dependencies (#5499) 2026-03-22 18:47:43 +08:00
fyyang 9a6447ab5c feat: goctl model Add a new method hasField (#5484) 2026-03-22 06:26:56 +00:00
kesonan 004995f06a feat(goctl/rpc): support external proto imports with cross-package ty… (#5472)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-22 04:01:20 +00:00
Amshith Nair c12c82b2f6 test(mathx,stringx): add missing edge case tests for CalcEntropy and … (#5471) 2026-03-22 03:24:02 +00:00
Name 85d770d340 perf(core/stringx): replace manual char filter with strings.Map (#5453)
Co-authored-by: 1911860538 <alxps1911@gmail.com>
2026-03-22 03:04:12 +00:00
Name 8cd7f7a2d8 refactor(core): replace TakeOne usage with cmp.Or (#5461)
Co-authored-by: 1911860538 <alxps1911@gmail.com>
2026-03-22 02:50:16 +00:00
Amshith Nair db3101361b docs(mathx): add godoc comment to Numerical type constraint (#5470) 2026-03-21 15:25:37 +00:00
kesonan eb2302b71e fix(swagger): add example field to path/form/header parameters (#5497)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-21 15:18:55 +00:00
Amshith Nair 04ed637366 test(hash): add unit tests for Hash, Hash determinism, and Md5Hex edg… (#5469) 2026-03-15 15:02:57 +00:00
Kevin Wan 567087a715 test(goctl): add regression test for per-service type alias filtering (#5481) (#5483)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-15 22:51:27 +08:00
kesonan 4d2e64a417 fix #5481 (#5482) 2026-03-15 14:14:12 +00:00
kesonan b01831b4c5 (goctl)fix file copy permission missed (#5475) 2026-03-15 13:55:27 +00:00
Kevin Wan d1a014955c fix: critical security fixes in core/codec (S0) (#5479) 2026-03-15 16:40:15 +08:00
Kevin Wan ec802e25a6 feat: add JSON5 configuration support (#5433)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-14 21:19:46 +08:00
dependabot[bot] 8a2e09dfd1 chore(deps): bump github.com/alicebob/miniredis/v2 from 2.36.1 to 2.37.0 (#5444)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-28 20:29:53 +08:00
dependabot[bot] 220d438fe7 chore(deps): bump github.com/modelcontextprotocol/go-sdk from 1.3.0 to 1.3.1 (#5435) 2026-02-21 13:04:51 +08:00
dependabot[bot] 2cd96146fa chore(deps): bump github.com/redis/go-redis/v9 from 9.17.3 to 9.18.0 (#5432)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-17 20:21:10 +08:00
191 changed files with 9691 additions and 777 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
+3 -3
View File
@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up Go 1.x
uses: actions/setup-go@v6
@@ -40,7 +40,7 @@ jobs:
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v7
with:
files: ./coverage.txt
flags: unittests
@@ -52,7 +52,7 @@ jobs:
runs-on: windows-latest
steps:
- name: Checkout codebase
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up Go 1.x
uses: actions/setup-go@v6
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
- goarch: "386"
goos: darwin
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: zeromicro/go-zero-release-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -5,7 +5,7 @@ jobs:
name: runner / staticcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-go@v6
with:
go-version-file: go.mod
+1 -1
View File
@@ -10,7 +10,7 @@ jobs:
version-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Set up Go
uses: actions/setup-go@v6
+32 -20
View File
@@ -6,8 +6,6 @@ import (
"crypto/cipher"
"encoding/base64"
"errors"
"github.com/zeromicro/go-zero/core/logx"
)
// ErrPaddingSize indicates bad padding size.
@@ -27,7 +25,8 @@ func newECB(b cipher.Block) *ecb {
type ecbEncrypter ecb
// NewECBEncrypter returns an ECB encrypter.
// Deprecated: NewECBEncrypter returns an ECB encrypter.
// ECB mode is insecure for multi-block data. Use AES-GCM instead.
func NewECBEncrypter(b cipher.Block) cipher.BlockMode {
return (*ecbEncrypter)(newECB(b))
}
@@ -39,12 +38,10 @@ func (x *ecbEncrypter) BlockSize() int { return x.blockSize }
// the block size. Dst and src must overlap entirely or not at all.
func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
logx.Error("crypto/cipher: input not full blocks")
return
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
logx.Error("crypto/cipher: output smaller than input")
return
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
@@ -56,7 +53,8 @@ func (x *ecbEncrypter) CryptBlocks(dst, src []byte) {
type ecbDecrypter ecb
// NewECBDecrypter returns an ECB decrypter.
// Deprecated: NewECBDecrypter returns an ECB decrypter.
// ECB mode is insecure for multi-block data. Use AES-GCM instead.
func NewECBDecrypter(b cipher.Block) cipher.BlockMode {
return (*ecbDecrypter)(newECB(b))
}
@@ -70,12 +68,10 @@ func (x *ecbDecrypter) BlockSize() int {
// the block size. Dst and src must overlap entirely or not at all.
func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
logx.Error("crypto/cipher: input not full blocks")
return
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
logx.Error("crypto/cipher: output smaller than input")
return
panic("crypto/cipher: output smaller than input")
}
for len(src) > 0 {
@@ -85,14 +81,18 @@ func (x *ecbDecrypter) CryptBlocks(dst, src []byte) {
}
}
// EcbDecrypt decrypts src with the given key.
// Deprecated: EcbDecrypt decrypts src with the given key.
// ECB mode is insecure for multi-block data. Use AES-GCM instead.
func EcbDecrypt(key, src []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
logx.Errorf("Decrypt key error: % x", key)
return nil, err
}
if len(src)%block.BlockSize() != 0 {
return nil, ErrPaddingSize
}
decrypter := NewECBDecrypter(block)
decrypted := make([]byte, len(src))
decrypter.CryptBlocks(decrypted, src)
@@ -100,8 +100,9 @@ func EcbDecrypt(key, src []byte) ([]byte, error) {
return pkcs5Unpadding(decrypted, decrypter.BlockSize())
}
// EcbDecryptBase64 decrypts base64 encoded src with the given base64 encoded key.
// Deprecated: EcbDecryptBase64 decrypts base64 encoded src with the given base64 encoded key.
// The returned string is also base64 encoded.
// ECB mode is insecure for multi-block data. Use AES-GCM instead.
func EcbDecryptBase64(key, src string) (string, error) {
keyBytes, err := getKeyBytes(key)
if err != nil {
@@ -121,11 +122,11 @@ func EcbDecryptBase64(key, src string) (string, error) {
return base64.StdEncoding.EncodeToString(decryptedBytes), nil
}
// EcbEncrypt encrypts src with the given key.
// Deprecated: EcbEncrypt encrypts src with the given key.
// ECB mode is insecure for multi-block data. Use AES-GCM instead.
func EcbEncrypt(key, src []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
logx.Errorf("Encrypt key error: % x", key)
return nil, err
}
@@ -137,8 +138,9 @@ func EcbEncrypt(key, src []byte) ([]byte, error) {
return crypted, nil
}
// EcbEncryptBase64 encrypts base64 encoded src with the given base64 encoded key.
// Deprecated: EcbEncryptBase64 encrypts base64 encoded src with the given base64 encoded key.
// The returned string is also base64 encoded.
// ECB mode is insecure for multi-block data. Use AES-GCM instead.
func EcbEncryptBase64(key, src string) (string, error) {
keyBytes, err := getKeyBytes(key)
if err != nil {
@@ -179,10 +181,20 @@ func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
func pkcs5Unpadding(src []byte, blockSize int) ([]byte, error) {
length := len(src)
unpadding := int(src[length-1])
if unpadding >= length || unpadding > blockSize {
if length == 0 {
return nil, ErrPaddingSize
}
unpadding := int(src[length-1])
if unpadding < 1 || unpadding > blockSize || unpadding > length {
return nil, ErrPaddingSize
}
for _, b := range src[length-unpadding:] {
if int(b) != unpadding {
return nil, ErrPaddingSize
}
}
return src[:length-unpadding], nil
}
+55 -19
View File
@@ -28,8 +28,8 @@ func TestAesEcb(t *testing.T) {
_, err = EcbDecrypt(badKey2, dst)
assert.NotNil(t, err)
_, err = EcbDecrypt(key, val)
// not enough block, just nil
assert.Nil(t, err)
// not a multiple of block size
assert.NotNil(t, err)
src, err := EcbDecrypt(key, dst)
assert.Nil(t, err)
assert.Equal(t, val, src)
@@ -41,33 +41,28 @@ func TestAesEcb(t *testing.T) {
assert.Equal(t, 16, decrypter.BlockSize())
dst = make([]byte, 8)
encrypter.CryptBlocks(dst, val)
for _, b := range dst {
assert.Equal(t, byte(0), b)
}
assert.Panics(t, func() {
encrypter.CryptBlocks(dst, val)
})
dst = make([]byte, 8)
encrypter.CryptBlocks(dst, valLong)
for _, b := range dst {
assert.Equal(t, byte(0), b)
}
assert.Panics(t, func() {
encrypter.CryptBlocks(dst, valLong)
})
dst = make([]byte, 8)
decrypter.CryptBlocks(dst, val)
for _, b := range dst {
assert.Equal(t, byte(0), b)
}
assert.Panics(t, func() {
decrypter.CryptBlocks(dst, val)
})
dst = make([]byte, 8)
decrypter.CryptBlocks(dst, valLong)
for _, b := range dst {
assert.Equal(t, byte(0), b)
}
assert.Panics(t, func() {
decrypter.CryptBlocks(dst, valLong)
})
_, err = EcbEncryptBase64("cTR0N3dDKkYtSmFOZFJnVWpYbjJyNXU4eC9BP0QK", "aGVsbG93b3JsZGxvbmcuLgo=")
assert.Error(t, err)
}
func TestAesEcbBase64(t *testing.T) {
const (
val = "hello"
@@ -98,3 +93,44 @@ func TestAesEcbBase64(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, val, string(b))
}
func TestPkcs5UnpaddingEmptyInput(t *testing.T) {
_, err := pkcs5Unpadding([]byte{}, 16)
assert.Equal(t, ErrPaddingSize, err)
}
func TestPkcs5UnpaddingMalformedPadding(t *testing.T) {
// Valid PKCS5 padding of 3: last 3 bytes should all be 0x03
// Here we corrupt one padding byte
malformed := []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x02, 0x03, 0x03}
_, err := pkcs5Unpadding(malformed, 16)
assert.Equal(t, ErrPaddingSize, err)
// All padding bytes correct
valid := []byte{0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41,
0x41, 0x41, 0x41, 0x41, 0x41, 0x03, 0x03, 0x03}
result, err := pkcs5Unpadding(valid, 16)
assert.NoError(t, err)
assert.Equal(t, valid[:13], result)
}
func TestPkcs5UnpaddingInvalidPaddingValue(t *testing.T) {
// padding value = 0 (< 1)
_, err := pkcs5Unpadding([]byte{0x41, 0x00}, 16)
assert.Equal(t, ErrPaddingSize, err)
// padding value > blockSize
_, err = pkcs5Unpadding([]byte{0x41, 0x41, 0x41, 0x41, 17}, 4)
assert.Equal(t, ErrPaddingSize, err)
// padding value > length
_, err = pkcs5Unpadding([]byte{0x41, 0x03}, 16)
assert.Equal(t, ErrPaddingSize, err)
}
func TestEcbDecryptEmptyInput(t *testing.T) {
key := []byte("q4t7w!z%C*F-JaNdRgUjXn2r5u8x/A?D")
_, err := EcbDecrypt(key, []byte{})
assert.Equal(t, ErrPaddingSize, err)
}
+1 -1
View File
@@ -35,7 +35,7 @@ func ComputeKey(pubKey, priKey *big.Int) (*big.Int, error) {
return nil, ErrInvalidPubKey
}
if pubKey.Sign() <= 0 && p.Cmp(pubKey) <= 0 {
if pubKey.Sign() <= 0 || p.Cmp(pubKey) <= 0 {
return nil, ErrPubKeyOutOfBound
}
+29
View File
@@ -94,3 +94,32 @@ func TestDHOnErrors(t *testing.T) {
assert.NotNil(t, NewPublicKey([]byte("")))
}
func TestDHPubKeyBoundary(t *testing.T) {
key, err := GenerateKey()
assert.Nil(t, err)
// pubKey = 0 should be rejected
_, err = ComputeKey(big.NewInt(0), key.PriKey)
assert.ErrorIs(t, err, ErrPubKeyOutOfBound)
// pubKey = -1 should be rejected
_, err = ComputeKey(big.NewInt(-1), key.PriKey)
assert.ErrorIs(t, err, ErrPubKeyOutOfBound)
// pubKey = p should be rejected
_, err = ComputeKey(new(big.Int).Set(p), key.PriKey)
assert.ErrorIs(t, err, ErrPubKeyOutOfBound)
// pubKey = p+1 should be rejected
_, err = ComputeKey(new(big.Int).Add(p, big.NewInt(1)), key.PriKey)
assert.ErrorIs(t, err, ErrPubKeyOutOfBound)
// pubKey = 1 should be accepted
_, err = ComputeKey(big.NewInt(1), key.PriKey)
assert.NoError(t, err)
// pubKey = p-1 should be accepted
_, err = ComputeKey(new(big.Int).Sub(p, big.NewInt(1)), key.PriKey)
assert.NoError(t, err)
}
+94 -2
View File
@@ -3,6 +3,7 @@ package codec
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
@@ -46,7 +47,9 @@ type (
}
)
// NewRsaDecrypter returns a RsaDecrypter with the given file.
// Deprecated: NewRsaDecrypter returns a RsaDecrypter with the given file.
// PKCS#1 v1.5 padding is vulnerable to padding oracle attacks.
// Use NewRsaOAEPDecrypter instead.
func NewRsaDecrypter(file string) (RsaDecrypter, error) {
content, err := os.ReadFile(file)
if err != nil {
@@ -90,7 +93,9 @@ func (r *rsaDecrypter) DecryptBase64(input string) ([]byte, error) {
return r.Decrypt(base64Decoded)
}
// NewRsaEncrypter returns a RsaEncrypter with the given key.
// Deprecated: NewRsaEncrypter returns a RsaEncrypter with the given key.
// PKCS#1 v1.5 padding is vulnerable to padding oracle attacks.
// Use NewRsaOAEPEncrypter instead.
func NewRsaEncrypter(key []byte) (RsaEncrypter, error) {
block, _ := pem.Decode(key)
if block == nil {
@@ -154,3 +159,90 @@ func rsaDecryptBlock(privateKey *rsa.PrivateKey, block []byte) ([]byte, error) {
func rsaEncryptBlock(publicKey *rsa.PublicKey, msg []byte) ([]byte, error) {
return rsa.EncryptPKCS1v15(rand.Reader, publicKey, msg)
}
// NewRsaOAEPDecrypter returns a RsaDecrypter using OAEP with SHA-256.
func NewRsaOAEPDecrypter(file string) (RsaDecrypter, error) {
content, err := os.ReadFile(file)
if err != nil {
return nil, err
}
block, _ := pem.Decode(content)
if block == nil {
return nil, ErrPrivateKey
}
privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return &rsaOAEPDecrypter{
rsaBase: rsaBase{
bytesLimit: privateKey.N.BitLen() >> 3,
},
privateKey: privateKey,
}, nil
}
// NewRsaOAEPEncrypter returns a RsaEncrypter using OAEP with SHA-256.
func NewRsaOAEPEncrypter(key []byte) (RsaEncrypter, error) {
block, _ := pem.Decode(key)
if block == nil {
return nil, ErrPublicKey
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pubKey := pub.(type) {
case *rsa.PublicKey:
// OAEP overhead: 2*hash_size + 2
hashSize := sha256.New().Size()
return &rsaOAEPEncrypter{
rsaBase: rsaBase{
bytesLimit: (pubKey.N.BitLen() >> 3) - 2*hashSize - 2,
},
publicKey: pubKey,
}, nil
default:
return nil, ErrNotRsaKey
}
}
type rsaOAEPDecrypter struct {
rsaBase
privateKey *rsa.PrivateKey
}
func (r *rsaOAEPDecrypter) Decrypt(input []byte) ([]byte, error) {
return r.crypt(input, func(block []byte) ([]byte, error) {
return rsa.DecryptOAEP(sha256.New(), rand.Reader, r.privateKey, block, nil)
})
}
func (r *rsaOAEPDecrypter) DecryptBase64(input string) ([]byte, error) {
if len(input) == 0 {
return nil, nil
}
base64Decoded, err := base64.StdEncoding.DecodeString(input)
if err != nil {
return nil, err
}
return r.Decrypt(base64Decoded)
}
type rsaOAEPEncrypter struct {
rsaBase
publicKey *rsa.PublicKey
}
func (r *rsaOAEPEncrypter) Encrypt(input []byte) ([]byte, error) {
return r.crypt(input, func(block []byte) ([]byte, error) {
return rsa.EncryptOAEP(sha256.New(), rand.Reader, r.publicKey, block, nil)
})
}
+80
View File
@@ -1,7 +1,12 @@
package codec
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"os"
"testing"
@@ -58,3 +63,78 @@ func TestBadPubKey(t *testing.T) {
_, err := NewRsaEncrypter([]byte("foo"))
assert.Equal(t, ErrPublicKey, err)
}
func TestOAEPCryption(t *testing.T) {
enc, err := NewRsaOAEPEncrypter([]byte(pubKey))
assert.Nil(t, err)
ret, err := enc.Encrypt([]byte(testBody))
assert.Nil(t, err)
file, err := fs.TempFilenameWithText(priKey)
assert.Nil(t, err)
defer os.Remove(file)
dec, err := NewRsaOAEPDecrypter(file)
assert.Nil(t, err)
actual, err := dec.Decrypt(ret)
assert.Nil(t, err)
assert.Equal(t, testBody, string(actual))
actual, err = dec.DecryptBase64(base64.StdEncoding.EncodeToString(ret))
assert.Nil(t, err)
assert.Equal(t, testBody, string(actual))
// empty input
actual, err = dec.DecryptBase64("")
assert.Nil(t, err)
assert.Nil(t, actual)
}
func TestOAEPBadKeys(t *testing.T) {
_, err := NewRsaOAEPEncrypter([]byte("bad"))
assert.Equal(t, ErrPublicKey, err)
_, err = NewRsaOAEPDecrypter("nonexistent")
assert.Error(t, err)
// valid PEM but invalid private key content
badPem, err := fs.TempFilenameWithText("-----BEGIN RSA PRIVATE KEY-----\nYmFk\n-----END RSA PRIVATE KEY-----")
assert.Nil(t, err)
defer os.Remove(badPem)
_, err = NewRsaOAEPDecrypter(badPem)
assert.Error(t, err)
// not PEM content at all
notPem, err := fs.TempFilenameWithText("not a pem file")
assert.Nil(t, err)
defer os.Remove(notPem)
_, err = NewRsaOAEPDecrypter(notPem)
assert.Equal(t, ErrPrivateKey, err)
}
func TestOAEPEncrypterParseError(t *testing.T) {
// valid PEM block but invalid public key content
badPub := []byte("-----BEGIN PUBLIC KEY-----\nYmFk\n-----END PUBLIC KEY-----")
_, err := NewRsaOAEPEncrypter(badPub)
assert.Error(t, err)
}
func TestOAEPEncrypterNonRsaKey(t *testing.T) {
ecKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
assert.Nil(t, err)
derBytes, err := x509.MarshalPKIXPublicKey(&ecKey.PublicKey)
assert.Nil(t, err)
ecPem := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: derBytes})
_, err = NewRsaOAEPEncrypter(ecPem)
assert.Equal(t, ErrNotRsaKey, err)
}
func TestOAEPDecryptBase64Error(t *testing.T) {
file, err := fs.TempFilenameWithText(priKey)
assert.Nil(t, err)
defer os.Remove(file)
dec, err := NewRsaOAEPDecrypter(file)
assert.Nil(t, err)
_, err = dec.DecryptBase64("not-valid-base64!!!")
assert.Error(t, err)
}
+17 -6
View File
@@ -21,10 +21,11 @@ const (
var (
fillDefaultUnmarshaler = mapping.NewUnmarshaler(jsonTagKey, mapping.WithDefault())
loaders = map[string]func([]byte, any) error{
".json": LoadFromJsonBytes,
".toml": LoadFromTomlBytes,
".yaml": LoadFromYamlBytes,
".yml": LoadFromYamlBytes,
".json": LoadFromJsonBytes,
".json5": LoadFromJson5Bytes,
".toml": LoadFromTomlBytes,
".yaml": LoadFromYamlBytes,
".yml": LoadFromYamlBytes,
}
)
@@ -41,7 +42,7 @@ func FillDefault(v any) error {
return fillDefaultUnmarshaler.Unmarshal(map[string]any{}, v)
}
// Load loads config into v from file, .json, .yaml and .yml are acceptable.
// Load loads config into v from file, .json, .json5, .toml, .yaml and .yml are acceptable.
func Load(file string, v any, opts ...Option) error {
content, err := os.ReadFile(file)
if err != nil {
@@ -65,7 +66,7 @@ func Load(file string, v any, opts ...Option) error {
return loader(content, v)
}
// LoadConfig loads config into v from file, .json, .yaml and .yml are acceptable.
// LoadConfig loads config into v from file, .json, .json5, .toml, .yaml and .yml are acceptable.
// Deprecated: use Load instead.
func LoadConfig(file string, v any, opts ...Option) error {
return Load(file, v, opts...)
@@ -119,6 +120,16 @@ func LoadFromYamlBytes(content []byte, v any) error {
return LoadFromJsonBytes(b, v)
}
// LoadFromJson5Bytes loads config into v from content json5 bytes.
func LoadFromJson5Bytes(content []byte, v any) error {
b, err := encoding.Json5ToJson(content)
if err != nil {
return err
}
return LoadFromJsonBytes(b, v)
}
// LoadConfigFromYamlBytes loads config into v from content yaml bytes.
// Deprecated: use LoadFromYamlBytes instead.
func LoadConfigFromYamlBytes(content []byte, v any) error {
+154
View File
@@ -75,6 +75,160 @@ func TestLoadFromJsonBytesArray(t *testing.T) {
assert.EqualValues(t, []string{"foo", "bar"}, expect)
}
func TestConfigJson5(t *testing.T) {
// JSON5 with comments, trailing commas, and unquoted keys
text := `{
// This is a comment
a: 'foo', // single quotes
b: 1,
c: "${FOO}",
d: "abcd!@#$112", // trailing comma
}`
t.Setenv("FOO", "2")
tmpfile, err := createTempFile(t, ".json5", text)
assert.Nil(t, err)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
}
func TestConfigJsonStandardParser(t *testing.T) {
// Standard JSON uses standard JSON parser (not JSON5) for backward compatibility
text := `{
"a": "foo",
"b": 1,
"c": "${FOO}",
"d": "abcd!@#$112"
}`
t.Setenv("FOO", "2")
tmpfile, err := createTempFile(t, ".json", text)
assert.Nil(t, err)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "${FOO}", val.C)
assert.Equal(t, "abcd!@#$112", val.D)
}
func TestConfigJsonLargeIntegers(t *testing.T) {
// Test that .json files preserve large integer precision (backward compatibility)
text := `{
"id": 1234567890123456789,
"timestamp": 9223372036854775807
}`
tmpfile, err := createTempFile(t, ".json", text)
assert.Nil(t, err)
var val struct {
ID int64 `json:"id"`
Timestamp int64 `json:"timestamp"`
}
MustLoad(tmpfile, &val)
assert.Equal(t, int64(1234567890123456789), val.ID)
assert.Equal(t, int64(9223372036854775807), val.Timestamp)
}
func TestConfigJson5Env(t *testing.T) {
text := `{
// Comment with env variable
a: "foo",
b: 1,
c: "${FOO}",
d: "abcd!@#$a12 3",
}`
t.Setenv("FOO", "2")
tmpfile, err := createTempFile(t, ".json5", text)
assert.Nil(t, err)
var val struct {
A string `json:"a"`
B int `json:"b"`
C string `json:"c"`
D string `json:"d"`
}
MustLoad(tmpfile, &val, UseEnv())
assert.Equal(t, "foo", val.A)
assert.Equal(t, 1, val.B)
assert.Equal(t, "2", val.C)
assert.Equal(t, "abcd!@# 3", val.D)
}
func TestLoadFromJson5Bytes(t *testing.T) {
// Test JSON5 features: comments, trailing commas, single quotes, unquoted keys
input := []byte(`{
// This is a comment
users: [
{name: 'foo'}, // trailing comma
{Name: "bar"},
],
}`)
var val struct {
Users []struct {
Name string
}
}
assert.NoError(t, LoadFromJson5Bytes(input, &val))
var expect []string
for _, user := range val.Users {
expect = append(expect, user.Name)
}
assert.EqualValues(t, []string{"foo", "bar"}, expect)
}
func TestLoadFromJson5BytesError(t *testing.T) {
// Invalid JSON5 syntax
input := []byte(`{a: foo}`) // unquoted string value (invalid)
var val struct {
A string
}
assert.Error(t, LoadFromJson5Bytes(input, &val))
}
func TestConfigJson5LargeIntegersLimitation(t *testing.T) {
// Document that JSON5 has precision limitations for large integers (>2^53)
// due to JavaScript number semantics. Users should use .json for configs with large IDs.
text := `{
// JSON5 converts numbers to float64, which loses precision for large integers
id: 1234567890123456789
}`
tmpfile, err := createTempFile(t, ".json5", text)
assert.Nil(t, err)
var val struct {
ID int64 `json:"id"`
}
// This will load; depending on the JSON5 implementation, large integers may lose precision.
// This test documents that behavior without requiring loss of precision as an invariant.
err = Load(tmpfile, &val)
assert.NoError(t, err)
t.Logf("loaded JSON5 large integer id=%d (original 1234567890123456789)", val.ID)
}
func TestConfigToml(t *testing.T) {
text := `a = "foo"
b = 1
+17 -7
View File
@@ -263,14 +263,24 @@ func (c *cluster) handleWatchEvents(ctx context.Context, key watchKey, events []
for _, ev := range events {
switch ev.Type {
case clientv3.EventTypePut:
evKey := string(ev.Kv.Key)
evVal := string(ev.Kv.Value)
c.lock.Lock()
watcher.values[string(ev.Kv.Key)] = string(ev.Kv.Value)
oldVal, exists := watcher.values[evKey]
watcher.values[evKey] = evVal
c.lock.Unlock()
if exists && oldVal == evVal {
// duplicate PUT with same value, skip to prevent unbounded growth
continue
}
if exists {
// key moved to a new value, notify delete of old entry first
for _, l := range listeners {
l.OnDelete(KV{Key: evKey, Val: oldVal})
}
}
for _, l := range listeners {
l.OnAdd(KV{
Key: string(ev.Kv.Key),
Val: string(ev.Kv.Value),
})
l.OnAdd(KV{Key: evKey, Val: evVal})
}
case clientv3.EventTypeDelete:
c.lock.Lock()
@@ -433,7 +443,7 @@ func (c *cluster) setupWatch(cli EtcdClient, key watchKey, rev int64) (context.C
}
ctx, cancel := context.WithCancel(cli.Ctx())
c.lock.Lock()
if watcher, ok := c.watchers[key]; ok {
watcher.cancel = cancel
@@ -508,7 +518,7 @@ func makeKeyPrefix(key string) string {
return fmt.Sprintf("%s%c", key, Delimiter)
}
// NewClient returns a watchValue that make sure values are not nil.
// newWatchValue returns a watchValue that make sure values are not nil.
func newWatchValue() *watchValue {
return &watchValue{
values: make(map[string]string),
+45 -1
View File
@@ -517,7 +517,7 @@ func TestCluster_ConcurrentMonitor(t *testing.T) {
go func() {
defer wg.Done()
key := keys[idx%len(keys)]
if idx%2 == 0 {
// Half the goroutines add listeners (write operation)
c.addListener(key, &mockListener{})
@@ -543,6 +543,50 @@ func TestCluster_ConcurrentMonitor(t *testing.T) {
close(c.done)
}
func TestCluster_handleWatchEvents_DuplicatePut(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
listener := NewMockUpdateListener(ctrl)
// OnAdd must be called exactly once despite two PUT events with the same key+value.
listener.EXPECT().OnAdd(KV{Key: "hello", Val: "world"}).Times(1)
c := newCluster([]string{"any"})
key := watchKey{key: "any"}
c.watchers[key] = &watchValue{
listeners: []UpdateListener{listener},
values: make(map[string]string),
}
events := []*clientv3.Event{
{Type: clientv3.EventTypePut, Kv: &mvccpb.KeyValue{Key: []byte("hello"), Value: []byte("world")}},
{Type: clientv3.EventTypePut, Kv: &mvccpb.KeyValue{Key: []byte("hello"), Value: []byte("world")}},
}
c.handleWatchEvents(context.Background(), key, events)
}
func TestCluster_handleWatchEvents_ValueChange(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
listener := NewMockUpdateListener(ctrl)
gomock.InOrder(
listener.EXPECT().OnAdd(KV{Key: "hello", Val: "world1"}),
listener.EXPECT().OnDelete(KV{Key: "hello", Val: "world1"}),
listener.EXPECT().OnAdd(KV{Key: "hello", Val: "world2"}),
)
c := newCluster([]string{"any"})
key := watchKey{key: "any"}
c.watchers[key] = &watchValue{
listeners: []UpdateListener{listener},
values: make(map[string]string),
}
c.handleWatchEvents(context.Background(), key, []*clientv3.Event{
{Type: clientv3.EventTypePut, Kv: &mvccpb.KeyValue{Key: []byte("hello"), Value: []byte("world1")}},
})
c.handleWatchEvents(context.Background(), key, []*clientv3.Event{
{Type: clientv3.EventTypePut, Kv: &mvccpb.KeyValue{Key: []byte("hello"), Value: []byte("world2")}},
})
}
type mockListener struct {
}
+12 -1
View File
@@ -141,12 +141,23 @@ func (c *container) addKv(key, value string) ([]string, bool) {
c.lock.Lock()
defer c.lock.Unlock()
oldVal, alreadyMapped := c.mapping[key]
if alreadyMapped && oldVal == value {
// duplicate PUT with same key+value, nothing to do
return nil, false
}
c.dirty.Set(true)
if alreadyMapped {
// key moved to a new value; remove stale entry to prevent leak
c.doRemoveKey(key)
}
keys := c.values[value]
previous := append([]string(nil), keys...)
early := len(keys) > 0
if c.exclusive && early {
for _, each := range keys {
for _, each := range previous {
c.doRemoveKey(each)
}
}
+75
View File
@@ -201,6 +201,81 @@ func TestContainer(t *testing.T) {
}
}
func TestContainer_DuplicateAdd(t *testing.T) {
c := newContainer(false)
// Simulate 100 duplicate PUT events for the same key+value.
for i := 0; i < 100; i++ {
c.OnAdd(internal.KV{Key: "etcd-key", Val: "host:1234"})
}
assert.ElementsMatch(t, []string{"host:1234"}, c.GetValues())
// Internal slice must not have grown beyond one entry.
c.lock.Lock()
assert.Len(t, c.values["host:1234"], 1)
c.lock.Unlock()
}
func TestContainer_KeyValueChange(t *testing.T) {
c := newContainer(false)
c.OnAdd(internal.KV{Key: "etcd-key", Val: "host:1234"})
assert.ElementsMatch(t, []string{"host:1234"}, c.GetValues())
// Key moves to a different server value.
c.OnAdd(internal.KV{Key: "etcd-key", Val: "host:5678"})
assert.ElementsMatch(t, []string{"host:5678"}, c.GetValues())
// Old server must be fully removed; a subsequent delete must leave nothing.
c.OnDelete(internal.KV{Key: "etcd-key", Val: "host:5678"})
assert.Empty(t, c.GetValues())
}
// TestContainer_ExclusiveMode verifies that adding successive keys for the same
// value in exclusive mode leaves only the latest key and evicts all prior ones.
func TestContainer_ExclusiveMode(t *testing.T) {
c := newContainer(true)
c.OnAdd(internal.KV{Key: "key1", Val: "server1"})
c.OnAdd(internal.KV{Key: "key2", Val: "server1"})
c.OnAdd(internal.KV{Key: "key3", Val: "server1"})
assert.ElementsMatch(t, []string{"server1"}, c.GetValues())
c.lock.Lock()
assert.Equal(t, []string{"key3"}, c.values["server1"], "only the latest key must remain")
assert.NotContains(t, c.mapping, "key1", "key1 must have been evicted")
assert.NotContains(t, c.mapping, "key2", "key2 must have been evicted")
assert.Equal(t, "server1", c.mapping["key3"])
c.lock.Unlock()
}
// TestContainer_ExclusiveMode_MultipleEvictions injects 3 keys for the same
// value directly into internal state and then triggers the exclusive eviction
// loop via OnAdd. This exercises the range-over-previous fix: iterating over
// the live slice (range keys) would corrupt iteration when doRemoveKey
// compacts the shared underlying array in-place, causing the second and third
// keys to be skipped; ranging over the deep copy (range previous) is safe.
func TestContainer_ExclusiveMode_MultipleEvictions(t *testing.T) {
c := newContainer(true)
// Bypass the exclusive invariant to simulate 3 pre-existing keys for the
// same value — the state that would expose the in-place aliasing bug.
c.lock.Lock()
c.values["server1"] = []string{"key1", "key2", "key3"}
c.mapping["key1"] = "server1"
c.mapping["key2"] = "server1"
c.mapping["key3"] = "server1"
c.lock.Unlock()
// Adding key4 must evict all three existing keys via the exclusive loop.
c.OnAdd(internal.KV{Key: "key4", Val: "server1"})
assert.ElementsMatch(t, []string{"server1"}, c.GetValues())
c.lock.Lock()
assert.Equal(t, []string{"key4"}, c.values["server1"], "all prior keys must be evicted")
assert.NotContains(t, c.mapping, "key1", "key1 must be evicted")
assert.NotContains(t, c.mapping, "key2", "key2 must be evicted")
assert.NotContains(t, c.mapping, "key3", "key3 must be evicted")
assert.Equal(t, "server1", c.mapping["key4"])
c.lock.Unlock()
}
func TestSubscriber(t *testing.T) {
sub := new(Subscriber)
Exclusive()(sub)
+23
View File
@@ -25,6 +25,29 @@ func TestMd5Hex(t *testing.T) {
assert.Equal(t, md5Digest, actual)
}
func TestHash(t *testing.T) {
result := Hash([]byte(text))
assert.NotEqual(t, uint64(0), result)
}
func TestHash_Deterministic(t *testing.T) {
data := []byte("consistent-hash-test")
first := Hash(data)
second := Hash(data)
assert.Equal(t, first, second)
}
func TestHash_Empty(t *testing.T) {
// Hash should not panic on empty input.
result := Hash([]byte{})
_ = result
}
func TestMd5Hex_Empty(t *testing.T) {
result := Md5Hex([]byte{})
assert.Equal(t, 32, len(result))
}
func BenchmarkHashFnv(b *testing.B) {
for i := 0; i < b.N; i++ {
h := fnv.New32()
+4 -4
View File
@@ -247,7 +247,7 @@ func TestStructedLogDebugf(t *testing.T) {
defer writer.Store(old)
doTestStructedLog(t, levelDebug, w, func(v ...any) {
Debugf(fmt.Sprint(v...))
Debugf("%s", fmt.Sprint(v...))
})
}
@@ -559,7 +559,7 @@ func TestStructedLogSlowf(t *testing.T) {
defer writer.Store(old)
doTestStructedLog(t, levelSlow, w, func(v ...any) {
Slowf(fmt.Sprint(v...))
Slowf("%s", fmt.Sprint(v...))
})
}
@@ -625,7 +625,7 @@ func TestStructedLogStatf(t *testing.T) {
defer writer.Store(old)
doTestStructedLog(t, levelStat, w, func(v ...any) {
Statf(fmt.Sprint(v...))
Statf("%s", fmt.Sprint(v...))
})
}
@@ -645,7 +645,7 @@ func TestStructedLogSeveref(t *testing.T) {
defer writer.Store(old)
doTestStructedLog(t, levelSevere, w, func(v ...any) {
Severef(fmt.Sprint(v...))
Severef("%s", fmt.Sprint(v...))
})
}
+1 -1
View File
@@ -2,7 +2,7 @@
# logx
[English](readme.md) | 简体中文
[English](readme.md) | 简体中文 | [한국어](readme-ko.md)
## logx 配置
+205
View File
@@ -0,0 +1,205 @@
<img align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
# logx
[English](readme.md) | [简体中文](readme-cn.md) | 한국어
## logx 설정
```go
type LogConf struct {
ServiceName string `json:",optional"`
Mode string `json:",default=console,options=[console,file,volume]"`
Encoding string `json:",default=json,options=[json,plain]"`
TimeFormat string `json:",optional"`
Path string `json:",default=logs"`
Level string `json:",default=info,options=[info,error,severe]"`
Compress bool `json:",optional"`
KeepDays int `json:",optional"`
StackCooldownMillis int `json:",default=100"`
MaxBackups int `json:",default=0"`
MaxSize int `json:",default=0"`
Rotation string `json:",default=daily,options=[daily,size]"`
}
```
- `ServiceName`: 서비스 이름을 설정합니다. 선택 사항입니다. `volume` 모드에서는 이 이름이 로그 파일 생성에 사용됩니다. `rest/zrpc` 서비스에서는 이름이 `rest` 또는 `zrpc`의 이름으로 자동 설정됩니다.
- `Mode`: 로그 출력 모드입니다. 기본값은 `console`입니다.
- `console` 모드는 로그를 `stdout/stderr`에 씁니다.
- `file` 모드는 `Path`로 지정한 파일에 로그를 씁니다.
- `volume` 모드는 docker에서 사용하며, 마운트된 볼륨에 로그를 씁니다.
- `Encoding`: 로그 인코딩 방식을 나타냅니다. 기본값은 `json`입니다.
- `json` 모드는 로그를 json 형식으로 씁니다.
- `plain` 모드는 터미널 색상이 활성화된 일반 텍스트로 로그를 씁니다.
- `TimeFormat`: 시간 형식을 사용자 지정합니다. 선택 사항입니다. 기본값은 `2006-01-02T15:04:05.000Z07:00`입니다.
- `Path`: 로그 경로를 설정합니다. 기본값은 `logs`입니다.
- `Level`: 로그를 필터링할 로깅 레벨입니다. 기본값은 `info`입니다.
- `info`: 모든 로그가 기록됩니다.
- `error`: `info` 로그가 억제됩니다.
- `severe`: `info``error` 로그가 억제되고 `severe` 로그만 기록됩니다.
- `Compress`: 로그 파일 압축 여부입니다. `file` 모드에서만 동작합니다.
- `KeepDays`: 로그 파일을 보관할 일수입니다. 지정한 일수가 지나면 오래된 파일이 자동으로 삭제됩니다. `console` 모드에는 영향을 주지 않습니다.
- `StackCooldownMillis`: 스택 트레이스를 다시 기록하기까지의 밀리초입니다. 스택 트레이스 로그 폭주를 방지하는 데 사용됩니다.
- `MaxBackups`: 보관할 백업 로그 파일 개수입니다. 0은 모든 파일을 영구 보관한다는 의미입니다. `Rotation``size`일 때만 적용됩니다. 참고: `KeepDays` 옵션의 우선순위가 더 높습니다. `MaxBackups`가 0이더라도 `KeepDays` 제한에 도달하면 로그 파일은 삭제됩니다.
- `MaxSize`: 현재 기록 중인 로그 파일이 차지할 수 있는 최대 공간입니다. 0은 제한 없음을 의미합니다. 단위는 `MB`입니다. `Rotation``size`일 때만 적용됩니다.
- `Rotation`: 로그 로테이션 규칙의 유형입니다. 기본값은 `daily`입니다.
- `daily`: 날짜 단위로 로그를 회전합니다.
- `size`: 로그 크기 단위로 로그를 회전합니다.
## 로깅 메서드
```go
type Logger interface {
// Error logs a message at error level.
Error(...any)
// Errorf logs a message at error level.
Errorf(string, ...any)
// Errorv logs a message at error level.
Errorv(any)
// Errorw logs a message at error level.
Errorw(string, ...LogField)
// Info logs a message at info level.
Info(...any)
// Infof logs a message at info level.
Infof(string, ...any)
// Infov logs a message at info level.
Infov(any)
// Infow logs a message at info level.
Infow(string, ...LogField)
// Slow logs a message at slow level.
Slow(...any)
// Slowf logs a message at slow level.
Slowf(string, ...any)
// Slowv logs a message at slow level.
Slowv(any)
// Sloww logs a message at slow level.
Sloww(string, ...LogField)
// WithContext returns a new logger with the given context.
WithContext(context.Context) Logger
// WithDuration returns a new logger with the given duration.
WithDuration(time.Duration) Logger
}
```
- `Error`, `Info`, `Slow`: `fmt.Sprint(…)`처럼 모든 종류의 메시지를 로그에 씁니다.
- `Errorf`, `Infof`, `Slowf`: 지정한 형식으로 메시지를 로그에 씁니다.
- `Errorv`, `Infov`, `Slowv`: 모든 종류의 메시지를 json 마샬링으로 인코딩해 로그에 씁니다.
- `Errorw`, `Infow`, `Sloww`: 지정한 `key:value` 필드와 함께 문자열 메시지를 씁니다.
- `WithContext`: 지정한 ctx를 로그 메시지에 주입합니다. 일반적으로 `trace-id``span-id`를 기록하는 데 사용됩니다.
- `WithDuration`: 경과 시간을 `duration` 키로 로그 메시지에 씁니다.
## 타사 로깅 라이브러리와 통합
- zap
- 구현: [https://github.com/zeromicro/zero-contrib/blob/main/logx/zapx/zap.go](https://github.com/zeromicro/zero-contrib/blob/main/logx/zapx/zap.go)
- 사용 예시: [https://github.com/zeromicro/zero-examples/blob/main/logx/zaplog/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/zaplog/main.go)
- logrus
- 구현: [https://github.com/zeromicro/zero-contrib/blob/main/logx/logrusx/logrus.go](https://github.com/zeromicro/zero-contrib/blob/main/logx/logrusx/logrus.go)
- 사용 예시: [https://github.com/zeromicro/zero-examples/blob/main/logx/logrus/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/logrus/main.go)
더 많은 라이브러리는 직접 구현한 뒤 [https://github.com/zeromicro/zero-contrib](https://github.com/zeromicro/zero-contrib)에 PR을 보내주세요.
## 특정 저장소에 로그 쓰기
`logx`는 로그를 원하는 저장소에 쓸 수 있도록 사용자 지정할 수 있는 두 인터페이스를 정의합니다.
- `logx.NewWriter(w io.Writer)`
- `logx.SetWriter(writer logx.Writer)`
예를 들어 로그를 콘솔이나 파일 대신 kafka에 쓰고 싶다면 아래처럼 할 수 있습니다.
```go
type KafkaWriter struct {
Pusher *kq.Pusher
}
func NewKafkaWriter(pusher *kq.Pusher) *KafkaWriter {
return &KafkaWriter{
Pusher: pusher,
}
}
func (w *KafkaWriter) Write(p []byte) (n int, err error) {
// writing log with newlines, trim them.
if err := w.Pusher.Push(strings.TrimSpace(string(p))); err != nil {
return 0, err
}
return len(p), nil
}
func main() {
pusher := kq.NewPusher([]string{"localhost:9092"}, "go-zero")
defer pusher.Close()
writer := logx.NewWriter(NewKafkaWriter(pusher))
logx.SetWriter(writer)
// more code
}
```
전체 코드: [https://github.com/zeromicro/zero-examples/blob/main/logx/tokafka/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/tokafka/main.go)
## 민감한 필드 필터링
`password` 필드가 로그에 기록되지 않도록 하려면 아래처럼 할 수 있습니다.
```go
type (
Message struct {
Name string
Password string
Message string
}
SensitiveLogger struct {
logx.Writer
}
)
func NewSensitiveLogger(writer logx.Writer) *SensitiveLogger {
return &SensitiveLogger{
Writer: writer,
}
}
func (l *SensitiveLogger) Info(msg any, fields ...logx.LogField) {
if m, ok := msg.(Message); ok {
l.Writer.Info(Message{
Name: m.Name,
Password: "******",
Message: m.Message,
}, fields...)
} else {
l.Writer.Info(msg, fields...)
}
}
func main() {
// setup logx to make sure originalWriter not nil,
// the injected writer is only for filtering, like a middleware.
originalWriter := logx.Reset()
writer := NewSensitiveLogger(originalWriter)
logx.SetWriter(writer)
logx.Infov(Message{
Name: "foo",
Password: "shouldNotAppear",
Message: "bar",
})
// more code
}
```
전체 코드: [https://github.com/zeromicro/zero-examples/blob/main/logx/filterfields/main.go](https://github.com/zeromicro/zero-examples/blob/main/logx/filterfields/main.go)
## 더 많은 예제
[https://github.com/zeromicro/zero-examples/tree/main/logx](https://github.com/zeromicro/zero-examples/tree/main/logx)
## 별을 눌러주세요! ⭐
이 프로젝트가 마음에 들거나 학습 또는 자체 솔루션을 시작하는 데 사용 중이라면 star를 눌러주세요. 감사합니다!
+1 -1
View File
@@ -2,7 +2,7 @@
# logx
English | [简体中文](readme-cn.md)
English | [简体中文](readme-cn.md) | [한국어](readme-ko.md)
## logx configurations
+3 -3
View File
@@ -1,6 +1,7 @@
package mapping
import (
"cmp"
"encoding/json"
"errors"
"fmt"
@@ -12,7 +13,6 @@ import (
"sync"
"github.com/zeromicro/go-zero/core/lang"
"github.com/zeromicro/go-zero/core/stringx"
)
const (
@@ -278,7 +278,7 @@ func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fie
cache, ok := optionsCache[value]
cacheLock.RUnlock()
if ok {
return stringx.TakeOne(cache.key, field.Name), cache.options, cache.err
return cmp.Or(cache.key, field.Name), cache.options, cache.err
}
key, options, err := doParseKeyAndOptions(field, value)
@@ -290,7 +290,7 @@ func parseKeyAndOptions(tagName string, field reflect.StructField) (string, *fie
}
cacheLock.Unlock()
return stringx.TakeOne(key, field.Name), options, err
return cmp.Or(key, field.Name), options, err
}
// support below notations:
+7
View File
@@ -29,3 +29,10 @@ func TestCalcDiffEntropy(t *testing.T) {
}
assert.True(t, CalcEntropy(m) < .99)
}
func TestCalcEntropySingleItem(t *testing.T) {
m := map[any]int{
"only": 42,
}
assert.Equal(t, float64(1), CalcEntropy(m))
}
+1
View File
@@ -1,5 +1,6 @@
package mathx
// Numerical is a constraint that permits any numeric type.
type Numerical interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
+4 -4
View File
@@ -1,10 +1,10 @@
# mapreduce
[English](readme.md) | 简体中文
[English](readme.md) | 简体中文 | [한국어](readme-ko.md)
## 为什么需要 MapReduce
在实际的业务场景中我们常常需要从不同的 rpc 服务中获取相应属性来组装成复杂对象。
在实际的业务场景中我们常常需要从不同的 RPC 服务中获取相应属性来组装成复杂对象。
比如要查询商品详情:
@@ -13,9 +13,9 @@
3. 价格服务-查询价格属性
4. 营销服务-查询营销属性
如果是串行调用的话响应时间会随着 rpc 调用次数呈线性增长,所以我们要优化性能一般会将串行改并行。
如果是串行调用的话响应时间会随着 RPC 调用次数呈线性增长,所以我们要优化性能一般会将串行改并行。
简单的场景下使用 `WaitGroup` 也能够满足需求,但是如果我们需要对 rpc 调用返回的数据进行校验、数据加工转换、数据汇总呢?继续使用 `WaitGroup` 就有点力不从心了go 的官方库中并没有这种工具java 中提供了 CompleteFuture我们依据 MapReduce 架构思想实现了进程内的数据批处理 MapReduce 并发工具类。
简单的场景下使用 `WaitGroup` 也能够满足需求,但是如果我们需要对 RPC 调用返回的数据进行校验、数据加工转换、数据汇总呢?继续使用 `WaitGroup` 就有点力不从心了go 的官方库中并没有这种工具java 中提供了 CompletableFuture我们依据 MapReduce 架构思想实现了进程内的数据批处理 MapReduce 并发工具类。
## 设计思路
+89
View File
@@ -0,0 +1,89 @@
<img align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
# mapreduce
[English](readme.md) | [简体中文](readme-cn.md) | 한국어
## MapReduce가 필요한 이유
실제 비즈니스 시나리오에서는 서로 다른 RPC 서비스에서 속성을 가져와 복잡한 객체를 조립해야 하는 경우가 많습니다.
예를 들어 상품 상세 정보를 조회한다고 해봅시다.
1. 상품 서비스 - 상품 속성 조회
2. 재고 서비스 - 재고 속성 조회
3. 가격 서비스 - 가격 속성 조회
4. 마케팅 서비스 - 마케팅 속성 조회
직렬 호출이라면 RPC 호출 횟수에 따라 응답 시간이 선형적으로 증가하므로, 일반적으로 응답 시간을 최적화하기 위해 직렬 호출을 병렬 호출로 바꿉니다.
단순한 시나리오에서는 `WaitGroup`만으로도 요구 사항을 충족할 수 있습니다. 하지만 RPC 호출이 반환한 데이터를 검증하거나, 데이터를 처리하거나, 데이터를 집계해야 한다면 어떻게 해야 할까요? Go 표준 라이브러리에는 이런 도구가 없습니다(Java에는 CompletableFuture가 제공됩니다). 그래서 우리는 MapReduce 아키텍처를 기반으로 프로세스 내부 데이터 배치 처리를 위한 MapReduce 동시성 도구를 구현했습니다.
## 설계 아이디어
동시성 도구가 필요한 비즈니스 시나리오를 작성자의 관점에서 정리해봅시다.
1. 상품 상세 조회: 여러 서비스를 동시에 호출해 상품 속성을 조합하고, 호출 오류가 발생하면 즉시 종료할 수 있어야 합니다.
2. 상품 상세 페이지에서 사용자 쿠폰 자동 추천: 쿠폰을 동시에 검증하고, 검증에 실패한 쿠폰은 자동으로 제외하며, 나머지를 모두 반환할 수 있어야 합니다.
위 시나리오는 모두 입력 데이터를 처리한 뒤 정제된 데이터를 출력하는 과정입니다. 데이터 처리에는 아주 고전적인 비동기 패턴인 생산자-소비자 패턴이 있습니다. 따라서 데이터 배치 처리의 생명 주기를 추상화하면 대략 세 단계로 나눌 수 있습니다.
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/mapreduce-serial-en.png" width="500">
1. 데이터 생성(generator)
2. 데이터 처리(mapper)
3. 데이터 집계(reducer)
데이터 생성은 필수 단계이고, 데이터 처리와 데이터 집계는 선택 단계입니다. 데이터 생성과 처리는 동시 호출을 지원하며, 데이터 집계는 기본적으로 순수 메모리 작업이므로 단일 고루틴으로 처리할 수 있습니다.
서로 다른 데이터 처리 단계가 서로 다른 고루틴에서 수행되므로, 고루틴 간 통신을 위해 채널을 사용하는 것이 자연스럽습니다.
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/mapreduce-en.png" width="500">
언제든 프로세스를 종료하려면 어떻게 해야 할까요?
간단합니다. 고루틴에서 채널 또는 전달된 context의 완료 신호를 감시하면 됩니다.
## 간단한 예시
동시성을 시뮬레이션하며 제곱합을 계산합니다.
```go
package main
import (
"fmt"
"log"
"github.com/zeromicro/go-zero/core/mr"
)
func main() {
val, err := mr.MapReduce(func(source chan<- int) {
// generator
for i := 0; i < 10; i++ {
source <- i
}
}, func(i int, writer mr.Writer[int], cancel func(error)) {
// mapper
writer.Write(i * i)
}, func(pipe <-chan int, writer mr.Writer[int], cancel func(error)) {
// reducer
var sum int
for i := range pipe {
sum += i
}
writer.Write(sum)
})
if err != nil {
log.Fatal(err)
}
fmt.Println("result:", val)
}
```
더 많은 예제: [https://github.com/zeromicro/zero-examples/tree/main/mapreduce](https://github.com/zeromicro/zero-examples/tree/main/mapreduce)
## 별을 눌러주세요! ⭐
이 프로젝트가 마음에 들거나 학습 또는 자체 솔루션을 시작하는 데 사용 중이라면 star를 눌러주세요. 감사합니다!
+4 -4
View File
@@ -2,11 +2,11 @@
# mapreduce
English | [简体中文](readme-cn.md)
English | [简体中文](readme-cn.md) | [한국어](readme-ko.md)
## Why MapReduce is needed
In practical business scenarios we often need to get the corresponding properties from different rpc services to assemble complex objects.
In practical business scenarios we often need to get the corresponding properties from different RPC services to assemble complex objects.
For example, to query product details.
@@ -15,9 +15,9 @@ For example, to query product details.
3. price service - query price attributes
4. marketing service - query marketing properties
If it is a serial call, the response time will increase linearly with the number of rpc calls, so we will generally change serial to parallel to optimize response time.
If it is a serial call, the response time will increase linearly with the number of RPC calls, so we will generally change serial to parallel to optimize response time.
Simple scenarios using `WaitGroup` can also meet the needs, but what if we need to check the data returned by the rpc call, data processing, data aggregation? The official go library does not have such a tool (CompleteFuture is provided in java), so we implemented an in-process data batching MapReduce concurrent tool based on the MapReduce architecture.
Simple scenarios using `WaitGroup` can also meet the needs, but what if we need to check the data returned by the RPC call, data processing, data aggregation? The official go library does not have such a tool (CompletableFuture is provided in java), so we implemented an in-process data batching MapReduce concurrent tool based on the MapReduce architecture.
## Design ideas
+1 -1
View File
@@ -542,7 +542,7 @@ func mustNewTestModel(collection mon.Collection, c cache.CacheConf, opts ...cach
}
}
// NewNodeModel returns a test Model with a cache node.
// mustNewTestNodeModel returns a test Model with a cache node.
func mustNewTestNodeModel(collection mon.Collection, rds *redis.Redis, opts ...cache.Option) *Model {
c := cache.NewNode(rds, singleFlight, stats, mongo.ErrNoDocuments, opts...)
return &Model{
+35
View File
@@ -65,6 +65,7 @@ type (
// RedisNode interface represents a redis node.
RedisNode interface {
red.Cmdable
Do(ctx context.Context, args ...any) *red.Cmd
}
// GeoLocation is used with GeoAdd to add geospatial location.
@@ -405,6 +406,25 @@ func (s *Redis) DelCtx(ctx context.Context, keys ...string) (int, error) {
return int(v), nil
}
// Do executes a generic redis command with given arguments.
func (s *Redis) Do(args ...any) (any, error) {
return s.DoCtx(context.Background(), args...)
}
// DoCtx executes a generic redis command with given arguments using the provided context.
func (s *Redis) DoCtx(ctx context.Context, args ...any) (any, error) {
if len(args) == 0 {
return nil, errors.New("missing redis command")
}
conn, err := getRedis(s)
if err != nil {
return nil, err
}
return conn.Do(ctx, args...).Result()
}
// Eval is the implementation of redis eval command.
func (s *Redis) Eval(script string, keys []string, args ...any) (any, error) {
return s.EvalCtx(context.Background(), script, keys, args...)
@@ -1848,6 +1868,21 @@ func (s *Redis) XGroupCreateCtx(ctx context.Context, stream string, group string
return conn.XGroupCreate(ctx, stream, group, start).Result()
}
// XGroupSetID sets the last delivered ID for a Redis stream consumer group.
func (s *Redis) XGroupSetID(stream, group, start string) (string, error) {
return s.XGroupSetIDCtx(context.Background(), stream, group, start)
}
// XGroupSetIDCtx is the context-aware version of XGroupSetID.
func (s *Redis) XGroupSetIDCtx(ctx context.Context, stream, group, start string) (string, error) {
conn, err := getRedis(s)
if err != nil {
return "", err
}
return conn.XGroupSetID(ctx, stream, group, start).Result()
}
// XInfoConsumers returns information about consumers in a Redis stream consumer group.
func (s *Redis) XInfoConsumers(stream string, group string) ([]red.XInfoConsumer, error) {
return s.XInfoConsumersCtx(context.Background(), stream, group)
+55
View File
@@ -275,6 +275,36 @@ func TestRedis_Eval(t *testing.T) {
})
}
func TestRedis_Do(t *testing.T) {
runOnRedis(t, func(client *Redis) {
_, err := newRedis(client.Addr, badType()).Do("PING")
assert.NotNil(t, err)
pong, err := client.Do("PING")
assert.Nil(t, err)
assert.Equal(t, "PONG", pong)
ok, err := client.Do("SET", "key1", "value1")
assert.Nil(t, err)
assert.Equal(t, "OK", ok)
val, err := client.Do("GET", "key1")
assert.Nil(t, err)
assert.Equal(t, "value1", val)
_, err = client.Do("GET", "not_exist")
assert.Equal(t, Nil, err)
_, err = client.Do()
assert.NotNil(t, err)
ctx, cancel := context.WithCancel(context.Background())
cancel()
_, err = client.DoCtx(ctx, "PING")
assert.Equal(t, context.Canceled, err)
})
}
func TestRedis_ScriptRun(t *testing.T) {
runOnRedis(t, func(client *Redis) {
sc := NewScript(`redis.call("EXISTS", KEYS[1])`)
@@ -2264,6 +2294,31 @@ func TestRedisXGroupCreate(t *testing.T) {
})
}
func TestRedisXGroupSetID(t *testing.T) {
runOnRedis(t, func(client *Redis) {
_, err := newRedis(client.Addr, badType()).XGroupSetID("Source", "Destination", "0")
assert.NotNil(t, err)
redisCli := newRedis(client.Addr)
stream := "aa"
group := "bb"
_, err = redisCli.XGroupCreateMkStream(stream, group, "0")
assert.Nil(t, err)
res, err := redisCli.XGroupSetID(stream, group, "0")
assert.Empty(t, res)
assert.ErrorContains(t, err, "not supported")
_, err = newRedis(client.Addr, badType()).XGroupSetIDCtx(context.Background(), stream, group, "0")
assert.NotNil(t, err)
res, err = redisCli.XGroupSetIDCtx(context.Background(), stream, group, "0")
assert.Empty(t, res)
assert.ErrorContains(t, err, "not supported")
})
}
func TestRedisXInfo(t *testing.T) {
runOnRedis(t, func(client *Redis) {
_, err := newRedis(client.Addr, badType()).XInfoStream("Source")
+14 -14
View File
@@ -3,6 +3,7 @@ package stringx
import (
"errors"
"slices"
"strings"
"unicode"
"github.com/zeromicro/go-zero/core/lang"
@@ -21,24 +22,22 @@ func Contains(list []string, str string) bool {
return slices.Contains(list, str)
}
// Filter filters chars from s with given filter function.
func Filter(s string, filter func(r rune) bool) string {
var n int
chars := []rune(s)
for i, x := range chars {
if n < i {
chars[n] = x
// Filter filters chars from s with given remove function.
func Filter(s string, remove func(r rune) bool) string {
return strings.Map(func(r rune) rune {
if remove(r) {
return -1
}
if !filter(x) {
n++
}
}
return string(chars[:n])
return r
}, s)
}
// FirstN returns first n runes from s.
func FirstN(s string, n int, ellipsis ...string) string {
if n < 0 {
return ""
}
var i int
for j := range s {
@@ -133,7 +132,7 @@ func Substr(str string, start, stop int) (string, error) {
return "", ErrInvalidStartPosition
}
if stop < 0 || stop > length {
if stop < 0 || stop > length || start > stop {
return "", ErrInvalidStopPosition
}
@@ -141,6 +140,7 @@ func Substr(str string, start, stop int) (string, error) {
}
// TakeOne returns valid string if not empty or later one.
// Deprecated: use cmp.Or instead.
func TakeOne(valid, or string) string {
if len(valid) > 0 {
return valid
+71
View File
@@ -29,6 +29,40 @@ func TestContainsString(t *testing.T) {
}
}
func TestHasEmpty(t *testing.T) {
cases := []struct {
args []string
expect bool
}{
{
args: []string{"a", "b", "c"},
expect: false,
},
{
args: []string{"a", "", "c"},
expect: true,
},
{
args: []string{""},
expect: true,
},
{
args: []string{},
expect: false,
},
{
args: nil,
expect: false,
},
}
for _, each := range cases {
t.Run(path.Join(each.args...), func(t *testing.T) {
assert.Equal(t, each.expect, HasEmpty(each.args...))
})
}
}
func TestNotEmpty(t *testing.T) {
cases := []struct {
args []string
@@ -92,6 +126,24 @@ func TestFilter(t *testing.T) {
}
}
func BenchmarkFilter(b *testing.B) {
b.Run("true", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
Filter(`ab,cd,ef`, func(r rune) bool { return r == ',' })
}
})
b.Run("false", func(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
Filter(`ab,cd,ef`, func(r rune) bool { return r == '!' })
}
})
}
func TestFirstN(t *testing.T) {
tests := []struct {
name string
@@ -138,6 +190,18 @@ func TestFirstN(t *testing.T) {
n: 10,
expect: "我是中国人",
},
{
name: "negative n returns empty string",
input: "hello world",
n: -1,
expect: "",
},
{
name: "negative n with utf8 returns empty string",
input: "我是中国人",
n: -5,
expect: "",
},
}
for _, test := range tests {
@@ -292,6 +356,13 @@ func TestSubstr(t *testing.T) {
err: ErrInvalidStopPosition,
expect: "",
},
{
input: "hello",
start: 3,
stop: 2,
err: ErrInvalidStopPosition,
expect: "",
},
}
for _, each := range cases {
+73 -61
View File
@@ -1,126 +1,138 @@
module github.com/zeromicro/go-zero
go 1.23.0
go 1.24.0
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/alicebob/miniredis/v2 v2.36.1
github.com/alicebob/miniredis/v2 v2.38.0
github.com/fatih/color v1.18.0
github.com/fullstorydev/grpcurl v1.9.3
github.com/go-sql-driver/mysql v1.9.3
github.com/go-sql-driver/mysql v1.10.0
github.com/golang-jwt/jwt/v4 v4.5.2
github.com/golang/protobuf v1.5.4
github.com/google/uuid v1.6.0
github.com/grafana/pyroscope-go v1.2.7
github.com/jackc/pgx/v5 v5.7.4
github.com/jhump/protoreflect v1.17.0
github.com/modelcontextprotocol/go-sdk v1.3.0
github.com/pelletier/go-toml/v2 v2.2.4
github.com/grafana/pyroscope-go v1.3.0
github.com/jackc/pgx/v5 v5.8.0
github.com/jhump/protoreflect v1.18.0
github.com/modelcontextprotocol/go-sdk v1.4.0
github.com/pelletier/go-toml/v2 v2.4.2
github.com/prometheus/client_golang v1.23.2
github.com/redis/go-redis/v9 v9.17.3
github.com/redis/go-redis/v9 v9.21.0
github.com/spaolacci/murmur3 v1.1.0
github.com/stretchr/testify v1.11.1
go.etcd.io/etcd/api/v3 v3.5.15
go.etcd.io/etcd/client/v3 v3.5.15
go.mongodb.org/mongo-driver/v2 v2.5.0
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0
go.opentelemetry.io/otel/exporters/zipkin v1.24.0
go.opentelemetry.io/otel/sdk v1.24.0
go.opentelemetry.io/otel/trace v1.24.0
github.com/titanous/json5 v1.0.0
go.etcd.io/etcd/api/v3 v3.5.21
go.etcd.io/etcd/client/v3 v3.5.21
go.mongodb.org/mongo-driver/v2 v2.7.0
go.opentelemetry.io/otel v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0
go.opentelemetry.io/otel/exporters/zipkin v1.40.0
go.opentelemetry.io/otel/sdk v1.40.0
go.opentelemetry.io/otel/trace v1.40.0
go.uber.org/automaxprocs v1.6.0
go.uber.org/goleak v1.3.0
go.uber.org/mock v0.6.0
golang.org/x/net v0.43.0
golang.org/x/sys v0.35.0
golang.org/x/time v0.10.0
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d
google.golang.org/grpc v1.65.0
golang.org/x/net v0.50.0
golang.org/x/sys v0.41.0
golang.org/x/time v0.14.0
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
gopkg.in/cheggaaa/pb.v1 v1.0.28
gopkg.in/h2non/gock.v1 v1.1.2
gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.29.3
k8s.io/apimachinery v0.29.4
k8s.io/client-go v0.29.3
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2
k8s.io/api v0.34.3
k8s.io/apimachinery v0.34.3
k8s.io/client-go v0.34.3
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bufbuild/protocompile v0.14.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/envoyproxy/go-control-plane v0.12.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/jsonschema-go v0.4.2 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.10 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/segmentio/asm v1.1.3 // indirect
github.com/segmentio/encoding v0.5.3 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.2.0 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
+170 -134
View File
@@ -1,9 +1,9 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/alicebob/miniredis/v2 v2.36.1 h1:Dvc5oAnNOr7BIfPn7tF269U8DvRW1dBG2D5n0WrfYMI=
github.com/alicebob/miniredis/v2 v2.36.1/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
github.com/alicebob/miniredis/v2 v2.38.0 h1:nZAzCR+Lj+Vxk4ZXzm2NuKq2O33RXj1XxJ2e2uP9jiw=
github.com/alicebob/miniredis/v2 v2.38.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
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 v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -14,12 +14,12 @@ github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
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.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
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/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-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
@@ -28,76 +28,78 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
github.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=
github.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fullstorydev/grpcurl v1.9.3 h1:PC1Xi3w+JAvEE2Tg2Gf2RfVgPbf9+tbuQr1ZkyVU3jk=
github.com/fullstorydev/grpcurl v1.9.3/go.mod h1:/b4Wxe8bG6ndAjlfSUjwseQReUDUvBJiFEB7UllOlUE=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
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.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8=
github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
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/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac=
github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/grafana/pyroscope-go v1.3.0 h1:t3Jehad8vvqN4oRAB0LdmfQ5ZSUXQw3asoft+K4GAT8=
github.com/grafana/pyroscope-go v1.3.0/go.mod h1:XA7I3usNx+UdjOZfQnl1WV8y924vsJo9KIVrKB+9jx4=
github.com/grafana/pyroscope-go/godeltaprof v0.1.10 h1:dvhndEbyavTb59vFCd6PsrAG5qi69/qZZtegh/TJKSY=
github.com/grafana/pyroscope-go/godeltaprof v0.1.10/go.mod h1:XnWRGg2XO5uxZdiz1rfeJH6w1eZ+YICCBVXNWOfH86g=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/jhump/protoreflect v1.18.0 h1:TOz0MSR/0JOZ5kECB/0ufGnC2jdsgZ123Rd/k4Z5/2w=
github.com/jhump/protoreflect v1.18.0/go.mod h1:ezWcltJIVF4zYdIFM+D/sHV4Oh5LNU08ORzCGfwvTz8=
github.com/jhump/protoreflect/v2 v2.0.0-beta.1 h1:Dw1rslK/VotaUGYsv53XVWITr+5RCPXfvvlGrM/+B6w=
github.com/jhump/protoreflect/v2 v2.0.0-beta.1/go.mod h1:D9LBEowZyv8/iSu97FU2zmXG3JxVTmNw21mu63niFzU=
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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -105,8 +107,10 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
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/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -125,27 +129,32 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
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/modelcontextprotocol/go-sdk v1.3.0 h1:gMfZkv3DzQF5q/DcQePo5rahEY+sguyPfXDfNBcT0Zs=
github.com/modelcontextprotocol/go-sdk v1.3.0/go.mod h1:AnQ//Qc6+4nIyyrB4cxBU7UW9VibK4iOZBeyP/rF1IE=
github.com/modelcontextprotocol/go-sdk v1.4.0 h1:u0kr8lbJc1oBcawK7Df+/ajNMpIDFE41OEPxdeTLOn8=
github.com/modelcontextprotocol/go-sdk v1.4.0/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E=
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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.4.2 h1:M2fKKbmyvI+hGId/D0W64qDBMVhJnNR10O5gIbMc//Q=
github.com/pelletier/go-toml/v2 v2.4.2/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 h1:KPpdlQLZcHfTMQRi6bFQ7ogNO0ltFT4PmtwTLW4W+14=
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
@@ -158,16 +167,24 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E=
github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
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/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w=
github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spiffe/go-spiffe/v2 v2.6.0 h1:l+DolpxNWYgruGQVV0xsfeya3CsC7m8iBzDnMpsbLuo=
github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -180,6 +197,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/titanous/json5 v1.0.0 h1:hJf8Su1d9NuI/ffpxgxQfxh/UiBFZX7bMPid0rIL/7s=
github.com/titanous/json5 v1.0.0/go.mod h1:7JH1M8/LHKc6cyP5o5g3CSaRj+mBrIimTxzpvmckH8c=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs=
@@ -195,36 +216,42 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.mongodb.org/mongo-driver/v2 v2.7.0 h1:RO+zqavD2/GCL3cxOMyZhx6R9Irzr8/6gsoqx5tcY/c=
go.mongodb.org/mongo-driver/v2 v2.7.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8=
go.opentelemetry.io/otel/exporters/zipkin v1.40.0 h1:zu+I4j+FdO6xIxBVPeuncQVbjxUM4LiMgv6GwGe9REE=
go.opentelemetry.io/otel/exporters/zipkin v1.40.0/go.mod h1:zS6cC4nFBYXbu18e7aLfMzubBjOiN7ZcROu477qtMf8=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
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.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -237,12 +264,14 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -252,16 +281,16 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -271,37 +300,39 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -309,31 +340,36 @@ 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/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk=
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y=
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 h1:OfgiEo21hGiwx1oJUU5MpEaeOEg6coWndBkZF/lkFuE=
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4=
k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk=
k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE=
k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A=
k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM=
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+51
View File
@@ -3,12 +3,63 @@ package encoding
import (
"bytes"
"encoding/json"
"fmt"
"math"
"github.com/pelletier/go-toml/v2"
"github.com/titanous/json5"
"github.com/zeromicro/go-zero/core/lang"
"gopkg.in/yaml.v2"
)
// Json5ToJson converts JSON5 data into its JSON representation.
func Json5ToJson(data []byte) ([]byte, error) {
var val any
if err := json5.Unmarshal(data, &val); err != nil {
return nil, err
}
// Validate that there are no unsupported values like Infinity or NaN
if err := validateJSONCompatible(val); err != nil {
return nil, err
}
return encodeToJSON(val)
}
// validateJSONCompatible checks if the value can be represented in standard JSON.
// JSON5 allows Infinity and NaN, but standard JSON does not support these values.
func validateJSONCompatible(val any) error {
switch v := val.(type) {
case float64:
if math.IsInf(v, 0) {
return fmt.Errorf("JSON5 value Infinity cannot be represented in standard JSON")
}
if math.IsNaN(v) {
return fmt.Errorf("JSON5 value NaN cannot be represented in standard JSON")
}
case []any:
for _, item := range v {
if err := validateJSONCompatible(item); err != nil {
return err
}
}
case map[string]any:
for _, value := range v {
if err := validateJSONCompatible(value); err != nil {
return err
}
}
case map[any]any:
for _, value := range v {
if err := validateJSONCompatible(value); err != nil {
return err
}
}
}
return nil
}
// TomlToJson converts TOML data into its JSON representation.
func TomlToJson(data []byte) ([]byte, error) {
var val any
+140
View File
@@ -1,6 +1,7 @@
package encoding
import (
"math"
"testing"
"github.com/stretchr/testify/assert"
@@ -116,3 +117,142 @@ func TestYamlToJsonSlice(t *testing.T) {
assert.Equal(t, `{"foo":["bar","baz"]}
`, string(b))
}
func TestJson5ToJson(t *testing.T) {
tests := []struct {
name string
input string
expect string
}{
{
name: "standard json",
input: `{"a":"foo","b":1,"c":"${FOO}","d":"abcd!@#$112"}`,
expect: "{\"a\":\"foo\",\"b\":1,\"c\":\"${FOO}\",\"d\":\"abcd!@#$112\"}\n",
},
{
name: "json5 with comments",
input: `{/*comment*/"a":"foo","b":1}`,
expect: "{\"a\":\"foo\",\"b\":1}\n",
},
{
name: "json5 with trailing commas",
input: `{"a":"foo","b":1,}`,
expect: "{\"a\":\"foo\",\"b\":1}\n",
},
{
name: "json5 with unquoted keys",
input: `{a:"foo",b:1}`,
expect: "{\"a\":\"foo\",\"b\":1}\n",
},
{
name: "json5 with single quotes",
input: `{"a":'foo',"b":1}`,
expect: "{\"a\":\"foo\",\"b\":1}\n",
},
{
name: "json5 with line comments",
input: "{\n// This is a comment\n\"a\":\"foo\",\n\"b\":1\n}",
expect: "{\"a\":\"foo\",\"b\":1}\n",
},
{
name: "json5 all features combined",
input: "{\n// comment\na: 'foo', // trailing comma\nb: 1,\n}",
expect: "{\"a\":\"foo\",\"b\":1}\n",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
got, err := Json5ToJson([]byte(test.input))
assert.NoError(t, err)
assert.Equal(t, test.expect, string(got))
})
}
}
func TestJson5ToJsonError(t *testing.T) {
// Invalid JSON5: unquoted string value
_, err := Json5ToJson([]byte("{a: foo}"))
assert.Error(t, err)
}
func TestJson5ToJsonInfinity(t *testing.T) {
// JSON5 allows Infinity but standard JSON does not
_, err := Json5ToJson([]byte(`{value: Infinity}`))
assert.Error(t, err)
assert.Contains(t, err.Error(), "Infinity")
// Negative infinity
_, err = Json5ToJson([]byte(`{value: -Infinity}`))
assert.Error(t, err)
assert.Contains(t, err.Error(), "Infinity")
// Infinity in array
_, err = Json5ToJson([]byte(`{values: [1, Infinity, 3]}`))
assert.Error(t, err)
assert.Contains(t, err.Error(), "Infinity")
}
func TestJson5ToJsonNaN(t *testing.T) {
// JSON5 allows NaN but standard JSON does not
_, err := Json5ToJson([]byte(`{value: NaN}`))
assert.Error(t, err)
assert.Contains(t, err.Error(), "NaN")
// NaN in nested structure
_, err = Json5ToJson([]byte(`{nested: {value: NaN}}`))
assert.Error(t, err)
assert.Contains(t, err.Error(), "NaN")
}
func TestJson5ToJsonSlice(t *testing.T) {
b, err := Json5ToJson([]byte(`{
// comment
foo: [
'bar',
"baz", // trailing comma
],
}`))
assert.NoError(t, err)
assert.Equal(t, `{"foo":["bar","baz"]}
`, string(b))
}
func TestValidateJSONCompatible(t *testing.T) {
// Test float64 types
assert.NoError(t, validateJSONCompatible(float64(1.5)))
assert.Error(t, validateJSONCompatible(math.Inf(1)))
assert.Error(t, validateJSONCompatible(math.Inf(-1)))
assert.Error(t, validateJSONCompatible(math.NaN()))
// Test arrays with invalid values
assert.Error(t, validateJSONCompatible([]any{1, math.Inf(1), 3}))
assert.Error(t, validateJSONCompatible([]any{1, math.NaN(), 3}))
assert.NoError(t, validateJSONCompatible([]any{1, 2, 3}))
// Test map[string]any with invalid values
assert.Error(t, validateJSONCompatible(map[string]any{"value": math.Inf(1)}))
assert.Error(t, validateJSONCompatible(map[string]any{"value": math.NaN()}))
assert.NoError(t, validateJSONCompatible(map[string]any{"value": 1.5}))
// Test map[any]any with invalid values
assert.Error(t, validateJSONCompatible(map[any]any{"value": math.Inf(1)}))
assert.Error(t, validateJSONCompatible(map[any]any{"value": math.NaN()}))
assert.NoError(t, validateJSONCompatible(map[any]any{"value": 1.5}))
// Test nested structures
assert.Error(t, validateJSONCompatible(map[string]any{
"nested": map[string]any{"value": math.Inf(1)},
}))
assert.Error(t, validateJSONCompatible([]any{
map[string]any{"value": math.NaN()},
}))
// Test valid values of various types
assert.NoError(t, validateJSONCompatible("string"))
assert.NoError(t, validateJSONCompatible(42))
assert.NoError(t, validateJSONCompatible(true))
assert.NoError(t, validateJSONCompatible(nil))
}
+33
View File
@@ -0,0 +1,33 @@
package mcp
import "net/http"
// RequestMetadataExtractor extracts request metadata for downstream handlers.
type RequestMetadataExtractor func(*http.Request) RequestMetadata
// McpOption customizes MCP server construction.
type McpOption interface {
apply(*serverOptions)
}
type mcpOptionFunc func(*serverOptions)
func (f mcpOptionFunc) apply(opts *serverOptions) {
f(opts)
}
type serverOptions struct {
requestMetadataExtractor RequestMetadataExtractor
}
func defaultServerOptions() serverOptions {
return serverOptions{}
}
// WithRequestMetadataExtractor installs an extractor that runs for each incoming
// MCP HTTP request, and stores the extracted metadata into handler context.
func WithRequestMetadataExtractor(extractor RequestMetadataExtractor) McpOption {
return mcpOptionFunc(func(opts *serverOptions) {
opts.requestMetadataExtractor = extractor
})
}
+30
View File
@@ -15,6 +15,7 @@ This package provides a go-zero integration for the [Model Context Protocol (MCP
- **CORS Support**: Configurable CORS settings for cross-origin requests
- **Type-Safe Tool Handlers**: Generic tool handlers with automatic JSON schema generation
- **Prompts and Resources**: Full support for MCP prompts and resources
- **Request Metadata Bridge**: Optional request metadata extraction into handler context
## Quick Start
@@ -220,6 +221,35 @@ mcp:
messageEndpoint: /message
```
## Request Metadata Bridge
For multi-tenant or request-context-aware tools, you can extract selected HTTP request metadata once at the transport boundary and read it from `context.Context` in handlers.
```go
server := mcp.NewMcpServerWithOptions(c,
mcp.WithRequestMetadataExtractor(mcp.DefaultRequestMetadataExtractor),
)
handler := func(ctx context.Context, req *mcp.CallToolRequest, args SomeArgs) (*mcp.CallToolResult, any, error) {
tenant, _ := mcp.HeaderFromContext(ctx, "X-Tenant-Id")
traceID, _ := mcp.QueryFromContext(ctx, "trace")
scope, _ := mcp.PathFromContext(ctx, "scope")
_ = tenant
_ = traceID
_ = scope
return &mcp.CallToolResult{}, nil, nil
}
```
Available helpers:
- `RequestMetadataFromContext(ctx)`
- `HeaderFromContext(ctx, key)`
- `QueryFromContext(ctx, key)`
- `PathFromContext(ctx, key)`
## Configuration Options
| Field | Type | Default | Description |
+150
View File
@@ -0,0 +1,150 @@
package mcp
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/pathvar"
)
// RequestMetadata carries selected request-scoped values into MCP handlers.
type RequestMetadata struct {
Headers map[string][]string
Query map[string][]string
Path map[string]string
}
type requestMetadataCtxKey struct{}
// RequestMetadataFromContext returns metadata extracted at the transport boundary.
func RequestMetadataFromContext(ctx context.Context) (RequestMetadata, bool) {
metadata, ok := requestMetadataFromContext(ctx)
if !ok {
return RequestMetadata{}, false
}
return normalizeRequestMetadata(metadata), true
}
// HeaderFromContext returns the first header value for key.
func HeaderFromContext(ctx context.Context, key string) (string, bool) {
metadata, ok := requestMetadataFromContext(ctx)
if !ok {
return "", false
}
vals := metadata.Headers[http.CanonicalHeaderKey(key)]
if len(vals) == 0 {
return "", false
}
return vals[0], true
}
// QueryFromContext returns the first query value for key.
func QueryFromContext(ctx context.Context, key string) (string, bool) {
metadata, ok := requestMetadataFromContext(ctx)
if !ok {
return "", false
}
vals := metadata.Query[key]
if len(vals) == 0 {
return "", false
}
return vals[0], true
}
// PathFromContext returns the path variable value for key.
func PathFromContext(ctx context.Context, key string) (string, bool) {
metadata, ok := requestMetadataFromContext(ctx)
if !ok {
return "", false
}
val, ok := metadata.Path[key]
if !ok {
return "", false
}
return val, true
}
func requestMetadataFromContext(ctx context.Context) (RequestMetadata, bool) {
metadata, ok := ctx.Value(requestMetadataCtxKey{}).(RequestMetadata)
if !ok {
return RequestMetadata{}, false
}
return metadata, true
}
// DefaultRequestMetadataExtractor extracts headers, query values, and path variables.
func DefaultRequestMetadataExtractor(r *http.Request) RequestMetadata {
metadata := RequestMetadata{
Headers: make(map[string][]string, len(r.Header)),
Query: make(map[string][]string),
Path: clonePathVars(pathvar.Vars(r)),
}
for key, vals := range r.Header {
metadata.Headers[http.CanonicalHeaderKey(key)] = append([]string(nil), vals...)
}
if r.URL != nil {
for key, vals := range r.URL.Query() {
metadata.Query[key] = append([]string(nil), vals...)
}
}
return metadata
}
func normalizeRequestMetadata(metadata RequestMetadata) RequestMetadata {
return RequestMetadata{
Headers: cloneCanonicalHeaderValues(metadata.Headers),
Query: cloneHeaderValues(metadata.Query),
Path: clonePathVars(metadata.Path),
}
}
func cloneHeaderValues(values map[string][]string) map[string][]string {
if len(values) == 0 {
return nil
}
cloned := make(map[string][]string, len(values))
for key, vals := range values {
cloned[key] = append([]string(nil), vals...)
}
return cloned
}
func cloneCanonicalHeaderValues(values map[string][]string) map[string][]string {
if len(values) == 0 {
return nil
}
cloned := make(map[string][]string, len(values))
for key, vals := range values {
canonical := http.CanonicalHeaderKey(key)
cloned[canonical] = append(cloned[canonical], vals...)
}
return cloned
}
func clonePathVars(values map[string]string) map[string]string {
if len(values) == 0 {
return nil
}
cloned := make(map[string]string, len(values))
for key, val := range values {
cloned[key] = val
}
return cloned
}
+185
View File
@@ -0,0 +1,185 @@
package mcp
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/rest/pathvar"
)
func TestDefaultRequestMetadataExtractor(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/sse?tenant=t1&trace=abc", nil)
req.Header.Add("X-Tenant-Id", "tenant-from-header")
req = pathvar.WithVars(req, map[string]string{"tool": "sum"})
metadata := DefaultRequestMetadataExtractor(req)
header, ok := metadata.Headers["X-Tenant-Id"]
assert.True(t, ok)
assert.Equal(t, []string{"tenant-from-header"}, header)
assert.Equal(t, []string{"t1"}, metadata.Query["tenant"])
assert.Equal(t, "sum", metadata.Path["tool"])
}
func TestRequestMetadataContextHelpers(t *testing.T) {
ctx := context.WithValue(context.Background(), requestMetadataCtxKey{}, RequestMetadata{
Headers: map[string][]string{"X-Trace-Id": {"trace-1"}},
Query: map[string][]string{"tenant": {"foo"}},
Path: map[string]string{"scope": "prod"},
})
metadata, ok := RequestMetadataFromContext(ctx)
assert.True(t, ok)
assert.Equal(t, []string{"trace-1"}, metadata.Headers["X-Trace-Id"])
header, ok := HeaderFromContext(ctx, "x-trace-id")
assert.True(t, ok)
assert.Equal(t, "trace-1", header)
query, ok := QueryFromContext(ctx, "tenant")
assert.True(t, ok)
assert.Equal(t, "foo", query)
path, ok := PathFromContext(ctx, "scope")
assert.True(t, ok)
assert.Equal(t, "prod", path)
}
func TestRequestMetadataContextHelpersMissingKeys(t *testing.T) {
ctx := context.WithValue(context.Background(), requestMetadataCtxKey{}, RequestMetadata{
Headers: map[string][]string{"X-Trace-Id": {"trace-1"}},
Query: map[string][]string{"tenant": {"foo"}},
Path: map[string]string{"scope": "prod"},
})
_, ok := HeaderFromContext(ctx, "x-missing")
assert.False(t, ok)
_, ok = QueryFromContext(ctx, "missing")
assert.False(t, ok)
_, ok = PathFromContext(ctx, "missing")
assert.False(t, ok)
}
func TestRequestMetadataFromContextNotFound(t *testing.T) {
_, ok := RequestMetadataFromContext(context.Background())
assert.False(t, ok)
_, ok = HeaderFromContext(context.Background(), "x-test")
assert.False(t, ok)
_, ok = QueryFromContext(context.Background(), "tenant")
assert.False(t, ok)
_, ok = PathFromContext(context.Background(), "tenant")
assert.False(t, ok)
}
func TestWrapRequestMetadata(t *testing.T) {
s := &mcpServerImpl{
options: serverOptions{
requestMetadataExtractor: DefaultRequestMetadataExtractor,
},
}
called := false
handler := s.wrapRequestMetadata(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
called = true
header, ok := HeaderFromContext(r.Context(), "x-tenant-id")
assert.True(t, ok)
assert.Equal(t, "tenant-1", header)
query, ok := QueryFromContext(r.Context(), "tenant")
assert.True(t, ok)
assert.Equal(t, "q-tenant", query)
}))
req := httptest.NewRequest(http.MethodGet, "/sse?tenant=q-tenant", nil)
req.Header.Set("X-Tenant-Id", "tenant-1")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
assert.True(t, called)
}
func TestWrapRequestMetadataNoExtractor(t *testing.T) {
s := &mcpServerImpl{}
called := false
handler := s.wrapRequestMetadata(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
called = true
_, ok := RequestMetadataFromContext(r.Context())
assert.False(t, ok)
}))
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/sse", nil))
assert.True(t, called)
}
func TestWrapRequestMetadataCanonicalizesCustomHeaders(t *testing.T) {
s := &mcpServerImpl{
options: serverOptions{
requestMetadataExtractor: func(*http.Request) RequestMetadata {
return RequestMetadata{
Headers: map[string][]string{
"x-tenant-id": {"tenant-lower"},
},
}
},
},
}
called := false
handler := s.wrapRequestMetadata(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
called = true
header, ok := HeaderFromContext(r.Context(), "X-Tenant-Id")
assert.True(t, ok)
assert.Equal(t, "tenant-lower", header)
}))
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/sse", nil))
assert.True(t, called)
}
func TestRequestMetadataFromContextReturnsCopy(t *testing.T) {
ctx := context.WithValue(context.Background(), requestMetadataCtxKey{}, RequestMetadata{
Headers: map[string][]string{"X-Trace-Id": {"trace-1"}},
})
metadata, ok := RequestMetadataFromContext(ctx)
assert.True(t, ok)
metadata.Headers["X-Trace-Id"][0] = "mutated"
metadata.Headers["X-New"] = []string{"new"}
fresh, ok := RequestMetadataFromContext(ctx)
assert.True(t, ok)
assert.Equal(t, []string{"trace-1"}, fresh.Headers["X-Trace-Id"])
assert.Nil(t, fresh.Headers["X-New"])
}
func TestRequestMetadataFromContextWithEmptyAndCanonicalizedHeaders(t *testing.T) {
emptyCtx := context.WithValue(context.Background(), requestMetadataCtxKey{}, RequestMetadata{})
empty, ok := RequestMetadataFromContext(emptyCtx)
assert.True(t, ok)
assert.Nil(t, empty.Headers)
assert.Nil(t, empty.Query)
assert.Nil(t, empty.Path)
ctx := context.WithValue(context.Background(), requestMetadataCtxKey{}, RequestMetadata{
Headers: map[string][]string{
"x-tenant-id": {"a"},
"X-Tenant-Id": {"b"},
},
})
metadata, ok := RequestMetadataFromContext(ctx)
assert.True(t, ok)
assert.ElementsMatch(t, []string{"a", "b"}, metadata.Headers["X-Tenant-Id"])
}
+30 -2
View File
@@ -1,6 +1,7 @@
package mcp
import (
"context"
"net/http"
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
@@ -20,10 +21,23 @@ type mcpServerImpl struct {
conf McpConf
httpServer *rest.Server
mcpServer *sdkmcp.Server
options serverOptions
}
// NewMcpServer creates a new MCP server using the official SDK
func NewMcpServer(c McpConf) McpServer {
return NewMcpServerWithOptions(c)
}
// NewMcpServerWithOptions creates a new MCP server with optional customizations.
func NewMcpServerWithOptions(c McpConf, opts ...McpOption) McpServer {
serverOpts := defaultServerOptions()
for _, opt := range opts {
if opt != nil {
opt.apply(&serverOpts)
}
}
// Create the underlying rest HTTP server
var httpServer *rest.Server
if len(c.Mcp.Cors) == 0 {
@@ -52,6 +66,7 @@ func NewMcpServer(c McpConf) McpServer {
conf: c,
httpServer: httpServer,
mcpServer: mcpServer,
options: serverOpts,
}
// Choose transport based on configuration
@@ -85,7 +100,7 @@ func (s *mcpServerImpl) setupSSETransport() {
return s.mcpServer
}, nil)
s.registerRoutes(handler, s.conf.Mcp.SseEndpoint)
s.registerRoutes(s.wrapRequestMetadata(handler), s.conf.Mcp.SseEndpoint)
}
// setupStreamableTransport configures the server to use Streamable HTTP transport (2025-03-26 spec)
@@ -96,7 +111,7 @@ func (s *mcpServerImpl) setupStreamableTransport() {
return s.mcpServer
}, nil)
s.registerRoutes(handler, s.conf.Mcp.MessageEndpoint)
s.registerRoutes(s.wrapRequestMetadata(handler), s.conf.Mcp.MessageEndpoint)
}
func (s *mcpServerImpl) registerRoutes(handler http.Handler, endpoint string) {
@@ -113,3 +128,16 @@ func (s *mcpServerImpl) registerRoutes(handler http.Handler, endpoint string) {
Handler: handler.ServeHTTP,
}, rest.WithTimeout(s.conf.Mcp.MessageTimeout))
}
func (s *mcpServerImpl) wrapRequestMetadata(next http.Handler) http.Handler {
extractor := s.options.requestMetadataExtractor
if extractor == nil {
return next
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
metadata := normalizeRequestMetadata(extractor(r))
ctx := context.WithValue(r.Context(), requestMetadataCtxKey{}, metadata)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
+148
View File
@@ -3,11 +3,14 @@ package mcp
import (
"bytes"
"context"
"fmt"
"net"
"net/http"
"net/http/httptest"
"testing"
"time"
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/conf"
)
@@ -391,3 +394,148 @@ func TestAddToolWithCustomServer(t *testing.T) {
return nil, nil, nil
})
}
func TestRequestMetadataIntegrationSSEToolCall(t *testing.T) {
port := getFreePort(t)
c := McpConf{}
c.Host = "127.0.0.1"
c.Port = port
c.Mcp.Name = "metadata-integration-test"
c.Mcp.UseStreamable = false
c.Mcp.SseEndpoint = "/sse/:scope"
c.Mcp.MessageTimeout = 2 * time.Second
c.Mcp.SseTimeout = 2 * time.Second
server := NewMcpServerWithOptions(c, WithRequestMetadataExtractor(DefaultRequestMetadataExtractor))
tool := &Tool{
Name: "inspect_metadata",
Description: "Inspect metadata in handler context",
}
type Args struct{}
AddTool(server, tool, func(ctx context.Context, req *CallToolRequest, args Args) (*CallToolResult, any, error) {
header, ok := HeaderFromContext(ctx, "x-tenant-id")
if !ok || header != "tenant-header" {
return nil, nil, fmt.Errorf("unexpected header from context: %q", header)
}
query, ok := QueryFromContext(ctx, "tenant")
if !ok || query != "tenant-query" {
return nil, nil, fmt.Errorf("unexpected query from context: %q", query)
}
scope, ok := PathFromContext(ctx, "scope")
if !ok || scope != "prod" {
return nil, nil, fmt.Errorf("unexpected path from context: %q", scope)
}
return &CallToolResult{
Content: []Content{&TextContent{Text: "metadata-ok"}},
}, nil, nil
})
go server.Start()
t.Cleanup(server.Stop)
baseURL := fmt.Sprintf("http://127.0.0.1:%d/sse/prod?tenant=tenant-query", port)
waitForServerReady(t, baseURL, 2*time.Second)
client := sdkmcp.NewClient(&sdkmcp.Implementation{
Name: "metadata-client",
Version: "1.0.0",
}, nil)
httpClient := &http.Client{
Timeout: 2 * time.Second,
Transport: metadataHeaderRoundTripper{
next: http.DefaultTransport,
},
}
transport := &sdkmcp.SSEClientTransport{
Endpoint: baseURL,
HTTPClient: httpClient,
}
session, err := client.Connect(context.Background(), transport, nil)
if !assert.NoError(t, err) {
return
}
t.Cleanup(func() {
_ = session.Close()
})
res, err := session.CallTool(context.Background(), &sdkmcp.CallToolParams{
Name: "inspect_metadata",
Arguments: map[string]any{},
})
if !assert.NoError(t, err) {
return
}
if !assert.NotNil(t, res) {
return
}
assert.False(t, res.IsError)
}
type metadataHeaderRoundTripper struct {
next http.RoundTripper
}
func (r metadataHeaderRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
next := r.next
if next == nil {
next = http.DefaultTransport
}
clone := req.Clone(req.Context())
clone.Header.Set("X-Tenant-Id", "tenant-header")
return next.RoundTrip(clone)
}
func getFreePort(t *testing.T) int {
t.Helper()
listener, err := net.Listen("tcp", "127.0.0.1:0")
if !assert.NoError(t, err) {
return 0
}
defer listener.Close()
addr, ok := listener.Addr().(*net.TCPAddr)
if !assert.True(t, ok) {
return 0
}
return addr.Port
}
func waitForServerReady(t *testing.T, endpoint string, timeout time.Duration) {
t.Helper()
client := &http.Client{Timeout: 200 * time.Millisecond}
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
t.Fatalf("failed to build readiness request: %v", err)
}
req.Header.Set("Accept", "text/event-stream")
resp, err := client.Do(req)
if err == nil {
_ = resp.Body.Close()
if resp.StatusCode > 0 {
return
}
}
time.Sleep(20 * time.Millisecond)
}
t.Fatalf("server did not become ready for %s within %s", endpoint, timeout)
}
+1 -1
View File
@@ -4,7 +4,7 @@
***缩短从需求到上线的距离***
[English](readme.md) | 简体中文
[English](readme.md) | 简体中文 | [한국어](readme-ko.md)
[![Go Report Card](https://goreportcard.com/badge/github.com/zeromicro/go-zero)](https://goreportcard.com/report/github.com/zeromicro/go-zero)
[![goproxy](https://goproxy.cn/stats/github.com/zeromicro/go-zero/badges/download-count.svg)](https://goproxy.cn/stats/github.com/zeromicro/go-zero/badges/download-count.svg)
+278
View File
@@ -0,0 +1,278 @@
# go-zero
<p align="center">
<img align="center" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
</p>
go-zero는 다양한 엔지니어링 모범 사례가 내장된 웹 및 RPC 프레임워크입니다. 회복 탄력성 설계로 트래픽이 많은 서비스의 안정성을 보장하기 위해 만들어졌으며, 수년 동안 수천만 사용자를 보유한 사이트에서 운영되어 왔습니다.
<div align=center>
[![codecov](https://codecov.io/gh/zeromicro/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/zeromicro/go-zero)
[![Go Report Card](https://goreportcard.com/badge/github.com/zeromicro/go-zero)](https://goreportcard.com/report/github.com/zeromicro/go-zero)
[![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
[![Go Reference](https://pkg.go.dev/badge/github.com/zeromicro/go-zero.svg)](https://pkg.go.dev/github.com/zeromicro/go-zero)
[![Awesome Go](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/avelino/awesome-go)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe)
</div>
## 🤷‍ go-zero란?
[English](readme.md) | [简体中文](readme-cn.md) | 한국어
<a href="https://trendshift.io/repositories/3263" target="_blank"><img src="https://trendshift.io/api/badge/repositories/3263" alt="zeromicro%2Fgo-zero | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
go-zero는 [CNCF Landscape](https://landscape.cncf.io/?selected=go-zero)에 등재된, 다양한 엔지니어링 모범 사례가 내장된 웹 및 RPC 프레임워크입니다. 회복 탄력성 설계로 트래픽이 많은 서비스의 안정성을 보장하기 위해 만들어졌으며, 수년 동안 수천만 사용자를 보유한 사이트에서 운영되어 왔습니다.
go-zero에는 간단한 API 설명 문법과 `goctl`이라는 코드 생성 도구가 포함되어 있습니다. `goctl`을 사용하면 .api 파일에서 Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript 코드를 생성할 수 있습니다.
#### go-zero의 장점:
* 수천만 일간 활성 사용자를 처리하는 서비스의 안정성을 높입니다.
* 체인형 타임아웃 제어, 동시성 제어, 속도 제한, 적응형 서킷 브레이커, 적응형 로드 셰딩이 내장되어 있으며, 별도 설정 없이도 사용할 수 있습니다.
* 내장 미들웨어를 기존 프레임워크에 통합할 수 있습니다.
* 간단한 API 문법과 한 번의 명령으로 여러 언어 코드를 생성할 수 있습니다.
* 클라이언트 요청 파라미터를 자동으로 검증합니다.
* 다양한 내장 마이크로서비스 관리 도구와 동시성 도구를 제공합니다.
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
## go-zero의 배경
2018년 초, 우리는 Java+MongoDB 기반 모놀리식 아키텍처에서 마이크로서비스로 전환하면서 다음을 선택했습니다.
* **Golang** - 높은 성능, 간결한 문법, 뛰어난 배포 경험, 낮은 리소스 사용량
* **자체 설계한 마이크로서비스 프레임워크** - 더 나은 문제 격리, 더 쉬운 기능 확장, 더 빠른 이슈 해결
## go-zero의 설계 고려 사항
go-zero는 다음 핵심 설계 원칙을 따릅니다.
* **단순성** - 단순하게 유지하는 것을 제1원칙으로 삼습니다.
* **고가용성** - 높은 동시성 상황에서도 안정적으로 동작합니다.
* **회복 탄력성** - 적응형 보호를 갖춘 장애 지향 프로그래밍을 지향합니다.
* **개발자 친화성** - 복잡성을 캡슐화하고 한 가지 일을 하는 한 가지 방법을 제공합니다.
* **확장 용이성** - 성장을 위한 유연한 아키텍처를 제공합니다.
## go-zero의 구현과 기능
go-zero는 엔지니어링 모범 사례를 통합합니다.
* **코드 생성** - 보일러플레이트를 최소화하는 강력한 도구
* **간단한 API** - 깔끔한 인터페이스와 net/http 완전 호환
* **고성능** - 속도와 효율성을 위한 최적화
* **회복 탄력성** - 내장 서킷 브레이커, 속도 제한, 로드 셰딩, 타임아웃 제어
* **서비스 메시** - 서비스 디스커버리, 부하 분산, 호출 추적
* **개발자 도구** - 자동 파라미터 검증, 캐시 관리, 메트릭과 모니터링
![Resilience](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/resilience-en.png)
## go-zero 아키텍처
<img width="1067" alt="image" src="https://user-images.githubusercontent.com/1918356/171880372-5010d846-e8b1-4942-8fe2-e2bbb584f762.png">
## 설치
프로젝트에서 다음 명령을 실행하세요.
```shell
go get -u github.com/zeromicro/go-zero
```
## AI 네이티브 개발
go-zero 팀은 Claude, GitHub Copilot, Cursor가 프레임워크 규칙을 따르는 코드를 생성할 수 있도록 AI 도구를 제공합니다.
### 세 가지 핵심 프로젝트
**[ai-context](https://github.com/zeromicro/ai-context)** - AI 어시스턴트를 위한 워크플로 가이드
**[zero-skills](https://github.com/zeromicro/zero-skills)** - 예제가 포함된 패턴 라이브러리
**[mcp-zero](https://github.com/zeromicro/mcp-zero)** - Model Context Protocol을 통한 코드 생성 도구
### 빠른 설정
#### GitHub Copilot
```bash
git submodule add https://github.com/zeromicro/ai-context.git .github/ai-context
ln -s ai-context/00-instructions.md .github/copilot-instructions.md # macOS/Linux
# Windows: mklink .github\copilot-instructions.md .github\ai-context\00-instructions.md
git submodule update --remote .github/ai-context # 업데이트
```
#### Cursor
```bash
git submodule add https://github.com/zeromicro/ai-context.git .cursorrules
git submodule update --remote .cursorrules # 업데이트
```
#### Windsurf
```bash
git submodule add https://github.com/zeromicro/ai-context.git .windsurfrules
git submodule update --remote .windsurfrules # 업데이트
```
#### Claude Desktop
```bash
git clone https://github.com/zeromicro/mcp-zero.git && cd mcp-zero && go build
# 설정: ~/Library/Application Support/Claude/claude_desktop_config.json
# 또는: claude mcp add --transport stdio mcp-zero --env GOCTL_PATH=/path/to/goctl -- /path/to/mcp-zero
```
### 동작 방식
AI 어시스턴트는 다음 도구를 함께 사용합니다.
1. **ai-context** - 워크플로 안내
2. **zero-skills** - 구현 패턴
3. **mcp-zero** - 실시간 코드 생성
**예시**: REST API 생성 → AI가 **ai-context**에서 워크플로를 읽음 → **mcp-zero**를 호출해 코드 생성 → **zero-skills**에서 패턴 참조 → 프로덕션 준비가 된 코드 생성 ✅
## 빠른 시작
1. 전체 예제:
[마이크로서비스 시스템 빠른 개발](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)
[마이크로서비스 시스템 빠른 개발 - 다중 RPC](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/bookstore-en.md)
2. goctl 설치
```shell
# Go 사용
go install github.com/zeromicro/go-zero/tools/goctl@latest
# Mac 사용
brew install goctl
# 모든 플랫폼에서 docker 사용
docker pull kevinwan/goctl
# goctl 실행
docker run --rm -it -v `pwd`:/app kevinwan/goctl --help
```
goctl이 실행 가능하고 $PATH에 포함되어 있는지 확인하세요.
3. API 파일(greet.api) 생성:
```go
type (
Request {
Name string `path:"name,options=[you,me]"` // 파라미터는 자동으로 검증됩니다.
}
Response {
Message string `json:"message"`
}
)
service greet-api {
@handler GreetHandler
get /greet/from/:name(Request) returns (Response)
}
```
.api 템플릿 생성:
```shell
goctl api -o greet.api
```
4. Go 서버 코드 생성
```shell
goctl api go -api greet.api -dir greet
```
생성되는 구조:
```Plain Text
├── greet
│   ├── etc
│   │   └── greet-api.yaml // 설정 파일
│   ├── greet.go // main 파일
│   └── internal
│   ├── config
│   │   └── config.go // 설정 정의
│   ├── handler
│   │   ├── greethandler.go // get/put/post/delete 라우트가 여기에 정의됩니다.
│   │   └── routes.go // 라우트 목록
│   ├── logic
│   │   └── greetlogic.go // 요청 로직을 여기에 작성할 수 있습니다.
│   ├── svc
│   │   └── servicecontext.go // 서비스 컨텍스트, mysql/redis를 여기에 전달할 수 있습니다.
│   └── types
│   └── types.go // 요청/응답이 여기에 정의됩니다.
└── greet.api // api 설명 파일
```
서비스 실행:
```shell
cd greet
go mod tidy
go run greet.go -f etc/greet-api.yaml
```
기본 포트: 8888(etc/greet-api.yaml에서 변경 가능)
curl로 테스트:
```shell
curl -i http://localhost:8888/greet/from/you
```
응답:
```http
HTTP/1.1 200 OK
Date: Sun, 30 Aug 2020 15:32:35 GMT
Content-Length: 0
```
5. 비즈니스 로직 작성
* servicecontext.go를 통해 의존성(mysql, redis 등)을 전달합니다.
* .api 정의에 따라 logic 패키지에 로직 코드를 추가합니다.
6. 여러 언어의 클라이언트 코드 생성
```shell
goctl api java -api greet.api -dir greet
goctl api dart -api greet.api -dir greet
...
```
## 벤치마크
![benchmark](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/benchmark.png)
[테스트 코드 보기](https://github.com/smallnest/go-web-framework-benchmark)
## 문서
* [문서](https://go-zero.dev/)
* [마이크로서비스 시스템 빠른 개발](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)
* [마이크로서비스 시스템 빠른 개발 - 다중 RPC](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/bookstore-en.md)
* [예제](https://github.com/zeromicro/zero-examples)
## 채팅 그룹
다음 링크에서 채팅에 참여하세요: https://discord.gg/4JQvC5A4Fe
## Cloud Native Landscape
<p float="left">
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/cncf-logo.svg" width="200"/>&nbsp;&nbsp;&nbsp;
<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/cncf-landscape-logo.svg" width="150"/>
</p>
go-zero는 [CNCF Cloud Native Landscape](https://landscape.cncf.io/?selected=go-zero)에 등재되었습니다.
## 별을 눌러주세요! ⭐
이 프로젝트가 마음에 들거나 학습 또는 자체 솔루션을 시작하는 데 사용 중이라면, 새 릴리스 업데이트를 받을 수 있도록 star를 눌러주세요. 여러분의 지원은 큰 힘이 됩니다!
+1 -1
View File
@@ -19,7 +19,7 @@ go-zero is a web and rpc framework with lots of builtin engineering practices. I
## 🤷‍ What is go-zero?
English | [简体中文](readme-cn.md)
English | [简体中文](readme-cn.md) | [한국어](readme-ko.md)
<a href="https://trendshift.io/repositories/3263" target="_blank"><img src="https://trendshift.io/api/badge/repositories/3263" alt="zeromicro%2Fgo-zero | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
+4 -1
View File
@@ -84,8 +84,11 @@ func buildRequest(ctx context.Context, method, url string, data any) (*http.Requ
var reader io.Reader
jsonVars, hasJsonBody := val[jsonKey]
if hasJsonBody {
if method == http.MethodGet {
switch method {
case http.MethodGet:
return nil, ErrGetWithBody
case http.MethodHead:
return nil, ErrHeadWithBody
}
var buf bytes.Buffer
+103
View File
@@ -229,3 +229,106 @@ func TestDo_WithClientHttpTrace(t *testing.T) {
assert.Nil(t, err)
assert.True(t, enter)
}
func TestBuildRequestWithBody(t *testing.T) {
testBody := struct {
Key string `json:"key"`
Value int `json:"value"`
}{
Key: "foo",
Value: 10,
}
testcases := []struct {
testName string
method string
url string
body any
wantedErr error
}{
{
testName: "GET Request with Body",
method: http.MethodGet,
url: "/ping",
body: testBody,
wantedErr: ErrGetWithBody,
},
{
testName: "GET Request without Body",
method: http.MethodGet,
url: "/ping",
body: nil,
wantedErr: nil,
},
{
testName: "HEAD Request with Body",
method: http.MethodHead,
url: "/ping",
body: testBody,
wantedErr: ErrHeadWithBody,
},
{
testName: "HEAD Request without Body",
method: http.MethodHead,
url: "/ping",
body: nil,
wantedErr: nil,
},
{
testName: "POST Request with Body",
method: http.MethodPost,
url: "/ping",
body: testBody,
wantedErr: nil,
},
{
testName: "PUT Request with Body",
method: http.MethodPut,
url: "/ping",
body: testBody,
wantedErr: nil,
},
{
testName: "PATCH Request with Body",
method: http.MethodPatch,
url: "/ping",
body: testBody,
wantedErr: nil,
},
{
testName: "DELETE Request with Body",
method: http.MethodDelete,
url: "/ping",
body: testBody,
wantedErr: nil,
},
{
testName: "CONNECT Request with Body",
method: http.MethodConnect,
url: "/ping",
body: testBody,
wantedErr: nil,
},
{
testName: "OPTIONS Request with Body",
method: http.MethodOptions,
url: "/ping",
body: testBody,
wantedErr: nil,
},
{
testName: "TRACE Request with Body",
method: http.MethodTrace,
url: "/ping",
body: testBody,
wantedErr: nil,
},
}
for _, tc := range testcases {
t.Run(tc.testName, func(t *testing.T) {
_, err := buildRequest(context.Background(), tc.method, tc.url, tc.body)
assert.Equal(t, tc.wantedErr, err)
})
}
}
+6 -2
View File
@@ -11,5 +11,9 @@ const (
colon = ':'
)
// ErrGetWithBody indicates that GET request with body.
var ErrGetWithBody = errors.New("HTTP GET should not have body")
var (
// ErrGetWithBody indicates that GET request with body.
ErrGetWithBody = errors.New("HTTP GET should not have body")
// ErrHeadWithBody indicates that HEAD request with body.
ErrHeadWithBody = errors.New("HTTP HEAD should not have body")
)
@@ -0,0 +1,34 @@
syntax = "v1"
info (
title: "Issue 5496 Reproduce"
version: "v1"
host: "localhost"
)
// Reproduces issue #5496:
// The `example` tag option in `path` and `form` tags does not appear in generated swagger JSON.
type (
Request {
// path param with example — should appear as example in swagger
Name string `path:"name,options=you|me,example=nihao"`
// form param with example and range — should appear as example in swagger
Age int `form:"age,optional,range=[1:200],example=18"`
}
Response {
Name string `json:"name,example=nihao"`
Age int `json:"age,example=18"`
}
)
@server (
tags: "issue5496"
summary: "Issue 5496 - example field missing for path/form params"
)
service issue5496 {
@doc (
description: "Demonstrate that example is missing in path/form swagger params"
)
@handler getUser
get /user/:name (Request) returns (Response)
}
@@ -0,0 +1,81 @@
{
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"schemes": [
"https"
],
"swagger": "2.0",
"info": {
"title": "Issue 5496 Reproduce",
"version": "v1"
},
"host": "localhost",
"basePath": "/",
"paths": {
"/user/{name}": {
"get": {
"description": "Demonstrate that example is missing in path/form swagger params",
"produces": [
"application/json"
],
"schemes": [
"https"
],
"tags": [
"issue5496"
],
"summary": "getUser",
"operationId": "getUser",
"parameters": [
{
"enum": [
"you",
"me"
],
"type": "string",
"example": "nihao",
"name": "name",
"in": "path",
"required": true
},
{
"maximum": 200,
"minimum": 1,
"type": "integer",
"example": 18,
"name": "age",
"in": "query",
"allowEmptyValue": true
}
],
"responses": {
"200": {
"description": "",
"schema": {
"type": "object",
"properties": {
"age": {
"type": "integer",
"example": 18
},
"name": {
"type": "string",
"example": "nihao"
}
}
}
}
}
}
}
},
"x-date": "2026-03-19 22:52:34",
"x-description": "This is a goctl generated swagger file.",
"x-github": "https://github.com/zeromicro/go-zero",
"x-go-zero-doc": "https://go-zero.dev/",
"x-goctl-version": "1.10.0"
}
+4
View File
@@ -72,6 +72,7 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, headerTag.Options, member.Type),
Example: exampleValueFromOptions(ctx, headerTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
@@ -96,6 +97,7 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, pathParameterTag.Options, member.Type),
Example: exampleValueFromOptions(ctx, pathParameterTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
@@ -121,6 +123,7 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, formTag.Options, member.Type),
Example: exampleValueFromOptions(ctx, formTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
@@ -143,6 +146,7 @@ func parametersFromType(ctx Context, method string, tp apiSpec.Type) []spec.Para
SimpleSchema: spec.SimpleSchema{
Type: sampleTypeFromGoType(ctx, member.Type),
Default: defValueFromOptions(ctx, formTag.Options, member.Type),
Example: exampleValueFromOptions(ctx, formTag.Options, member.Type),
Items: sampleItemsFromGoType(ctx, member.Type),
},
ParamProps: spec.ParamProps{
+35
View File
@@ -83,6 +83,41 @@ func TestParametersFromType_EdgeCases(t *testing.T) {
assert.Empty(t, params)
}
// TestParametersFromType_ExampleField reproduces issue #5496:
// example= in path/form tags was not emitted in the generated swagger JSON.
func TestParametersFromType_ExampleField(t *testing.T) {
ctx := testingContext(t)
testStruct := apiSpec.DefineStruct{
RawName: "Request",
Members: []apiSpec.Member{
{
Name: "Name",
Type: apiSpec.PrimitiveType{RawName: "string"},
Tag: `path:"name,options=you|me,example=nihao"`,
},
{
Name: "Age",
Type: apiSpec.PrimitiveType{RawName: "int"},
Tag: `form:"age,optional,range=[1:200],example=18"`,
},
},
}
params := parametersFromType(ctx, http.MethodGet, testStruct)
assert.Len(t, params, 2)
// path param should have example
pathParam := params[0]
assert.Equal(t, paramsInPath, pathParam.In)
assert.Equal(t, "nihao", pathParam.SimpleSchema.Example, "path param example should be set")
// form/query param should have example
queryParam := params[1]
assert.Equal(t, paramsInQuery, queryParam.In)
assert.EqualValues(t, int64(18), queryParam.SimpleSchema.Example, "form param example should be set")
}
func createTestStruct(name string, hasJson bool) apiSpec.DefineStruct {
tag := `form:"username"`
if hasJson {
+59 -54
View File
@@ -1,14 +1,14 @@
module github.com/zeromicro/go-zero/tools/goctl
go 1.23.0
go 1.24.0
require (
github.com/DATA-DOG/go-sqlmock v1.5.2
github.com/emicklei/proto v1.14.3
github.com/fatih/structtag v1.2.0
github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e
github.com/go-sql-driver/mysql v1.9.3
github.com/gookit/color v1.6.0
github.com/go-sql-driver/mysql v1.10.0
github.com/gookit/color v1.6.1
github.com/iancoleman/strcase v0.3.0
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
@@ -16,103 +16,108 @@ require (
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1
github.com/zeromicro/antlr v0.0.1
github.com/zeromicro/ddl-parser v1.0.5
github.com/zeromicro/go-zero v1.10.0
golang.org/x/text v0.28.0
google.golang.org/grpc v1.65.0
github.com/zeromicro/go-zero v1.10.2
golang.org/x/text v0.34.0
google.golang.org/grpc v1.80.0
google.golang.org/protobuf v1.36.11
gopkg.in/yaml.v2 v2.4.0
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/alicebob/miniredis/v2 v2.36.1 // indirect
filippo.io/edwards25519 v1.2.0 // indirect
github.com/alicebob/miniredis/v2 v2.38.0 // indirect
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.21.1 // indirect
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/swag v0.23.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grafana/pyroscope-go v1.2.7 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/grafana/pyroscope-go v1.3.0 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.10 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.4 // indirect
github.com/jackc/pgx/v5 v5.8.0 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pelletier/go-toml/v2 v2.3.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.66.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/redis/go-redis/v9 v9.17.3 // indirect
github.com/redis/go-redis/v9 v9.19.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/titanous/json5 v1.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
go.etcd.io/etcd/api/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect
go.etcd.io/etcd/client/v3 v3.5.15 // indirect
go.opentelemetry.io/otel v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.etcd.io/etcd/api/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.21 // indirect
go.etcd.io/etcd/client/v3 v3.5.21 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/otel v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.40.0 // indirect
go.opentelemetry.io/otel/metric v1.40.0 // indirect
go.opentelemetry.io/otel/sdk v1.40.0 // indirect
go.opentelemetry.io/otel/trace v1.40.0 // indirect
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/automaxprocs v1.6.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
go.uber.org/zap v1.24.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/net v0.43.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/time v0.10.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/term v0.40.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.29.3 // indirect
k8s.io/apimachinery v0.29.4 // indirect
k8s.io/client-go v0.29.3 // indirect
k8s.io/klog/v2 v2.110.1 // indirect
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
k8s.io/api v0.34.3 // indirect
k8s.io/apimachinery v0.34.3 // indirect
k8s.io/client-go v0.34.3 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)
+137 -119
View File
@@ -1,9 +1,9 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/alicebob/miniredis/v2 v2.36.1 h1:Dvc5oAnNOr7BIfPn7tF269U8DvRW1dBG2D5n0WrfYMI=
github.com/alicebob/miniredis/v2 v2.36.1/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
github.com/alicebob/miniredis/v2 v2.38.0 h1:nZAzCR+Lj+Vxk4ZXzm2NuKq2O33RXj1XxJ2e2uP9jiw=
github.com/alicebob/miniredis/v2 v2.38.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
@@ -14,8 +14,8 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
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.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
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/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
@@ -26,20 +26,19 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/proto v1.14.3 h1:zEhlzNkpP8kN6utonKMzlPfIvy82t5Kb9mufaJxSe1Q=
github.com/emicklei/proto v1.14.3/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
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.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
@@ -50,37 +49,35 @@ github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e h1:auobAirzhPsL
github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e/go.mod h1:NAKTe9SplQBxIUlHlsuId1jk1I7bWTVV/2q/GtdRi6g=
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
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/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
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/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0=
github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E=
github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA=
github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/grafana/pyroscope-go v1.2.7 h1:VWBBlqxjyR0Cwk2W6UrE8CdcdD80GOFNutj0Kb1T8ac=
github.com/grafana/pyroscope-go v1.2.7/go.mod h1:o/bpSLiJYYP6HQtvcoVKiE9s5RiNgjYTj1DhiddP2Pc=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 h1:c1Us8i6eSmkW+Ez05d3co8kasnuOY813tbMN8i/a3Og=
github.com/grafana/pyroscope-go/godeltaprof v0.1.9/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/gookit/color v1.6.1 h1:KoTnDxJPRgrL0SoX0f8rCFg2zI0t4E3GZZBMo2nN8LU=
github.com/gookit/color v1.6.1/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs=
github.com/grafana/pyroscope-go v1.3.0 h1:t3Jehad8vvqN4oRAB0LdmfQ5ZSUXQw3asoft+K4GAT8=
github.com/grafana/pyroscope-go v1.3.0/go.mod h1:XA7I3usNx+UdjOZfQnl1WV8y924vsJo9KIVrKB+9jx4=
github.com/grafana/pyroscope-go/godeltaprof v0.1.10 h1:dvhndEbyavTb59vFCd6PsrAG5qi69/qZZtegh/TJKSY=
github.com/grafana/pyroscope-go/godeltaprof v0.1.10/go.mod h1:XnWRGg2XO5uxZdiz1rfeJH6w1eZ+YICCBVXNWOfH86g=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
@@ -91,8 +88,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.4 h1:9wKznZrhWa2QiHL+NjTSPP6yjl3451BX3imWDnokYlg=
github.com/jackc/pgx/v5 v5.7.4/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -102,8 +99,10 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
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/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -122,18 +121,19 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
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 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
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/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=
github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/openzipkin/zipkin-go v0.4.3 h1:9EGwpqkgnwdEIJ+Od7QVSEIH+ocmm5nPat0G7sjsSdg=
github.com/openzipkin/zipkin-go v0.4.3/go.mod h1:M9wCJZFWCo2RiY+o1eBCEMe0Dp2S5LDHcMZmk3RmK7c=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.3.1 h1:MYEvvGnQjeNkRF1qUuGolNtNExTDwct51yp7olPtrEc=
github.com/pelletier/go-toml/v2 v2.3.1/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -148,10 +148,12 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4=
github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/redis/go-redis/v9 v9.19.0 h1:XPVaaPSnG6RhYf7p+rmSa9zZfeVAnWsH5h3lxthOm/k=
github.com/redis/go-redis/v9 v9.19.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -167,48 +169,58 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/titanous/json5 v1.0.0 h1:hJf8Su1d9NuI/ffpxgxQfxh/UiBFZX7bMPid0rIL/7s=
github.com/titanous/json5 v1.0.0/go.mod h1:7JH1M8/LHKc6cyP5o5g3CSaRj+mBrIimTxzpvmckH8c=
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1 h1:+dBg5k7nuTE38VVdoroRsT0Z88fmvdYrI2EjzJst35I=
github.com/withfig/autocomplete-tools/integrations/cobra v1.2.1/go.mod h1:nmuySobZb4kFgFy6BptpXp/BBw+xFSyvVPP6auoJB4k=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
github.com/zeromicro/ddl-parser v1.0.5 h1:LaVqHdzMTjasua1yYpIYaksxKqRzFrEukj2Wi2EbWaQ=
github.com/zeromicro/ddl-parser v1.0.5/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
github.com/zeromicro/go-zero v1.10.0 h1:+qfAqj+BGt0qjW1PQk2VO5WLwIQBh60CA3OTLsBosS8=
github.com/zeromicro/go-zero v1.10.0/go.mod h1:qAModWGsfkrBl0JP9oS7K7k6dgucExOuQdpzHyXVKLg=
go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=
go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=
go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=
go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=
go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=
go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0 h1:Mw5xcxMwlqoJd97vwPxA8isEaIoxsta9/Q51+TTJLGE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0/go.mod h1:CQNu9bj7o7mC6U7+CA/schKEYakYXWr79ucDHTMGhCM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0 h1:3evrL5poBuh1KF51D9gO/S+N/1msnm4DaBqs/rpXUqY=
go.opentelemetry.io/otel/exporters/zipkin v1.24.0/go.mod h1:0EHgD8R0+8yRhUYJOGR8Hfg2dpiJQxDOszd5smVO9wM=
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
github.com/zeromicro/go-zero v1.10.2 h1:XVxs4tGi4dkNE08iZP0BoqlCuof4iAnCdZ424mz8yyM=
github.com/zeromicro/go-zero v1.10.2/go.mod h1:Qn1kdpoQfj9DzTtYUlv5pXIFAij6gNAwmkZ+w2ldr2Q=
go.etcd.io/etcd/api/v3 v3.5.21 h1:A6O2/JDb3tvHhiIz3xf9nJ7REHvtEFJJ3veW3FbCnS8=
go.etcd.io/etcd/api/v3 v3.5.21/go.mod h1:c3aH5wcvXv/9dqIw2Y810LDXJfhSYdHQ0vxmP3CCHVY=
go.etcd.io/etcd/client/pkg/v3 v3.5.21 h1:lPBu71Y7osQmzlflM9OfeIV2JlmpBjqBNlLtcoBqUTc=
go.etcd.io/etcd/client/pkg/v3 v3.5.21/go.mod h1:BgqT/IXPjK9NkeSDjbzwsHySX3yIle2+ndz28nVsjUs=
go.etcd.io/etcd/client/v3 v3.5.21 h1:T6b1Ow6fNjOLOtM0xSoKNQt1ASPCLWrF9XMHcH9pEyY=
go.etcd.io/etcd/client/v3 v3.5.21/go.mod h1:mFYy67IOqmbRf/kRUvsHixzo3iG+1OF2W2+jVIQRAnU=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8=
go.opentelemetry.io/otel/exporters/zipkin v1.40.0 h1:zu+I4j+FdO6xIxBVPeuncQVbjxUM4LiMgv6GwGe9REE=
go.opentelemetry.io/otel/exporters/zipkin v1.40.0/go.mod h1:zS6cC4nFBYXbu18e7aLfMzubBjOiN7ZcROu477qtMf8=
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8=
go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE=
go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw=
go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg=
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
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.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -221,12 +233,11 @@ go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
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.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -235,76 +246,83 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d h1:kHjw/5UfflP/L5EbledDrcG4C2597RtymmGRZvHiCuY=
google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d h1:JU0iKnSg02Gmb5ZdV8nYsKEKsP6o/FGVWTrw4i1DA9A=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M=
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/h2non/gock.v1 v1.1.2 h1:jBbHXgGBK/AoPVfJh5x4r/WxIrElvbLel8TCZkkZJoY=
gopkg.in/h2non/gock.v1 v1.1.2/go.mod h1:n7UGz/ckNChHiK05rDoiC4MYSunEC/lyaUm2WWaDva0=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.29.3 h1:2ORfZ7+bGC3YJqGpV0KSDDEVf8hdGQ6A03/50vj8pmw=
k8s.io/api v0.29.3/go.mod h1:y2yg2NTyHUUkIoTC+phinTnEa3KFM6RZ3szxt014a80=
k8s.io/apimachinery v0.29.4 h1:RaFdJiDmuKs/8cm1M6Dh1Kvyh59YQFDcFuFTSmXes6Q=
k8s.io/apimachinery v0.29.4/go.mod h1:i3FJVwhvSp/6n8Fl4K97PJEP8C+MM+aoDq4+ZJBf70Y=
k8s.io/client-go v0.29.3 h1:R/zaZbEAxqComZ9FHeQwOh3Y1ZUs7FaHKZdQtIc2WZg=
k8s.io/client-go v0.29.3/go.mod h1:tkDisCvgPfiRpxGnOORfkljmS+UrW+WtXAy2fTvXJB0=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
k8s.io/klog/v2 v2.110.1/go.mod h1:YGtd1984u+GgbuZ7e08/yBuAfKLSO0+uR1Fhi6ExXjo=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 h1:aVUu9fTY98ivBPKR9Y5w/AuzbMm96cd3YHRTU83I780=
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00/go.mod h1:AsvuZPBlUDVuCdzJ87iajxtXuR9oktsTctW/R9wwouA=
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2 h1:OfgiEo21hGiwx1oJUU5MpEaeOEg6coWndBkZF/lkFuE=
k8s.io/utils v0.0.0-20251222233032-718f0e51e6d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
k8s.io/api v0.34.3 h1:D12sTP257/jSH2vHV2EDYrb16bS7ULlHpdNdNhEw2S4=
k8s.io/api v0.34.3/go.mod h1:PyVQBF886Q5RSQZOim7DybQjAbVs8g7gwJNhGtY5MBk=
k8s.io/apimachinery v0.34.3 h1:/TB+SFEiQvN9HPldtlWOTp0hWbJ+fjU+wkxysf/aQnE=
k8s.io/apimachinery v0.34.3/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.34.3 h1:wtYtpzy/OPNYf7WyNBTj3iUA0XaBHVqhv4Iv3tbrF5A=
k8s.io/client-go v0.34.3/go.mod h1:OxxeYagaP9Kdf78UrKLa3YZixMCfP6bgPwPwNBQBzpM=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM=
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
+1 -1
View File
@@ -6,7 +6,7 @@ import (
)
// BuildVersion is the version of goctl.
const BuildVersion = "1.10.0"
const BuildVersion = "1.10.1"
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-beta": 2, "beta": 3, "released": 4, "": 5}
+1
View File
@@ -16,6 +16,7 @@ func genFindOne(table Table, withCache, postgreSql bool) (string, string, error)
output, err := util.With("findOne").
Parse(text).
AddFunc("hasField", hasField(table)).
Execute(map[string]any{
"withCache": withCache,
"upperStartCamelObject": camel,
+3 -3
View File
@@ -22,7 +22,7 @@ func genFindOneByField(table Table, withCache, postgreSql bool) (*findOneCode, e
return nil, err
}
t := util.With("findOneByField").Parse(text)
t := util.With("findOneByField").Parse(text).AddFunc("hasField", hasField(table))
var list []string
camelTableName := table.Name.ToCamel()
for _, key := range table.UniqueCacheKey {
@@ -54,7 +54,7 @@ func genFindOneByField(table Table, withCache, postgreSql bool) (*findOneCode, e
return nil, err
}
t = util.With("findOneByFieldMethod").Parse(text)
t = util.With("findOneByFieldMethod").Parse(text).AddFunc("hasField", hasField(table))
var listMethod []string
for _, key := range table.UniqueCacheKey {
var inJoin, paramJoin Join
@@ -88,7 +88,7 @@ func genFindOneByField(table Table, withCache, postgreSql bool) (*findOneCode, e
return nil, err
}
out, err := util.With("findOneByFieldExtraMethod").Parse(text).Execute(map[string]any{
out, err := util.With("findOneByFieldExtraMethod").AddFunc("hasField", hasField(table)).Parse(text).Execute(map[string]any{
"upperStartCamelObject": camelTableName,
"primaryKeyLeft": table.PrimaryCacheKey.VarLeft,
"lowerStartCamelObject": stringx.From(camelTableName).Untitle(),
+2
View File
@@ -360,6 +360,7 @@ func (g *defaultGenerator) genModelCustom(in parser.Table, withCache bool) (stri
t := util.With("model-custom").
Parse(text).
AddFunc("hasField", hasField(Table{Table: in})).
GoFmt(true)
output, err := t.Execute(map[string]any{
"pkg": g.pkg,
@@ -381,6 +382,7 @@ func (g *defaultGenerator) executeModel(table Table, code *code) (*bytes.Buffer,
}
t := util.With("model").
Parse(text).
AddFunc("hasField", hasField(table)).
GoFmt(true)
output, err := t.Execute(map[string]any{
"pkg": g.pkg,
+1 -1
View File
@@ -28,7 +28,7 @@ func genImports(table Table, withCache, timeImport bool) (string, error) {
return "", err
}
buffer, err := util.With("import").Parse(text).Execute(map[string]any{
buffer, err := util.With("import").Parse(text).AddFunc("hasField", hasField(table)).Execute(map[string]any{
"time": timeImport,
"containsPQ": table.ContainsPQ,
"data": table,
+13
View File
@@ -94,3 +94,16 @@ func Update() error {
return pathx.InitTemplates(category, templates)
}
// hasField returns a function that checks if a field exists in the table.
// It uses a pre-built map for O(1) lookup performance.
func hasField(table Table) func(string) bool {
fieldSet := make(map[string]struct{}, len(table.Fields))
for _, field := range table.Fields {
fieldSet[field.NameOriginal] = struct{}{}
}
return func(f string) bool {
_, ok := fieldSet[f]
return ok
}
}
+150
View File
@@ -6,8 +6,10 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/tools/goctl/model/sql/parser"
"github.com/zeromicro/go-zero/tools/goctl/model/sql/template"
"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
)
func TestGenTemplates(t *testing.T) {
@@ -91,3 +93,151 @@ func TestUpdate(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, template.New, string(data))
}
func TestHasField(t *testing.T) {
tests := []struct {
name string
table Table
fieldName string
wantResult bool
}{
{
name: "field exists",
table: Table{
Table: parser.Table{
Fields: []*parser.Field{
{NameOriginal: "id"},
{NameOriginal: "name"},
{NameOriginal: "created_at"},
},
},
},
fieldName: "name",
wantResult: true,
},
{
name: "field does not exist",
table: Table{
Table: parser.Table{
Fields: []*parser.Field{
{NameOriginal: "id"},
{NameOriginal: "name"},
},
},
},
fieldName: "email",
wantResult: false,
},
{
name: "empty table",
table: Table{
Table: parser.Table{
Fields: []*parser.Field{},
},
},
fieldName: "id",
wantResult: false,
},
{
name: "case sensitive match",
table: Table{
Table: parser.Table{
Fields: []*parser.Field{
{NameOriginal: "ID"},
{NameOriginal: "Name"},
},
},
},
fieldName: "id",
wantResult: false,
},
{
name: "exact match required",
table: Table{
Table: parser.Table{
Fields: []*parser.Field{
{NameOriginal: "user_name"},
},
},
},
fieldName: "user_name",
wantResult: true,
},
{
name: "partial match should fail",
table: Table{
Table: parser.Table{
Fields: []*parser.Field{
{NameOriginal: "user_name"},
},
},
},
fieldName: "user",
wantResult: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fn := hasField(tt.table)
result := fn(tt.fieldName)
assert.Equal(t, tt.wantResult, result)
})
}
}
func TestHasFieldWithRealTable(t *testing.T) {
// Create a realistic table structure
table := Table{
Table: parser.Table{
Name: stringx.From("users"),
Fields: []*parser.Field{
{NameOriginal: "id", DataType: "int64"},
{NameOriginal: "username", DataType: "string"},
{NameOriginal: "email", DataType: "string"},
{NameOriginal: "password", DataType: "string"},
{NameOriginal: "created_at", DataType: "time.Time"},
{NameOriginal: "updated_at", DataType: "time.Time"},
},
},
}
fn := hasField(table)
// Test all existing fields
assert.True(t, fn("id"))
assert.True(t, fn("username"))
assert.True(t, fn("email"))
assert.True(t, fn("password"))
assert.True(t, fn("created_at"))
assert.True(t, fn("updated_at"))
// Test non-existing fields
assert.False(t, fn("deleted_at"))
assert.False(t, fn("ID"))
assert.False(t, fn("Username"))
assert.False(t, fn(""))
}
func TestHasFieldPerformance(t *testing.T) {
// Create a table with many fields to test performance optimization
var fields []*parser.Field
for i := 0; i < 1000; i++ {
fields = append(fields, &parser.Field{
NameOriginal: "field_" + string(rune('0'+i%10)) + string(rune('a'+i%26)),
})
}
table := Table{
Table: parser.Table{
Fields: fields,
},
}
fn := hasField(table)
// Verify the function works correctly
assert.True(t, fn(fields[0].NameOriginal))
assert.True(t, fn(fields[999].NameOriginal))
assert.False(t, fn("non_existent_field"))
}
+2 -2
View File
@@ -61,7 +61,7 @@ func genUpdate(table Table, withCache, postgreSql bool) (
return "", "", err
}
output, err := util.With("update").Parse(text).Execute(
output, err := util.With("update").Parse(text).AddFunc("hasField", hasField(table)).Execute(
map[string]any{
"withCache": withCache,
"containsIndexCache": table.ContainsUniqueCacheKey,
@@ -94,7 +94,7 @@ func genUpdate(table Table, withCache, postgreSql bool) (
return "", "", err
}
updateMethodOutput, err := util.With("updateMethod").Parse(text).Execute(
updateMethodOutput, err := util.With("updateMethod").Parse(text).AddFunc("hasField", hasField(table)).Execute(
map[string]any{
"upperStartCamelObject": camelTableName,
"data": table,
+1 -1
View File
@@ -197,7 +197,7 @@ func (p *printer) print(x reflect.Value) {
case reflect.Struct:
if val, ok := x.Interface().(apitoken.Position); ok {
p.printf(val.String())
p.printf("%s", val.String())
return
}
t := x.Type()
+1 -1
View File
@@ -357,7 +357,7 @@ func (w *Writer) Write(opts ...Option) {
// WriteText writes the text.
func (w *Writer) WriteText(text string) {
_, _ = fmt.Fprintf(w.tw, text)
_, _ = fmt.Fprint(w.tw, text)
}
func (w *Writer) write(opt *option) {
+1 -1
View File
@@ -284,7 +284,7 @@ func (api *API) parseImportedAPI(imports []ast.ImportStmt) ([]*API, error) {
}
// import cycle check
if err := api.importManager.Push(impPath); err != nil {
return nil, ast.SyntaxError(tok.Position, err.Error())
return nil, ast.SyntaxError(tok.Position, "%s", err.Error())
}
if _, ok := api.importSet[impPath]; ok {
+2 -2
View File
@@ -1,7 +1,7 @@
package parser
import (
"fmt"
"errors"
"strings"
)
@@ -24,5 +24,5 @@ func (e *errorManager) error() error {
if len(e.errors) == 0 {
return nil
}
return fmt.Errorf(strings.Join(e.errors, "\n"))
return errors.New(strings.Join(e.errors, "\n"))
}
+1 -1
View File
@@ -1676,7 +1676,7 @@ func (p *Parser) CheckErrors() error {
errors = append(errors, e.Error())
}
return fmt.Errorf(strings.Join(errors, "\n"))
return fmt.Errorf("%s", strings.Join(errors, "\n"))
}
func (p *Parser) appendStmt(stmt ...ast.Stmt) {
@@ -130,7 +130,7 @@ func TestParser_Parse_infoStmt(t *testing.T) {
"author": `"type author here"`,
"email": `"type email here"`,
"version": `"type version here"`,
"enable": `true`,
"enable": `true`,
"disable": `false`,
}
p := New("foo.api", infoTestAPI)
@@ -762,6 +762,11 @@ func TestParser_Parse_service(t *testing.T) {
}),
},
Request: &ast.BodyStmt{
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
RParen: ast.NewTokenNode(token.Token{Type: token.RPAREN, Text: ")"}),
},
Returns: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "returns"}),
Response: &ast.BodyStmt{
LParen: ast.NewTokenNode(token.Token{Type: token.LPAREN, Text: "("}),
Body: &ast.BodyExpr{
Value: ast.NewTokenNode(token.Token{Type: token.IDENT, Text: "Foo"}),
+1 -1
View File
@@ -1,5 +1,5 @@
# goctl
[English](readme.md) | 简体中文
[English](readme.md) | 简体中文 | [한국어](readme-ko.md)
goctl 使用见文档 https://go-zero.dev/docs/tutorials/cli/overview
+5
View File
@@ -0,0 +1,5 @@
# goctl
[English](readme.md) | [简体中文](readme-cn.md) | 한국어
다음 문서에서 확인하세요: https://go-zero.dev/docs/tutorials/cli/overview
+1 -1
View File
@@ -1,5 +1,5 @@
# goctl
English | [简体中文](readme-cn.md)
English | [简体中文](readme-cn.md) | [한국어](readme-ko.md)
Read document at https://go-zero.dev/docs/tutorials/cli/overview
+119
View File
@@ -0,0 +1,119 @@
# 变更日志
[English](CHANGELOG.md) | 中文 | [한국어](CHANGELOG-ko.md)
## 未发布
### 新功能
#### 外部 Proto 导入支持(`--proto_path` / `-I`
新增通过 `-I` / `--proto_path` 标志导入外部目录中的 proto 文件,支持完整的传递性依赖解析。
**涉及文件:**
- `generator/gen.go``ZRpcContext` 新增 `ProtoPaths` 字段;新增 `resolveImportedProtos()` 在代码生成前填充 `ImportedProtos`
- `generator/genpb.go` — 新增 `buildProtocCmd()` 自动发现并追加传递性导入的 proto 文件到 `protoc` 命令;新增 `relativeToProtoPath()` 计算正确的相对路径。
- `parser/import.go` — 新文件(主要新增)。实现 `ResolveImports()` 递归解析传递性导入,`ParseImportedProtos()` 提取导入 proto 的 `go_package` / `package` 元数据,`BuildProtoPackageMap()` 构建按 proto 包名的 O(1) 查找表。
- `parser/proto.go``Proto` 结构体新增 `ImportedProtos []ImportedProto` 字段。
- `cli/cli.go``RPCNew` 传递 `ProtoPaths``ZRpcContext`
- `cli/zrpc.go` — 将 `VarStringSliceProtoPath` 传递到 `ZRpcContext.ProtoPaths`
**前后对比:**
| | 变更前 | 变更后 |
|---|---|---|
| 从外部目录导入 Proto | ❌ 不支持,所有类型必须在同一文件中定义 | ✅ 使用 `-I ./ext_protos` 添加搜索路径 |
| 传递性导入A → B → C | ❌ 仅识别直接导入 | ✅ 递归解析所有传递性依赖 |
| 导入 proto 的 `.pb.go` 生成 | ❌ 需手动为每个文件单独运行 protoc | ✅ 自动将导入的 proto 追加到 protoc 命令 |
| Proto 搜索路径 | ❌ 仅源文件所在目录 | ✅ 支持多个 `-I` 路径,与 protoc 一致 |
**行为说明:**
- 递归遍历 proto 文件中的所有 `import` 声明,跳过 `google/*` 知名类型。
- 在每个 `-I` 目录中搜索被导入的文件,未找到的系统级 proto 静默跳过。
- 将发现的 proto 文件追加到 `protoc` 命令,使其 `.pb.go` 文件与主 proto 一同生成。
---
#### 跨包类型解析
当导入的 proto 与主 proto 具有**不同**的 `go_package`goctl 现在能够自动在 server、logic 和 client 代码中生成正确的 Go 导入路径和限定类型引用。
**涉及文件:**
- `generator/typeref.go` — 新文件,核心类型解析引擎:
- `resolveRPCTypeRef()` — 将 proto RPC 类型简单类型、同包点号类型、跨包点号类型、Google WKT解析为带正确导入路径的 Go 类型引用。
- `resolveCallTypeRef()` — 客户端代码生成变体,支持类型别名。
- `googleWKTTable` — 支持的 Google 知名类型到 Go 等价类型的映射表。
- `generator/genserver.go``genFunctions()` 调用 `resolveRPCTypeRef()` 解析请求/响应类型并收集额外导入路径。
- `generator/genlogic.go``genLogicFunction()` 使用 `resolveRPCTypeRef()`;新增 `addLogicImports()` 按需添加主 pb 导入和跨包导入。
- `generator/gencall.go``genFunction()``getInterfaceFuncs()` 使用 `resolveCallTypeRef()` 处理类型别名和额外导入;新增 `buildExtraImportLines()` 辅助函数。
- `generator/call.tpl` — 新增 `{{.extraImports}}` 占位符用于跨包导入行。
**前后对比:**
| Proto 类型 | 变更前 | 变更后 |
|---|---|---|
| `GetReq`(同文件) | `pb.GetReq` | `pb.GetReq`(无变化) |
| `ext.ExtReq`(相同 `go_package` | ❌ 报错:"request type must defined in" | ✅ `pb.ExtReq` — 合并到主包 |
| `common.TypesReq`(不同 `go_package` | ❌ 报错:"request type must defined in" | ✅ `common.TypesReq` + 自动生成 `import "example.com/demo/pb/common"` |
| `google.protobuf.Empty` | ❌ 报错:"request type must defined in" | ✅ `emptypb.Empty` + 自动生成导入 |
**行为说明:**
- 简单类型(如 `GetReq`)解析为 `pb.GetReq`,无额外导入。
- 同包点号类型(如 `ext.ExtReq`,其中 `ext` 与主 proto 有相同的 `go_package`)解析为 `pb.ExtReq`
- 跨包点号类型(如 `common.TypesReq`,其中 `common` 有不同的 `go_package`)解析为 `common.TypesReq`,并自动添加正确的 Go 导入路径。
---
#### Google 知名类型作为 RPC 参数
Google protobuf 知名类型现在可以直接用作 RPC 的请求/响应类型(而不仅仅是消息字段)。
**涉及文件:**
- `generator/typeref.go``resolveGoogleWKT()` + `googleWKTTable` 处理支持的标准类型。
**前后对比:**
| Proto 类型 | 变更前(作为 RPC 参数) | 变更后(作为 RPC 参数) |
|---|---|---|
| `google.protobuf.Empty` | ❌ 报错 | ✅ `emptypb.Empty` |
| `google.protobuf.Timestamp` | ❌ 报错 | ✅ `timestamppb.Timestamp` |
| `google.protobuf.Duration` | ❌ 报错 | ✅ `durationpb.Duration` |
| `google.protobuf.Any` | ❌ 报错 | ✅ `anypb.Any` |
| `google.protobuf.Struct` | ❌ 报错 | ✅ `structpb.Struct` |
| `google.protobuf.FieldMask` | ❌ 报错 | ✅ `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` | ❌ 报错 | ✅ `wrapperspb.*Value` |
> 注:这些类型此前已可用作**消息字段**。本次变更使其可直接用作 **RPC 请求/响应类型**
**完整类型映射表:**
| Proto 类型 | Go 类型 |
|---|---|
| `google.protobuf.Empty` | `emptypb.Empty` |
| `google.protobuf.Timestamp` | `timestamppb.Timestamp` |
| `google.protobuf.Duration` | `durationpb.Duration` |
| `google.protobuf.Any` | `anypb.Any` |
| `google.protobuf.Struct` | `structpb.Struct` |
| `google.protobuf.Value` | `structpb.Value` |
| `google.protobuf.ListValue` | `structpb.ListValue` |
| `google.protobuf.FieldMask` | `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value`(包装类型) | `wrapperspb.*Value` |
---
### 不兼容变更
#### RPC 定义中允许使用点号类型名
此前 goctl 会拒绝 RPC 请求/响应类型中包含点号的情况(如 `base.Req`),要求所有类型必须定义在同一个 proto 文件中。此限制已移除。
**前后对比:**
| Proto 定义 | 变更前 | 变更后 |
|---|---|---|
| `rpc Fetch(base.Req) returns (base.Reply)` | ❌ 解析错误:"request type must defined in xxx.proto" | ✅ 解析成功,`base.Req` 通过导入的 proto 解析 |
| `rpc Ping(google.protobuf.Empty) returns (Reply)` | ❌ 解析错误:"request type must defined in xxx.proto" | ✅ 解析成功,解析为 `emptypb.Empty` |
**涉及文件:**
- `parser/service.go` — 移除了拒绝点号类型名的验证循环(原错误信息为 `"request type must defined in"` / `"returns type must defined in"`)。
- `parser/parser_test.go``TestDefaultProtoParseCaseInvalidRequestType``TestDefaultProtoParseCaseInvalidResponseType` 重命名并更新,验证点号类型现在可以正常解析。
+119
View File
@@ -0,0 +1,119 @@
# 변경 로그
[English](CHANGELOG.md) | [中文](CHANGELOG-cn.md) | 한국어
## 릴리스 예정
### 새로운 기능
#### 외부 proto import 지원(`--proto_path` / `-I`)
`-I` / `--proto_path` 플래그로 외부 디렉터리의 proto 파일을 import할 수 있도록 지원을 추가했으며, 전체 전이 의존성 해결을 제공합니다.
**영향받는 파일:**
- `generator/gen.go``ZRpcContext``ProtoPaths` 필드를 추가하고, 코드 생성 전에 `ImportedProtos`를 채우는 `resolveImportedProtos()`를 추가했습니다.
- `generator/genpb.go` — 전이적으로 import된 proto 파일을 자동 탐색해 `protoc` 명령에 추가하는 `buildProtocCmd()`를 추가하고, protoc 출력 경로에 맞는 상대 경로를 계산하는 `relativeToProtoPath()`를 추가했습니다.
- `parser/import.go` — 새 파일(대규모 추가). 재귀적 전이 import 해결을 위한 `ResolveImports()`, import된 proto에서 `go_package` / `package` 메타데이터를 추출하는 `ParseImportedProtos()`, proto `package` 이름으로 O(1) 조회를 제공하는 `BuildProtoPackageMap()`을 구현합니다.
- `parser/proto.go``Proto` 구조체에 `ImportedProtos []ImportedProto` 필드를 추가했습니다.
- `cli/cli.go``RPCNew``ProtoPaths``ZRpcContext`로 전달합니다.
- `cli/zrpc.go``VarStringSliceProtoPath``ZRpcContext.ProtoPaths`로 전달합니다.
**이전 vs 이후:**
| | 이전 | 이후 |
|---|---|---|
| 외부 디렉터리의 proto import | ❌ 지원하지 않음, 모든 타입이 같은 파일에 있어야 함 | ✅ `-I ./ext_protos`로 검색 경로 추가 |
| 전이 import(A → B → C) | ❌ 직접 import만 인식 | ✅ 모든 전이 의존성을 재귀적으로 해결 |
| import된 proto의 `.pb.go` 생성 | ❌ 수동, 파일마다 protoc를 별도로 실행해야 함 | ✅ 자동, import된 proto를 protoc 명령에 추가 |
| proto 검색 경로 | ❌ 원본 파일 디렉터리만 | ✅ protoc와 동일하게 여러 `-I` 경로 지원 |
**동작:**
- proto 파일의 모든 `import` 선언을 전이적으로 순회하며, `google/*` well-known types는 건너뜁니다.
- 각 `-I` 디렉터리에서 import된 파일을 검색하며, 사용자 경로에서 찾을 수 없는 시스템 수준 proto는 조용히 건너뜁니다.
- 발견한 proto 파일을 `protoc` 명령에 추가해 메인 proto와 함께 `.pb.go` 파일이 생성되도록 합니다.
---
#### 패키지 간 타입 해석
import된 proto의 `go_package`가 메인 proto와 **다른** 경우, goctl은 이제 서버, 로직, 클라이언트 코드에서 올바른 Go import 경로와 한정된 타입 참조를 자동 생성합니다.
**영향받는 파일:**
- `generator/typeref.go` — 새 파일. 핵심 타입 해석 엔진:
- `resolveRPCTypeRef()` — proto RPC 타입(단순 타입, 동일 패키지 점 표기 타입, 패키지 간 점 표기 타입, Google WKT)을 올바른 import 경로를 가진 Go 타입 참조로 해석합니다.
- `resolveCallTypeRef()` — 타입 alias를 지원하는 클라이언트 코드 생성용 변형입니다.
- `googleWKTTable` — 지원되는 Google well-known types를 Go 대응 타입으로 매핑하는 테이블입니다.
- `generator/genserver.go``genFunctions()`가 이제 요청/응답 타입에 대해 `resolveRPCTypeRef()`를 호출하고 추가 import 경로를 수집합니다.
- `generator/genlogic.go``genLogicFunction()``resolveRPCTypeRef()`를 사용합니다. main pb import와 패키지 간 import를 조건부로 포함하는 `addLogicImports()`를 추가했습니다.
- `generator/gencall.go``genFunction()``getInterfaceFuncs()`가 타입 alias와 추가 import를 위해 `resolveCallTypeRef()`를 사용합니다. `buildExtraImportLines()` 헬퍼를 추가했습니다.
- `generator/call.tpl` — 패키지 간 import 라인을 위한 `{{.extraImports}}` 플레이스홀더를 추가했습니다.
**이전 vs 이후:**
| Proto 타입 | 이전 | 이후 |
|---|---|---|
| `GetReq`(같은 파일) | `pb.GetReq` | `pb.GetReq`(변경 없음) |
| `ext.ExtReq`(같은 `go_package`) | ❌ 오류: "request type must defined in" | ✅ `pb.ExtReq` — 메인 proto의 Go 패키지에 병합 |
| `common.TypesReq`(다른 `go_package`) | ❌ 오류: "request type must defined in" | ✅ `common.TypesReq` + 자동 생성 `import "example.com/demo/pb/common"` |
| `google.protobuf.Empty` | ❌ 오류: "request type must defined in" | ✅ `emptypb.Empty` + 자동 생성 import |
**동작:**
- 단순 타입(예: `GetReq`)은 추가 import 없이 `pb.GetReq`로 해석됩니다.
- 동일 패키지 점 표기 타입(예: `ext.ExtReq`, `ext`가 같은 `go_package`를 가진 경우)은 `pb.ExtReq`로 해석됩니다.
- 패키지 간 점 표기 타입(예: `common.TypesReq`, `common`이 다른 `go_package`를 가진 경우)은 올바른 Go import 경로가 자동 추가된 `common.TypesReq`로 해석됩니다.
---
#### RPC 파라미터로 Google well-known types 사용
Google protobuf well-known types를 이제 메시지 필드뿐 아니라 RPC 요청/응답 타입으로도 직접 사용할 수 있습니다.
**영향받는 파일:**
- `generator/typeref.go``resolveGoogleWKT()` + `googleWKTTable`이 지원되는 표준 타입을 처리합니다.
**이전 vs 이후:**
| Proto 타입 | 이전(RPC 파라미터) | 이후(RPC 파라미터) |
|---|---|---|
| `google.protobuf.Empty` | ❌ 오류 | ✅ `emptypb.Empty` |
| `google.protobuf.Timestamp` | ❌ 오류 | ✅ `timestamppb.Timestamp` |
| `google.protobuf.Duration` | ❌ 오류 | ✅ `durationpb.Duration` |
| `google.protobuf.Any` | ❌ 오류 | ✅ `anypb.Any` |
| `google.protobuf.Struct` | ❌ 오류 | ✅ `structpb.Struct` |
| `google.protobuf.FieldMask` | ❌ 오류 | ✅ `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` | ❌ 오류 | ✅ `wrapperspb.*Value` |
> 참고: 이 타입들은 이전에도 **메시지 필드**로 사용할 수 있었습니다. 이번 변경으로 **RPC 요청/응답 타입**으로도 직접 사용할 수 있습니다.
**지원 타입:**
| Proto 타입 | Go 타입 |
|---|---|
| `google.protobuf.Empty` | `emptypb.Empty` |
| `google.protobuf.Timestamp` | `timestamppb.Timestamp` |
| `google.protobuf.Duration` | `durationpb.Duration` |
| `google.protobuf.Any` | `anypb.Any` |
| `google.protobuf.Struct` | `structpb.Struct` |
| `google.protobuf.Value` | `structpb.Value` |
| `google.protobuf.ListValue` | `structpb.ListValue` |
| `google.protobuf.FieldMask` | `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` (wrappers) | `wrapperspb.*Value` |
---
### 호환성이 깨지는 변경
#### RPC 정의에서 점 표기 타입 이름 허용
이전에는 goctl이 점(.)이 포함된 RPC 요청/응답 타입(예: `base.Req`)을 거부했으며, 모든 타입이 같은 proto 파일에 정의되어 있어야 했습니다. 이 제한이 제거되었습니다.
**이전 vs 이후:**
| Proto 정의 | 이전 | 이후 |
|---|---|---|
| `rpc Fetch(base.Req) returns (base.Reply)` | ❌ 파싱 오류: "request type must defined in xxx.proto" | ✅ 성공적으로 파싱되며, `base.Req`가 import된 proto를 통해 해석됨 |
| `rpc Ping(google.protobuf.Empty) returns (Reply)` | ❌ 파싱 오류: "request type must defined in xxx.proto" | ✅ 성공적으로 파싱되며, `emptypb.Empty`로 해석됨 |
**영향받는 파일:**
- `parser/service.go` — 점(.)이 포함된 타입 이름을 `"request type must defined in"` / `"returns type must defined in"` 오류로 거부하던 검증 루프를 제거했습니다.
- `parser/parser_test.go``TestDefaultProtoParseCaseInvalidRequestType``TestDefaultProtoParseCaseInvalidResponseType`을 이름을 변경하고 업데이트하여 점 표기 타입이 이제 성공적으로 파싱되는지 검증합니다.
+119
View File
@@ -0,0 +1,119 @@
# Changelog
English | [中文](CHANGELOG-cn.md) | [한국어](CHANGELOG-ko.md)
## Unreleased
### New Features
#### External Proto Import Support (`--proto_path` / `-I`)
Added support for importing proto files from external directories via `-I` / `--proto_path` flags, with full transitive dependency resolution.
**Affected files:**
- `generator/gen.go` — Added `ProtoPaths` field to `ZRpcContext`; added `resolveImportedProtos()` to populate `ImportedProtos` before code generation.
- `generator/genpb.go` — Added `buildProtocCmd()` to automatically discover and append transitively imported proto files to the `protoc` command; added `relativeToProtoPath()` to compute correct relative paths for protoc output.
- `parser/import.go` — New file (major addition). Implements `ResolveImports()` for recursive transitive import resolution, `ParseImportedProtos()` for extracting `go_package` / `package` metadata from imported protos, and `BuildProtoPackageMap()` for O(1) lookup by proto package name.
- `parser/proto.go` — Added `ImportedProtos []ImportedProto` field to the `Proto` struct.
- `cli/cli.go` — Passes `ProtoPaths` from `RPCNew` to `ZRpcContext`.
- `cli/zrpc.go` — Passes `VarStringSliceProtoPath` to `ZRpcContext.ProtoPaths`.
**Before vs After:**
| | Before | After |
|---|---|---|
| Proto imports from external dirs | ❌ Not supported, all types must be in the same file | ✅ Use `-I ./ext_protos` to add search paths |
| Transitive imports (A → B → C) | ❌ Only direct imports recognized | ✅ Recursively resolves all transitive dependencies |
| Imported proto `.pb.go` generation | ❌ Manual, must run protoc separately for each file | ✅ Automatic, imported protos appended to protoc command |
| Proto search paths | ❌ Only source file directory | ✅ Multiple `-I` paths, same as protoc |
**Behavior:**
- Transitively walks all `import` declarations in proto files, skipping `google/*` well-known types.
- Searches each `-I` directory for imported files, silently skipping system-level protos not found in user paths.
- Appends discovered proto files to the `protoc` command so their `.pb.go` files are generated alongside the main proto.
---
#### Cross-Package Type Resolution
When an imported proto has a **different** `go_package` from the main proto, goctl now automatically generates the correct Go import paths and qualified type references in server, logic, and client code.
**Affected files:**
- `generator/typeref.go` — New file. Core type resolution engine:
- `resolveRPCTypeRef()` — Resolves proto RPC types (simple, same-package dotted, cross-package dotted, Google WKT) to Go type references with correct import paths.
- `resolveCallTypeRef()` — Variant for client code generation with type alias support.
- `googleWKTTable` — Mapping table for supported Google well-known types to their Go equivalents.
- `generator/genserver.go``genFunctions()` now calls `resolveRPCTypeRef()` for request/response types and collects extra import paths.
- `generator/genlogic.go``genLogicFunction()` uses `resolveRPCTypeRef()`; added `addLogicImports()` to conditionally include main pb import and cross-package imports.
- `generator/gencall.go``genFunction()` and `getInterfaceFuncs()` use `resolveCallTypeRef()` for type aliases and extra imports; added `buildExtraImportLines()` helper.
- `generator/call.tpl` — Added `{{.extraImports}}` placeholder for cross-package import lines.
**Before vs After:**
| Proto type | Before | After |
|---|---|---|
| `GetReq` (same file) | `pb.GetReq` | `pb.GetReq` (unchanged) |
| `ext.ExtReq` (same `go_package`) | ❌ Error: "request type must defined in" | ✅ `pb.ExtReq` — merged into main package |
| `common.TypesReq` (different `go_package`) | ❌ Error: "request type must defined in" | ✅ `common.TypesReq` + auto-generated `import "example.com/demo/pb/common"` |
| `google.protobuf.Empty` | ❌ Error: "request type must defined in" | ✅ `emptypb.Empty` + auto-generated import |
**Behavior:**
- Simple types (e.g., `GetReq`) resolve to `pb.GetReq` with no extra import.
- Same-package dotted types (e.g., `ext.ExtReq` where `ext` has the same `go_package`) resolve to `pb.ExtReq`.
- Cross-package dotted types (e.g., `common.TypesReq` where `common` has a different `go_package`) resolve to `common.TypesReq` with the correct Go import path added automatically.
---
#### Google Well-Known Types as RPC Parameters
Google protobuf well-known types can now be used directly as RPC request/response types (not just as message fields).
**Affected files:**
- `generator/typeref.go``resolveGoogleWKT()` + `googleWKTTable` handles supported standard types.
**Before vs After:**
| Proto Type | Before (as RPC param) | After (as RPC param) |
|---|---|---|
| `google.protobuf.Empty` | ❌ Error | ✅ `emptypb.Empty` |
| `google.protobuf.Timestamp` | ❌ Error | ✅ `timestamppb.Timestamp` |
| `google.protobuf.Duration` | ❌ Error | ✅ `durationpb.Duration` |
| `google.protobuf.Any` | ❌ Error | ✅ `anypb.Any` |
| `google.protobuf.Struct` | ❌ Error | ✅ `structpb.Struct` |
| `google.protobuf.FieldMask` | ❌ Error | ✅ `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` | ❌ Error | ✅ `wrapperspb.*Value` |
> Note: These types were already usable as **message fields** before. This change allows them as **RPC request/response types** directly.
**Supported types:**
| Proto Type | Go Type |
|---|---|
| `google.protobuf.Empty` | `emptypb.Empty` |
| `google.protobuf.Timestamp` | `timestamppb.Timestamp` |
| `google.protobuf.Duration` | `durationpb.Duration` |
| `google.protobuf.Any` | `anypb.Any` |
| `google.protobuf.Struct` | `structpb.Struct` |
| `google.protobuf.Value` | `structpb.Value` |
| `google.protobuf.ListValue` | `structpb.ListValue` |
| `google.protobuf.FieldMask` | `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` (wrappers) | `wrapperspb.*Value` |
---
### Breaking Changes
#### Dotted Type Names Now Allowed in RPC Definitions
Previously, goctl rejected any RPC request/response type containing a dot (e.g., `base.Req`), requiring all types to be defined in the same proto file. This restriction has been removed.
**Before vs After:**
| Proto Definition | Before | After |
|---|---|---|
| `rpc Fetch(base.Req) returns (base.Reply)` | ❌ Parse error: "request type must defined in xxx.proto" | ✅ Parsed successfully, `base.Req` resolved via imported proto |
| `rpc Ping(google.protobuf.Empty) returns (Reply)` | ❌ Parse error: "request type must defined in xxx.proto" | ✅ Parsed successfully, resolved to `emptypb.Empty` |
**Affected files:**
- `parser/service.go` — Removed the validation loop that rejected dotted type names with `"request type must defined in"` / `"returns type must defined in"` errors.
- `parser/parser_test.go``TestDefaultProtoParseCaseInvalidRequestType` and `TestDefaultProtoParseCaseInvalidResponseType` renamed and updated to verify that dotted types now parse successfully.
+315
View File
@@ -0,0 +1,315 @@
# goctl rpc — RPC 代码生成
[English](README.md) | 中文 | [한국어](README-ko.md)
goctl rpc 是 `goctl` 脚手架下的 RPC 服务代码生成模块,基于 `.proto` 文件生成完整的 zRPC 服务代码。你只需编写 proto 定义和业务逻辑,其余代码均由工具自动生成。
## 特性
- **贴近 protoc**:与 protoc 完全兼容,透传所有 protoc 参数
- **外部 Proto 导入**:支持跨目录、跨包的 proto 导入,自动解析传递性依赖
- **多服务模式**:单个 proto 文件中定义多个 service按服务名自动分组
- **流式支持**:支持服务端流、客户端流和双向流
- **Google 标准类型**:自动识别支持的 `google.protobuf.*` 类型并生成正确的 Go 导入
- **客户端生成**:自动生成封装好的 RPC 客户端代码
## 前置条件
```bash
# 安装 protoc 插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```
## 快速开始
### 方式一:一键创建服务
```bash
goctl rpc new greeter
```
生成完整的项目结构:
```
greeter/
├── etc/
│ └── greeter.yaml
├── greeter/
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeter.proto
├── greeterclient/
│ └── greeter.go
└── internal/
├── config/
│ └── config.go
├── logic/
│ └── pinglogic.go
├── server/
│ └── greeterserver.go
└── svc/
└── servicecontext.go
```
### 方式二:基于 Proto 文件生成
1. 生成 proto 模板:
```bash
goctl rpc template -o=user.proto
```
2. 初始化输出目录并生成服务代码:
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
---
## 命令参考
### `goctl rpc protoc`
`.proto` 文件生成 zRPC 服务代码。
```bash
goctl rpc protoc <proto_file> [flags]
```
**示例:**
```bash
# 基础用法
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
# 多服务模式
goctl rpc protoc multi.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -m
# 导入外部 proto
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -I ./shared_protos
# 使用 Google 标准类型
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
**参数说明:**
| 参数 | 缩写 | 类型 | 默认值 | 说明 |
|------|------|------|--------|------|
| `--zrpc_out` | | string | **必填** | zRPC 服务代码输出目录 |
| `--go_out` | | string | **必填** | protoc Go 代码输出目录 |
| `--go-grpc_out` | | string | **必填** | protoc gRPC 代码输出目录 |
| `--go_opt` | | string | | protoc-gen-go 选项(如 `module=example.com/demo` |
| `--go-grpc_opt` | | string | | protoc-gen-go-grpc 选项(如 `module=example.com/demo` |
| `--proto_path` | `-I` | string[] | | proto 导入搜索目录(可多次指定) |
| `--multiple` | `-m` | bool | `false` | 多服务模式 |
| `--client` | `-c` | bool | `true` | 是否生成 RPC 客户端代码 |
| `--style` | | string | `gozero` | 文件命名风格 |
| `--module` | | string | | 自定义 Go module 名称 |
| `--name-from-filename` | | bool | `false` | 使用文件名而非 package 名命名服务 |
| `--verbose` | `-v` | bool | `false` | 显示详细日志 |
| `--home` | | string | | goctl 模板目录 |
| `--remote` | | string | | 远程模板 Git 仓库地址 |
| `--branch` | | string | | 远程模板分支 |
### `goctl rpc new`
快速创建一个完整的 RPC 服务项目。
```bash
goctl rpc new <service_name> [flags]
```
**参数说明:**
| 参数 | 缩写 | 类型 | 默认值 | 说明 |
|------|------|------|--------|------|
| `--style` | | string | `gozero` | 文件命名风格 |
| `--client` | `-c` | bool | `true` | 是否生成 RPC 客户端代码 |
| `--module` | | string | | 自定义 Go module 名称 |
| `--verbose` | `-v` | bool | `false` | 显示详细日志 |
| `--idea` | | bool | `false` | 生成 IDE 项目标记 |
| `--name-from-filename` | | bool | `false` | 使用文件名而非 package 名命名服务 |
| `--home` | | string | | goctl 模板目录 |
| `--remote` | | string | | 远程模板 Git 仓库地址 |
| `--branch` | | string | | 远程模板分支 |
### `goctl rpc template`
生成 proto 文件模板。
```bash
goctl rpc template -o=<output_file> [flags]
```
**参数说明:**
| 参数 | 类型 | 说明 |
|------|------|------|
| `-o` | string | 输出文件路径(必填) |
| `--home` | string | goctl 模板目录 |
| `--remote` | string | 远程模板 Git 仓库地址 |
| `--branch` | string | 远程模板分支 |
---
## 功能详解
### 多服务模式(`--multiple`
当 proto 文件包含多个 `service` 定义时,必须使用 `--multiple` 标志。
```protobuf
service SearchService {
rpc Search(SearchReq) returns (SearchReply);
}
service NotifyService {
rpc Notify(NotifyReq) returns (NotifyReply);
}
```
**启用 `--multiple` 后的目录变化:**
| 特性 | 默认模式 | `--multiple` 模式 |
|------|---------|-------------------|
| 服务数量 | 仅 1 个 | 1 个或多个 |
| 客户端目录 | 以服务名命名 | 固定为 `client/` |
| 代码组织 | 扁平结构 | 按服务名分组 |
**`--multiple=false`(默认)的目录结构:**
```
output/
├── greeterclient/
│ └── greeter.go
├── internal/
│ ├── logic/
│ │ └── sayhellologic.go
│ └── server/
│ └── greeterserver.go
└── ...
```
**`--multiple=true` 的目录结构:**
```
output/
├── client/
│ ├── searchservice/
│ │ └── searchservice.go
│ └── notifyservice/
│ └── notifyservice.go
├── internal/
│ ├── logic/
│ │ ├── searchservice/
│ │ │ └── searchlogic.go
│ │ └── notifyservice/
│ │ └── notifylogic.go
│ └── server/
│ ├── searchservice/
│ │ └── searchserviceserver.go
│ └── notifyservice/
│ └── notifyserviceserver.go
└── ...
```
### 外部 Proto 导入(`--proto_path`
通过 `-I` / `--proto_path` 指定额外的 proto 搜索目录,支持以下场景:
- **同目录导入**`import "types.proto";`
- **子目录导入**`import "common/types.proto";`
- **外部目录导入**proto 文件位于项目外部
- **传递性导入**A 导入 BB 导入 Cgoctl 自动递归解析
- **跨包导入**:不同 `go_package` 的 proto 文件,自动生成正确的 Go 导入
```bash
# 从多个目录搜索 proto 文件
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I . -I ./shared_protos -I /path/to/external_protos
```
### 服务命名
默认情况下,服务名称来自 proto 的 **package 名称**(例如 `package user;` → 服务名 `user`)。这使得多个 proto 文件可以共享同一个 package
```
protos/
├── user_base.proto # package user;
├── user_auth.proto # package user;
└── user_profile.proto # package user;
```
三个文件会生成到同一个 `user` 服务中。
如需使用 proto 文件名命名(旧版行为),请添加 `--name-from-filename` 标志。
### 流式 RPC
支持 gRPC 的三种流式模式:
```protobuf
service StreamService {
rpc ServerStream(Req) returns (stream Reply); // 服务端流
rpc ClientStream(stream Req) returns (Reply); // 客户端流
rpc BidiStream(stream Req) returns (stream Reply); // 双向流
}
```
### Google 标准类型
goctl 自动识别并正确处理 Google protobuf 标准类型:
| Proto 类型 | Go 类型 |
|-----------|---------|
| `google.protobuf.Empty` | `emptypb.Empty` |
| `google.protobuf.Timestamp` | `timestamppb.Timestamp` |
| `google.protobuf.Duration` | `durationpb.Duration` |
| `google.protobuf.Any` | `anypb.Any` |
| `google.protobuf.Struct` | `structpb.Struct` |
| `google.protobuf.FieldMask` | `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` | `wrapperspb.*Value` |
这些类型可直接用作 RPC 参数类型goctl 会自动生成正确的导入。
---
## 完整示例
详见 [example/](example/) 目录,包含 10 个完整示例,覆盖所有生成场景。
| # | 示例 | 场景 |
|---|------|------|
| 01 | [基础服务](example/01-basic/) | 单服务,无导入 |
| 02 | [同级导入](example/02-import-sibling/) | 导入同目录 proto |
| 03 | [子目录导入](example/03-import-subdir/) | 导入子目录 proto |
| 04 | [传递性导入](example/04-transitive-import/) | A → B → C 依赖链 |
| 05 | [多服务](example/05-multiple-services/) | `--multiple` 模式 |
| 06 | [标准类型](example/06-wellknown-types/) | 消息中使用 Timestamp 等 |
| 07 | [外部 Proto同包](example/07-external-proto-same-pkg/) | 外部 proto相同 go_package |
| 08 | [外部 Proto跨包](example/08-external-proto-diff-pkg/) | 外部 proto不同 go_package |
| 09 | [Google 标准类型作参数](example/09-google-types-as-rpc/) | Empty/Timestamp 作为 RPC 参数 |
| 10 | [流式通信](example/10-streaming/) | 服务端/客户端/双向流 |
+315
View File
@@ -0,0 +1,315 @@
# goctl rpc — RPC 코드 생성
[English](README.md) | [中文](README-cn.md) | 한국어
goctl rpc는 `goctl` 스캐폴딩 도구의 RPC 서비스 코드 생성 모듈입니다. `.proto` 파일에서 완전한 zRPC 서비스를 생성합니다. proto 정의와 비즈니스 로직만 작성하면 나머지 보일러플레이트 코드는 모두 자동으로 생성됩니다.
## 기능
- **protoc 호환**: protoc와 완전히 호환되며 모든 protoc 인수를 그대로 전달합니다.
- **외부 proto import**: 디렉터리와 패키지를 넘나드는 proto import를 지원하고 전이 의존성을 자동으로 해결합니다.
- **다중 서비스**: 하나의 proto 파일에 여러 서비스를 정의하고, 서비스 이름별로 자동 그룹화합니다.
- **스트리밍 지원**: 서버 스트리밍, 클라이언트 스트리밍, 양방향 스트리밍을 지원합니다.
- **Google well-known types**: 지원되는 `google.protobuf.*` 타입을 자동으로 인식하고 올바른 Go import를 생성합니다.
- **클라이언트 생성**: 자동 생성된 RPC 클라이언트 래퍼 코드를 제공합니다.
## 사전 요구 사항
```bash
# protoc 플러그인 설치
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```
## 빠른 시작
### 방법 1: 즉시 서비스 만들기
```bash
goctl rpc new greeter
```
완전한 프로젝트 구조가 생성됩니다.
```
greeter/
├── etc/
│ └── greeter.yaml
├── greeter/
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeter.proto
├── greeterclient/
│ └── greeter.go
└── internal/
├── config/
│ └── config.go
├── logic/
│ └── pinglogic.go
├── server/
│ └── greeterserver.go
└── svc/
└── servicecontext.go
```
### 방법 2: proto 파일에서 생성하기
1. proto 템플릿을 생성합니다.
```bash
goctl rpc template -o=user.proto
```
2. 출력 디렉터리를 초기화하고 서비스 코드를 생성합니다.
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
---
## 명령 참조
### `goctl rpc protoc`
`.proto` 파일에서 zRPC 서비스 코드를 생성합니다.
```bash
goctl rpc protoc <proto_file> [flags]
```
**예시:**
```bash
# 기본 사용법
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
# 다중 서비스 모드
goctl rpc protoc multi.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -m
# 외부 proto import
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -I ./shared_protos
# Google well-known types 사용
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
**플래그:**
| 플래그 | 축약 | 타입 | 기본값 | 설명 |
|------|-------|------|---------|-------------|
| `--zrpc_out` | | string | **필수** | zRPC 서비스 코드 출력 디렉터리 |
| `--go_out` | | string | **필수** | protoc Go 코드 출력 디렉터리 |
| `--go-grpc_out` | | string | **필수** | protoc gRPC 코드 출력 디렉터리 |
| `--go_opt` | | string | | protoc-gen-go 옵션(예: `module=example.com/demo`) |
| `--go-grpc_opt` | | string | | protoc-gen-go-grpc 옵션(예: `module=example.com/demo`) |
| `--proto_path` | `-I` | string[] | | proto import 검색 디렉터리(반복 지정 가능) |
| `--multiple` | `-m` | bool | `false` | 다중 서비스 모드 |
| `--client` | `-c` | bool | `true` | RPC 클라이언트 코드 생성 |
| `--style` | | string | `gozero` | 파일 이름 스타일 |
| `--module` | | string | | 사용자 지정 Go module 이름 |
| `--name-from-filename` | | bool | `false` | 서비스 이름에 `package` 이름 대신 파일 이름 사용 |
| `--verbose` | `-v` | bool | `false` | 상세 로그 활성화 |
| `--home` | | string | | goctl 템플릿 디렉터리 |
| `--remote` | | string | | 원격 템플릿 Git 저장소 URL |
| `--branch` | | string | | 원격 템플릿 브랜치 |
### `goctl rpc new`
완전한 RPC 서비스 프로젝트를 빠르게 생성합니다.
```bash
goctl rpc new <service_name> [flags]
```
**플래그:**
| 플래그 | 축약 | 타입 | 기본값 | 설명 |
|------|-------|------|---------|-------------|
| `--style` | | string | `gozero` | 파일 이름 스타일 |
| `--client` | `-c` | bool | `true` | RPC 클라이언트 코드 생성 |
| `--module` | | string | | 사용자 지정 Go module 이름 |
| `--verbose` | `-v` | bool | `false` | 상세 로그 활성화 |
| `--idea` | | bool | `false` | IDE 프로젝트 마커 생성 |
| `--name-from-filename` | | bool | `false` | 서비스 이름에 `package` 이름 대신 파일 이름 사용 |
| `--home` | | string | | goctl 템플릿 디렉터리 |
| `--remote` | | string | | 원격 템플릿 Git 저장소 URL |
| `--branch` | | string | | 원격 템플릿 브랜치 |
### `goctl rpc template`
proto 파일 템플릿을 생성합니다.
```bash
goctl rpc template -o=<output_file> [flags]
```
**플래그:**
| 플래그 | 타입 | 설명 |
|------|------|-------------|
| `-o` | string | 출력 파일 경로(필수) |
| `--home` | string | goctl 템플릿 디렉터리 |
| `--remote` | string | 원격 템플릿 Git 저장소 URL |
| `--branch` | string | 원격 템플릿 브랜치 |
---
## 기능 상세
### 다중 서비스 모드(`--multiple`)
proto 파일에 여러 `service` 정의가 포함된 경우 `--multiple` 플래그가 필요합니다.
```protobuf
service SearchService {
rpc Search(SearchReq) returns (SearchReply);
}
service NotifyService {
rpc Notify(NotifyReq) returns (NotifyReply);
}
```
**`--multiple` 사용 시 디렉터리 차이:**
| 기능 | 기본 모드 | `--multiple` 모드 |
|---------|-------------|-------------------|
| proto당 서비스 수 | 정확히 1개 | 1개 이상 |
| 클라이언트 디렉터리 | 서비스 이름 기반 | 고정 `client/` 디렉터리 |
| 코드 구성 | 평면 구조 | 서비스 이름별 그룹화 |
**`--multiple=false`(기본값) 디렉터리 구조:**
```
output/
├── greeterclient/
│ └── greeter.go
├── internal/
│ ├── logic/
│ │ └── sayhellologic.go
│ └── server/
│ └── greeterserver.go
└── ...
```
**`--multiple=true` 디렉터리 구조:**
```
output/
├── client/
│ ├── searchservice/
│ │ └── searchservice.go
│ └── notifyservice/
│ └── notifyservice.go
├── internal/
│ ├── logic/
│ │ ├── searchservice/
│ │ │ └── searchlogic.go
│ │ └── notifyservice/
│ │ └── notifylogic.go
│ └── server/
│ ├── searchservice/
│ │ └── searchserviceserver.go
│ └── notifyservice/
│ └── notifyserviceserver.go
└── ...
```
### 외부 proto import(`--proto_path`)
`-I` / `--proto_path`로 추가 proto 검색 디렉터리를 지정합니다. 지원되는 시나리오는 다음과 같습니다.
- **같은 디렉터리 import**: `import "types.proto";`
- **하위 디렉터리 import**: `import "common/types.proto";`
- **외부 디렉터리 import**: 프로젝트 외부에 있는 proto 파일
- **전이 import**: A가 B를 import하고 B가 C를 import하는 경우 — goctl이 재귀적으로 해결합니다.
- **패키지 간 import**: 서로 다른 `go_package` 값을 가진 파일에 대해 올바른 Go import를 자동으로 생성합니다.
```bash
# 여러 디렉터리에서 proto 파일 검색
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I . -I ./shared_protos -I /path/to/external_protos
```
### 서비스 이름 지정
기본적으로 서비스 이름은 proto의 **`package` 이름**에서 파생됩니다(예: `package user;` → 서비스 이름 `user`). 이를 통해 여러 proto 파일이 같은 `package`를 공유할 수 있습니다.
```
protos/
├── user_base.proto # package user;
├── user_auth.proto # package user;
└── user_profile.proto # package user;
```
세 파일은 모두 하나의 `user` 서비스로 생성됩니다.
서비스 이름에 proto 파일 이름을 사용하려면(레거시 동작) `--name-from-filename` 플래그를 추가하세요.
### 스트리밍 RPC
세 가지 gRPC 스트리밍 패턴을 모두 지원합니다.
```protobuf
service StreamService {
rpc ServerStream(Req) returns (stream Reply); // 서버 스트리밍
rpc ClientStream(stream Req) returns (Reply); // 클라이언트 스트리밍
rpc BidiStream(stream Req) returns (stream Reply); // 양방향 스트리밍
}
```
### Google well-known types
goctl은 지원되는 Google protobuf well-known types를 자동으로 인식하고 처리합니다.
| Proto 타입 | Go 타입 |
|-----------|---------|
| `google.protobuf.Empty` | `emptypb.Empty` |
| `google.protobuf.Timestamp` | `timestamppb.Timestamp` |
| `google.protobuf.Duration` | `durationpb.Duration` |
| `google.protobuf.Any` | `anypb.Any` |
| `google.protobuf.Struct` | `structpb.Struct` |
| `google.protobuf.FieldMask` | `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` | `wrapperspb.*Value` |
이 타입들은 RPC 파라미터 타입으로 직접 사용할 수 있으며, goctl이 올바른 import를 자동으로 생성합니다.
---
## 예제
모든 생성 시나리오를 다루는 10개의 완전한 예제는 [example/](example/) 디렉터리를 참고하세요.
| # | 예제 | 시나리오 |
|---|---------|----------|
| 01 | [기본 서비스](example/01-basic/) | 단일 서비스, import 없음 |
| 02 | [동일 디렉터리 import](example/02-import-sibling/) | 같은 디렉터리에서 import |
| 03 | [하위 디렉터리 import](example/03-import-subdir/) | 하위 디렉터리에서 import |
| 04 | [전이 import](example/04-transitive-import/) | A → B → C 의존성 체인 |
| 05 | [다중 서비스](example/05-multiple-services/) | `--multiple` 모드 |
| 06 | [Google well-known types](example/06-wellknown-types/) | 메시지에서 Timestamp 등 사용 |
| 07 | [외부 proto(동일 패키지)](example/07-external-proto-same-pkg/) | 외부 proto, 같은 go_package |
| 08 | [외부 proto(다른 패키지)](example/08-external-proto-diff-pkg/) | 외부 proto, 다른 go_package |
| 09 | [Google well-known types를 파라미터로 사용](example/09-google-types-as-rpc/) | Empty/Timestamp를 RPC 파라미터로 사용 |
| 10 | [스트리밍](example/10-streaming/) | 서버/클라이언트/양방향 스트리밍 |
+286 -199
View File
@@ -1,228 +1,315 @@
# Rpc Generation
# goctl rpc — RPC Code Generation
Goctl Rpc是`goctl`脚手架下的一个rpc服务代码生成模块支持proto模板生成和rpc服务代码生成通过此工具生成代码你只需要关注业务逻辑编写而不用去编写一些重复性的代码。这使得我们把精力重心放在业务上从而加快了开发效率且降低了代码出错率。
English | [中文](README-cn.md) | [한국어](README-ko.md)
## 特性
goctl rpc is the RPC service code generation module of the `goctl` scaffold. It generates a complete zRPC service from `.proto` files. You only need to write the proto definition and business logic — all boilerplate code is generated automatically.
* 简单易用
* 快速提升开发效率
* 出错率低
* 贴近 protoc
## Features
- **protoc compatible**: Fully compatible with protoc, all protoc arguments are passed through
- **External proto imports**: Cross-directory and cross-package proto imports with automatic transitive dependency resolution
- **Multiple services**: Define multiple services in a single proto file, auto-grouped by service name
- **Streaming support**: Server streaming, client streaming, and bidirectional streaming
- **Google well-known types**: Automatic recognition of supported `google.protobuf.*` types with correct Go imports
- **Client generation**: Auto-generated RPC client wrapper code
## 快速开始
### 方式一快速生成greet服务
通过命令 `goctl rpc new ${servieName}`生成
如生成greet rpc服务
```Bash
goctl rpc new greet
```
执行后代码结构如下:
```text
.
└── greet
├── etc
│   └── greet.yaml
├── greet
│   ├── greet.go
│   ├── greet.pb.go
│   └── greet_grpc.pb.go
├── greet.go
├── greet.proto
└── internal
├── config
│   └── config.go
├── logic
│   └── pinglogic.go
├── server
│   └── greetserver.go
└── svc
└── servicecontext.go
```
### 方式二通过指定proto生成rpc服务
* 生成proto模板
```Bash
$ goctl rpc template -o=user.proto
```
```proto
syntax = "proto3";
package user;
option go_package="./user";
message Request {
string ping = 1;
}
message Response {
string pong = 1;
}
service User {
rpc Ping(Request) returns(Response);
}
```
* 生成rpc服务代码
## Prerequisites
```bash
$ goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
# Install protoc plugins
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
```
## Quick Start
## 用法
### Method 1: Create a Service Instantly
### rpc 服务生成用法
```Bash
$ goctl rpc protoc -h
Generate grpc code
Usage:
goctl rpc protoc [flags]
Examples:
goctl rpc protoc xx.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=.
Flags:
--branch string The branch of the remote repo, it does work with --remote
-h, --help help for protoc
--home string The goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
-m, --multiple Generated in multiple rpc service mode
--name-from-filename Use proto filename instead of package name for service naming (legacy behavior)
--remote string The remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
The git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure
--style string The file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md] (default "gozero")
-v, --verbose Enable log output
--zrpc_out string The zrpc output directory
```bash
goctl rpc new greeter
```
### 参数说明
Generates a complete project structure:
* --branch 指定远程仓库模板分支
* --home 指定goctl模板根目录
* -m, --multiple 指定生成多个rpc服务模式, 默认为 false, 如果为 false, 则只支持生成一个rpc service, 如果为 true, 则支持生成多个 rpc service且多个 rpc service 会分组。
* --name-from-filename 使用proto文件名而非package名称来命名服务旧版行为。默认使用package名称这样可以支持多个proto文件共享同一个package。
* --style 指定文件输出格式
* -v, --verbose 显示日志
* --zrpc_out 指定zrpc输出目录
```
greeter/
├── etc/
│ └── greeter.yaml
├── greeter/
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeter.proto
├── greeterclient/
│ └── greeter.go
└── internal/
├── config/
│ └── config.go
├── logic/
│ └── pinglogic.go
├── server/
│ └── greeterserver.go
└── svc/
└── servicecontext.go
```
> ## --multiple
> 是否开启多个 rpc service 生成,如果开启,则满足一下新特性
> 1. 支持 1 到多个 rpc service
> 2. 生成 rpc 服务会按照服务名称分组(尽管只有一个 rpc service
> 3. rpc client 的文件目录变更为固定名称 `client`
>
> 如果不开启,则和旧版本 rpc 生成逻辑一样(兼容)
> 1. 有且只能有一个 rpc service
### Method 2: Generate from a Proto File
> ## Service Naming (Multi-Proto File Support)
>
> By default, the service name is derived from the **proto package name** (e.g., `package user;` → service name `user`).
> This enables splitting a large proto file into multiple smaller files that share the same package name,
> which is particularly useful for AI-assisted development where smaller files are easier to process.
>
> **Example: Multiple proto files with same package**
> ```
> protos/
> ├── user_base.proto # package user;
> ├── user_auth.proto # package user;
> └── user_profile.proto # package user;
> ```
> All three files will generate into a single `user` service.
>
> **Legacy behavior (--name-from-filename)**
>
> If you need the old behavior where service name is derived from the proto filename,
> use the `--name-from-filename` flag:
> ```bash
> goctl rpc protoc user.proto --go_out=./pb --go-grpc_out=./pb --zrpc_out=. --name-from-filename
> ```
1. Generate a proto template:
```bash
goctl rpc template -o=user.proto
```
## rpc 服务生成 example
详情见 [example/rpc](https://github.com/zeromicro/go-zero/tree/master/tools/goctl/example)
2. Initialize the output directory and generate service code:
## --multiple 为 true 和 false 的目录区别
源 proto 文件
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
---
## Command Reference
### `goctl rpc protoc`
Generate zRPC service code from a `.proto` file.
```bash
goctl rpc protoc <proto_file> [flags]
```
**Examples:**
```bash
# Basic usage
goctl rpc protoc user.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
# Multiple services mode
goctl rpc protoc multi.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -m
# Import external protos
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I . -I ./shared_protos
# Use Google well-known types
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo -I .
```
**Flags:**
| Flag | Short | Type | Default | Description |
|------|-------|------|---------|-------------|
| `--zrpc_out` | | string | **required** | Output directory for zRPC service code |
| `--go_out` | | string | **required** | Output directory for protoc Go code |
| `--go-grpc_out` | | string | **required** | Output directory for protoc gRPC code |
| `--go_opt` | | string | | Options for protoc-gen-go (e.g., `module=example.com/demo`) |
| `--go-grpc_opt` | | string | | Options for protoc-gen-go-grpc (e.g., `module=example.com/demo`) |
| `--proto_path` | `-I` | string[] | | Proto import search directories (repeatable) |
| `--multiple` | `-m` | bool | `false` | Multiple services mode |
| `--client` | `-c` | bool | `true` | Generate RPC client code |
| `--style` | | string | `gozero` | File naming style |
| `--module` | | string | | Custom Go module name |
| `--name-from-filename` | | bool | `false` | Use filename instead of package name for service naming |
| `--verbose` | `-v` | bool | `false` | Enable verbose logging |
| `--home` | | string | | goctl template directory |
| `--remote` | | string | | Remote template Git repository URL |
| `--branch` | | string | | Remote template branch |
### `goctl rpc new`
Quickly create a complete RPC service project.
```bash
goctl rpc new <service_name> [flags]
```
**Flags:**
| Flag | Short | Type | Default | Description |
|------|-------|------|---------|-------------|
| `--style` | | string | `gozero` | File naming style |
| `--client` | `-c` | bool | `true` | Generate RPC client code |
| `--module` | | string | | Custom Go module name |
| `--verbose` | `-v` | bool | `false` | Enable verbose logging |
| `--idea` | | bool | `false` | Generate IDE project marker |
| `--name-from-filename` | | bool | `false` | Use filename instead of package name for service naming |
| `--home` | | string | | goctl template directory |
| `--remote` | | string | | Remote template Git repository URL |
| `--branch` | | string | | Remote template branch |
### `goctl rpc template`
Generate a proto file template.
```bash
goctl rpc template -o=<output_file> [flags]
```
**Flags:**
| Flag | Type | Description |
|------|------|-------------|
| `-o` | string | Output file path (required) |
| `--home` | string | goctl template directory |
| `--remote` | string | Remote template Git repository URL |
| `--branch` | string | Remote template branch |
---
## Feature Details
### Multiple Services Mode (`--multiple`)
When a proto file contains multiple `service` definitions, the `--multiple` flag is required.
```protobuf
syntax = "proto3";
package hello;
option go_package = "./hello";
message HelloReq {
string in = 1;
service SearchService {
rpc Search(SearchReq) returns (SearchReply);
}
message HelloResp {
string msg = 1;
}
service Greet {
rpc SayHello(HelloReq) returns (HelloResp);
service NotifyService {
rpc Notify(NotifyReq) returns (NotifyReply);
}
```
### --multiple=true
**Directory differences with `--multiple`:**
```text
hello
├── client // 区别1rpc client 目录固定为 client 名称
│   └── greet // 区别2会按照 rpc service 名称分组
│   └── greet.go
├── etc
│   └── hello.yaml
├── hello.go
├── internal
│   ├── config
│   │   └── config.go
   ├── logic
│   │   └── greet // 区别2会按照 rpc service 名称分组
   │   └── sayhellologic.go
   ├── server
   │   └── greet // 区别2会按照 rpc service 名称分组
   │   └── greetserver.go
│   └── svc
│   └── servicecontext.go
└── pb
└── hello
├── hello.pb.go
└── hello_grpc.pb.go
| Feature | Default mode | `--multiple` mode |
|---------|-------------|-------------------|
| Services per proto | Exactly 1 | 1 or more |
| Client directory | Named after service | Fixed `client/` directory |
| Code organization | Flat structure | Grouped by service name |
**`--multiple=false` (default) directory structure:**
```
output/
├── greeterclient/
└── greeter.go
├── internal/
├── logic/
│ └── sayhellologic.go
└── server/
└── greeterserver.go
└── ...
```
### --multiple=false (旧版本目录,向后兼容)
```text
hello
├── etc
│   └── hello.yaml
├── greet
   └── greet.go
├── hello.go
├── internal
│   ├── config
   │   └── config.go
   ├── logic
   │   └── sayhellologic.go
   ├── server
   │   └── greetserver.go
   └── svc
   ── servicecontext.go
└── pb
└── hello
── hello.pb.go
└── hello_grpc.pb.go
```
**`--multiple=true` directory structure:**
```
output/
├── client/
│ ├── searchservice/
│ └── searchservice.go
│ └── notifyservice/
│ └── notifyservice.go
├── internal/
├── logic/
│ ├── searchservice/
│ │ └── searchlogic.go
│ └── notifyservice/
│ └── notifylogic.go
└── server/
── searchservice/
│ │ └── searchserviceserver.go
└── notifyservice/
── notifyserviceserver.go
└── ...
```
### External Proto Imports (`--proto_path`)
Use `-I` / `--proto_path` to specify additional proto search directories. Supported scenarios:
- **Same-directory import**: `import "types.proto";`
- **Subdirectory import**: `import "common/types.proto";`
- **External directory import**: Proto files outside the project
- **Transitive imports**: A imports B, B imports C — goctl resolves recursively
- **Cross-package imports**: Different `go_package` values generate correct Go imports automatically
```bash
# Search multiple directories for proto files
goctl rpc protoc service.proto \
--go_out=output --go-grpc_out=output --zrpc_out=output \
--go_opt=module=example.com/demo --go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I . -I ./shared_protos -I /path/to/external_protos
```
### Service Naming
By default, the service name is derived from the proto **package name** (e.g., `package user;` → service name `user`). This allows multiple proto files to share the same package:
```
protos/
├── user_base.proto # package user;
├── user_auth.proto # package user;
└── user_profile.proto # package user;
```
All three files generate into a single `user` service.
To use the proto filename for naming (legacy behavior), add the `--name-from-filename` flag.
### Streaming RPC
All three gRPC streaming patterns are supported:
```protobuf
service StreamService {
rpc ServerStream(Req) returns (stream Reply); // Server streaming
rpc ClientStream(stream Req) returns (Reply); // Client streaming
rpc BidiStream(stream Req) returns (stream Reply); // Bidirectional streaming
}
```
### Google Well-Known Types
goctl automatically recognizes and handles Google protobuf well-known types:
| Proto Type | Go Type |
|-----------|---------|
| `google.protobuf.Empty` | `emptypb.Empty` |
| `google.protobuf.Timestamp` | `timestamppb.Timestamp` |
| `google.protobuf.Duration` | `durationpb.Duration` |
| `google.protobuf.Any` | `anypb.Any` |
| `google.protobuf.Struct` | `structpb.Struct` |
| `google.protobuf.FieldMask` | `fieldmaskpb.FieldMask` |
| `google.protobuf.*Value` | `wrapperspb.*Value` |
These types can be used directly as RPC parameter types — goctl generates the correct imports automatically.
---
## Examples
See the [example/](example/) directory for 10 complete examples covering all generation scenarios.
| # | Example | Scenario |
|---|---------|----------|
| 01 | [Basic service](example/01-basic/) | Single service, no imports |
| 02 | [Sibling import](example/02-import-sibling/) | Import from same directory |
| 03 | [Subdirectory import](example/03-import-subdir/) | Import from subdirectory |
| 04 | [Transitive import](example/04-transitive-import/) | A → B → C dependency chain |
| 05 | [Multiple services](example/05-multiple-services/) | `--multiple` mode |
| 06 | [Well-known types](example/06-wellknown-types/) | Timestamp etc. in messages |
| 07 | [External proto (same pkg)](example/07-external-proto-same-pkg/) | External proto, same go_package |
| 08 | [External proto (diff pkg)](example/08-external-proto-diff-pkg/) | External proto, different go_package |
| 09 | [Google well-known types as params](example/09-google-types-as-rpc/) | Empty/Timestamp as RPC parameters |
| 10 | [Streaming](example/10-streaming/) | Server/client/bidirectional streaming |
+1
View File
@@ -98,6 +98,7 @@ func RPCNew(_ *cobra.Command, args []string) error {
ctx.IsGenClient = VarBoolClient
ctx.Module = VarStringModule
ctx.NameFromFilename = VarBoolNameFromFilename
ctx.ProtoPaths = []string{filepath.Dir(src)}
grpcOptList := VarStringSliceGoGRPCOpt
if len(grpcOptList) > 0 {
+1
View File
@@ -105,6 +105,7 @@ func ZRPC(_ *cobra.Command, args []string) error {
ctx.IsGenClient = VarBoolClient
ctx.Module = VarStringModule
ctx.NameFromFilename = VarBoolNameFromFilename
ctx.ProtoPaths = VarStringSliceProtoPath
g := generator.NewGenerator(style, verbose)
return g.Generate(&ctx)
}
@@ -0,0 +1,102 @@
# 示例 01基础 RPC 服务
[English](README.md) | 中文 | [한국어](README-ko.md)
这是使用 goctl 生成 RPC 服务的最简单示例。
## Proto 定义
一个 `greeter.proto` 文件,包含一个服务和一个 RPC 方法,无外部导入。
`go_package` 使用完整的模块路径:
```protobuf
option go_package = "example.com/demo/greeter";
```
## 生成命令
### 方式一:使用 `goctl rpc new` 快速创建
```bash
# 一条命令创建完整的 RPC 项目
goctl rpc new greeter
```
该命令会同时生成 proto 文件和服务代码:
```
greeter/
├── etc
│ └── greeter.yaml
├── greeter
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeter.proto
├── greeterclient
│ └── greeter.go
└── internal
├── config
│ └── config.go
├── logic
│ └── pinglogic.go
├── server
│ └── greeterserver.go
└── svc
└── servicecontext.go
```
### 方式二:基于已有 Proto 文件生成
首先,在输出目录中初始化 `go.mod`
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
然后生成代码:
```bash
goctl rpc protoc greeter.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
生成的目录结构:
```
output/
├── etc
│ └── greeter.yaml
├── go.mod
├── greeter
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeterclient
│ └── greeter.go
└── internal
├── config
│ └── config.go
├── logic
│ └── sayhellologic.go
├── server
│ └── greeterserver.go
└── svc
└── servicecontext.go
```
## 要点说明
- 这是最简单的场景:一个 proto 文件、一个服务、一个 RPC 方法。
- `go_package` 使用完整的模块路径(`example.com/demo/greeter`),而非相对路径。
- `--module` 告诉 goctl Go 模块名;`--go_opt=module=...``--go-grpc_opt=module=...` 告诉 protoc 从输出路径中去除模块前缀。
- `--zrpc_out` 指定 goctl 生成的服务代码输出目录。
- `--go_out``--go-grpc_out` 指定 protoc 生成代码的输出目录。
- 编辑逻辑文件(`internal/logic/sayhellologic.go`)来实现业务逻辑。
@@ -0,0 +1,102 @@
# 예제 01: 기본 RPC 서비스
[English](README.md) | [中文](README-cn.md) | 한국어
이 예제는 goctl로 RPC 서비스를 생성하는 가장 간단한 예제입니다.
## proto 정의
외부 import 없이 하나의 서비스와 하나의 RPC 메서드를 가진 `greeter.proto` 파일 하나를 사용합니다.
`go_package`는 전체 모듈 경로를 사용합니다.
```protobuf
option go_package = "example.com/demo/greeter";
```
## 생성 명령
### 방법 1: `goctl rpc new`로 빠르게 시작
```bash
# 한 번의 명령으로 완전한 RPC 프로젝트 생성
goctl rpc new greeter
```
이 명령은 proto 파일과 서비스 코드를 함께 생성합니다.
```
greeter/
├── etc
│ └── greeter.yaml
├── greeter
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeter.proto
├── greeterclient
│ └── greeter.go
└── internal
├── config
│ └── config.go
├── logic
│ └── pinglogic.go
├── server
│ └── greeterserver.go
└── svc
└── servicecontext.go
```
### 방법 2: 기존 proto에서 생성
먼저 출력 디렉터리에 `go.mod`를 초기화합니다.
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
그런 다음 코드를 생성합니다.
```bash
goctl rpc protoc greeter.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
생성되는 디렉터리 구조:
```
output/
├── etc
│ └── greeter.yaml
├── go.mod
├── greeter
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeterclient
│ └── greeter.go
└── internal
├── config
│ └── config.go
├── logic
│ └── sayhellologic.go
├── server
│ └── greeterserver.go
└── svc
└── servicecontext.go
```
## 핵심 사항
- 가장 단순한 시나리오입니다. proto 파일 하나, 서비스 하나, RPC 메서드 하나를 사용합니다.
- `go_package`는 상대 경로가 아닌 전체 모듈 경로(`example.com/demo/greeter`)를 사용합니다.
- `--module` 플래그는 goctl에 Go 모듈 이름을 알려줍니다. `--go_opt=module=...``--go-grpc_opt=module=...`은 protoc에 출력 경로에서 모듈 접두사를 제거하라고 알려줍니다.
- `--zrpc_out` 플래그는 goctl이 생성하는 서비스 코드의 출력 위치를 지정합니다.
- `--go_out``--go-grpc_out` 플래그는 protoc가 생성하는 코드의 출력 위치를 지정합니다.
- 비즈니스 로직을 구현하려면 logic 파일(`internal/logic/sayhellologic.go`)을 수정하세요.
+102
View File
@@ -0,0 +1,102 @@
# Example 01: Basic RPC Service
English | [中文](README-cn.md) | [한국어](README-ko.md)
This is the simplest example of generating an RPC service with goctl.
## Proto Definition
A single `greeter.proto` file with one service and one RPC method, no external imports.
The `go_package` uses a full module path:
```protobuf
option go_package = "example.com/demo/greeter";
```
## Generation Commands
### Method 1: Quick Start with `goctl rpc new`
```bash
# Create a complete RPC project with one command
goctl rpc new greeter
```
This generates the proto file and service code together:
```
greeter/
├── etc
│ └── greeter.yaml
├── greeter
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeter.proto
├── greeterclient
│ └── greeter.go
└── internal
├── config
│ └── config.go
├── logic
│ └── pinglogic.go
├── server
│ └── greeterserver.go
└── svc
└── servicecontext.go
```
### Method 2: Generate from an Existing Proto
First, initialize the output directory with a `go.mod`:
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
Then generate the code:
```bash
goctl rpc protoc greeter.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
Generated directory structure:
```
output/
├── etc
│ └── greeter.yaml
├── go.mod
├── greeter
│ ├── greeter.pb.go
│ └── greeter_grpc.pb.go
├── greeter.go
├── greeterclient
│ └── greeter.go
└── internal
├── config
│ └── config.go
├── logic
│ └── sayhellologic.go
├── server
│ └── greeterserver.go
└── svc
└── servicecontext.go
```
## Key Points
- This is the simplest scenario: one proto file, one service, one RPC method.
- The `go_package` uses a full module path (`example.com/demo/greeter`), not a relative path.
- The `--module` flag tells goctl the Go module name; `--go_opt=module=...` and `--go-grpc_opt=module=...` tell protoc to strip the module prefix from output paths.
- The `--zrpc_out` flag specifies where the goctl-generated service code goes.
- The `--go_out` and `--go-grpc_out` flags specify where protoc-generated code goes.
- Edit the logic file (`internal/logic/sayhellologic.go`) to implement your business logic.
@@ -0,0 +1,17 @@
syntax = "proto3";
package greeter;
option go_package = "example.com/demo/greeter";
message HelloReq {
string name = 1;
}
message HelloReply {
string message = 1;
}
service Greeter {
rpc SayHello(HelloReq) returns (HelloReply);
}
@@ -0,0 +1,79 @@
# 示例 02导入同级 Proto 文件
[English](README.md) | 中文 | [한국어](README-ko.md)
本示例演示如何导入同一目录下的 proto 文件。
## Proto 定义
同一目录下的两个 proto 文件共享相同的 `go_package`
- `types.proto` — 定义共享消息类型(`User`)。
- `user.proto` — 定义 RPC 服务,导入 `types.proto`
两个文件使用相同的 `go_package`,采用完整模块路径:
```protobuf
option go_package = "example.com/demo/pb";
```
`user.proto` 通过以下方式导入 `types.proto`
```protobuf
import "types.proto";
```
## 生成命令
首先,在输出目录中初始化 `go.mod`
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
然后生成代码:
```bash
goctl rpc protoc user.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
生成的目录结构:
```
output/
├── etc
│ └── usersvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── createuserlogic.go
│ │ └── getuserlogic.go
│ ├── server
│ │ └── userserviceserver.go
│ └── svc
│ └── servicecontext.go
├── pb
│ ├── types.pb.go
│ ├── user.pb.go
│ └── user_grpc.pb.go
├── userservice
│ └── userservice.go
└── usersvc.go
```
## 要点说明
- 两个 proto 文件(`user.proto``types.proto`)共享相同的 `go_package = "example.com/demo/pb"`,编译到同一个 Go 包中。
- `user.proto` 通过 `import "types.proto"` 导入 `types.proto`
- 多个 proto 文件共享相同的 `go_package` 时,它们会编译到同一个 Go 包中。
- 只需将包含 `service` 定义的 proto 文件传递给 `goctl rpc protoc`
- 导入的 proto 文件会被 protoc 自动编译,并由 goctl 自动解析。
@@ -0,0 +1,79 @@
# 예제 02: 같은 디렉터리의 proto 파일 import
[English](README.md) | [中文](README-cn.md) | 한국어
이 예제는 같은 디렉터리에 있는 proto 파일을 import하는 방법을 보여줍니다.
## proto 정의
같은 디렉터리의 두 proto 파일이 동일한 `go_package`를 공유합니다.
- `types.proto` — 공유 메시지 타입(`User`)을 정의합니다.
- `user.proto` — RPC 서비스를 정의하고 `types.proto`를 import합니다.
두 파일은 전체 모듈 경로를 가진 동일한 `go_package`를 사용합니다.
```protobuf
option go_package = "example.com/demo/pb";
```
`user.proto`는 다음과 같이 `types.proto`를 import합니다.
```protobuf
import "types.proto";
```
## 생성 명령
먼저 출력 디렉터리에 `go.mod`를 초기화합니다.
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
그런 다음 코드를 생성합니다.
```bash
goctl rpc protoc user.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
생성되는 디렉터리 구조:
```
output/
├── etc
│ └── usersvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── createuserlogic.go
│ │ └── getuserlogic.go
│ ├── server
│ │ └── userserviceserver.go
│ └── svc
│ └── servicecontext.go
├── pb
│ ├── types.pb.go
│ ├── user.pb.go
│ └── user_grpc.pb.go
├── userservice
│ └── userservice.go
└── usersvc.go
```
## 핵심 사항
- 두 proto 파일(`user.proto``types.proto`)은 동일한 `go_package = "example.com/demo/pb"`를 공유하며 하나의 Go 패키지로 컴파일됩니다.
- `user.proto``import "types.proto"``types.proto`를 import합니다.
- 여러 proto 파일이 동일한 `go_package`를 공유하면 하나의 Go 패키지로 컴파일됩니다.
- `service` 정의를 포함한 proto 파일만 `goctl rpc protoc`에 전달하면 됩니다.
- import된 proto는 protoc가 자동으로 컴파일하고 goctl이 해결합니다.
@@ -0,0 +1,79 @@
# Example 02: Importing a Sibling Proto File
English | [中文](README-cn.md) | [한국어](README-ko.md)
This example demonstrates importing a proto file from the same directory.
## Proto Definition
Two proto files in the same directory share the same `go_package`:
- `types.proto` — Defines shared message types (`User`).
- `user.proto` — Defines the RPC service, importing `types.proto`.
Both files use the same `go_package` with a full module path:
```protobuf
option go_package = "example.com/demo/pb";
```
`user.proto` imports `types.proto` via:
```protobuf
import "types.proto";
```
## Generation Commands
First, initialize the output directory with a `go.mod`:
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
Then generate the code:
```bash
goctl rpc protoc user.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
Generated directory structure:
```
output/
├── etc
│ └── usersvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── createuserlogic.go
│ │ └── getuserlogic.go
│ ├── server
│ │ └── userserviceserver.go
│ └── svc
│ └── servicecontext.go
├── pb
│ ├── types.pb.go
│ ├── user.pb.go
│ └── user_grpc.pb.go
├── userservice
│ └── userservice.go
└── usersvc.go
```
## Key Points
- Two proto files (`user.proto` and `types.proto`) share the same `go_package = "example.com/demo/pb"`, compiled into a single Go package.
- `user.proto` imports `types.proto` via `import "types.proto"`.
- When multiple proto files share the same `go_package`, they compile into a single Go package.
- Only the proto file containing `service` definitions needs to be passed to `goctl rpc protoc`.
- The imported proto is automatically compiled by protoc and resolved by goctl.
@@ -0,0 +1,11 @@
syntax = "proto3";
package types;
option go_package = "example.com/demo/pb";
message User {
string id = 1;
string name = 2;
int32 age = 3;
}
@@ -0,0 +1,29 @@
syntax = "proto3";
package usersvc;
option go_package = "example.com/demo/pb";
import "types.proto";
message GetUserReq {
string id = 1;
}
message GetUserReply {
types.User user = 1;
}
message CreateUserReq {
string name = 1;
int32 age = 2;
}
message CreateUserReply {
types.User user = 1;
}
service UserService {
rpc GetUser(GetUserReq) returns (GetUserReply);
rpc CreateUser(CreateUserReq) returns (CreateUserReply);
}
@@ -0,0 +1,84 @@
# 示例 03导入子目录中的 Proto 文件
[English](README.md) | 中文 | [한국어](README-ko.md)
本示例演示如何导入子目录中的 proto 文件。
## Proto 定义
两个 proto 文件有**不同**的 `go_package` 值:
- `order.proto` — 定义 `OrderService`,导入 `common/types.proto`
```protobuf
option go_package = "example.com/demo/pb";
```
- `common/types.proto` — 定义可复用的分页和排序消息。
```protobuf
option go_package = "example.com/demo/pb/common";
```
`order.proto` 从子目录导入 `common/types.proto`
```protobuf
import "common/types.proto";
```
注意两个文件的 `go_package` **不同**,因此会编译到不同的 Go 包中。
## 生成命令
首先,在输出目录中初始化 `go.mod`
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
然后生成代码:
```bash
goctl rpc protoc order.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
生成的目录结构:
```
output/
├── etc
│ └── ordersvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── getorderlogic.go
│ │ └── listorderslogic.go
│ ├── server
│ │ └── orderserviceserver.go
│ └── svc
│ └── servicecontext.go
├── orderservice
│ └── orderservice.go
├── ordersvc.go
└── pb
├── common
│ └── types.pb.go
├── order.pb.go
└── order_grpc.pb.go
```
## 要点说明
- 两个 proto 文件有**不同**的 `go_package` 值,编译到不同的 Go 包中(`pb/``pb/common/`)。
- `order.proto` 从子目录导入 `common/types.proto`
- 当导入的 proto 文件有不同的 `go_package`goctl 会自动生成跨包导入。
- `-I .` 告诉 protoc 从当前目录开始搜索,使其能够找到 `common/types.proto`
@@ -0,0 +1,84 @@
# 예제 03: 하위 디렉터리의 proto import
[English](README.md) | [中文](README-cn.md) | 한국어
이 예제는 하위 디렉터리에 있는 proto 파일을 import하는 방법을 보여줍니다.
## proto 정의
두 proto 파일은 **서로 다른** `go_package` 값을 사용합니다.
- `order.proto``OrderService`를 정의하고 `common/types.proto`를 import합니다.
```protobuf
option go_package = "example.com/demo/pb";
```
- `common/types.proto` — 재사용 가능한 페이지네이션 및 정렬 메시지를 정의합니다.
```protobuf
option go_package = "example.com/demo/pb/common";
```
`order.proto`는 하위 디렉터리에서 `common/types.proto`를 import합니다.
```protobuf
import "common/types.proto";
```
두 파일은 **서로 다른** `go_package` 값을 가지므로 별도의 Go 패키지로 컴파일됩니다.
## 생성 명령
먼저 출력 디렉터리에 `go.mod`를 초기화합니다.
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
그런 다음 코드를 생성합니다.
```bash
goctl rpc protoc order.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
생성되는 디렉터리 구조:
```
output/
├── etc
│ └── ordersvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── getorderlogic.go
│ │ └── listorderslogic.go
│ ├── server
│ │ └── orderserviceserver.go
│ └── svc
│ └── servicecontext.go
├── orderservice
│ └── orderservice.go
├── ordersvc.go
└── pb
├── common
│ └── types.pb.go
├── order.pb.go
└── order_grpc.pb.go
```
## 핵심 사항
- 두 proto 파일은 **서로 다른** `go_package` 값을 가지므로 별도의 Go 패키지(`pb/``pb/common/`)로 컴파일됩니다.
- `order.proto`는 하위 디렉터리에서 `common/types.proto`를 import합니다.
- import된 proto의 `go_package`가 다르면 goctl은 패키지 간 import를 자동으로 생성합니다.
- `-I .` 플래그는 protoc에 현재 디렉터리부터 검색하라고 알려주어 `common/types.proto`를 찾을 수 있게 합니다.
@@ -0,0 +1,84 @@
# Example 03: Importing Proto from a Subdirectory
English | [中文](README-cn.md) | [한국어](README-ko.md)
This example demonstrates importing a proto file from a subdirectory.
## Proto Definition
Two proto files with **different** `go_package` values:
- `order.proto` — Defines the `OrderService`, imports `common/types.proto`.
```protobuf
option go_package = "example.com/demo/pb";
```
- `common/types.proto` — Defines reusable pagination and sorting messages.
```protobuf
option go_package = "example.com/demo/pb/common";
```
`order.proto` imports `common/types.proto` from a subdirectory:
```protobuf
import "common/types.proto";
```
Note that the two files have **different** `go_package` values, so they compile into separate Go packages.
## Generation Commands
First, initialize the output directory with a `go.mod`:
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
Then generate the code:
```bash
goctl rpc protoc order.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
Generated directory structure:
```
output/
├── etc
│ └── ordersvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ ├── getorderlogic.go
│ │ └── listorderslogic.go
│ ├── server
│ │ └── orderserviceserver.go
│ └── svc
│ └── servicecontext.go
├── orderservice
│ └── orderservice.go
├── ordersvc.go
└── pb
├── common
│ └── types.pb.go
├── order.pb.go
└── order_grpc.pb.go
```
## Key Points
- Two proto files have **different** `go_package` values, so they compile into separate Go packages (`pb/` and `pb/common/`).
- `order.proto` imports `common/types.proto` from a subdirectory.
- When imported protos have a different `go_package`, goctl automatically generates cross-package imports.
- The `-I .` flag tells protoc to search from the current directory, enabling it to find `common/types.proto`.
@@ -0,0 +1,15 @@
syntax = "proto3";
package common;
option go_package = "example.com/demo/pb/common";
message PageInfo {
int32 page = 1;
int32 size = 2;
}
message SortInfo {
string field = 1;
string order = 2;
}
@@ -0,0 +1,35 @@
syntax = "proto3";
package ordersvc;
option go_package = "example.com/demo/pb";
import "common/types.proto";
message OrderItem {
string id = 1;
string name = 2;
double price = 3;
}
message ListOrdersReq {
common.PageInfo page = 1;
common.SortInfo sort = 2;
}
message ListOrdersReply {
repeated OrderItem orders = 1;
}
message GetOrderReq {
string id = 1;
}
message GetOrderReply {
OrderItem order = 1;
}
service OrderService {
rpc ListOrders(ListOrdersReq) returns (ListOrdersReply);
rpc GetOrder(GetOrderReq) returns (GetOrderReply);
}
@@ -0,0 +1,74 @@
# 示例 04传递性导入
[English](README.md) | 中文 | [한국어](README-ko.md)
本示例演示 proto 的传递性导入,即 A 导入 BB 导入 C。
## Proto 定义
三个 proto 文件形成传递导入链,共享相同的 `go_package`
```protobuf
option go_package = "example.com/demo/pb";
```
- `base.proto` — C 层:定义基础类型(`BaseResp`)。
- `middleware.proto` — B 层:导入 `base.proto`,定义 `RequestMeta`
- `main.proto` — A 层:导入 `middleware.proto`,定义 `PingService`(入口文件)。
导入链:`main.proto``middleware.proto``base.proto`
## 生成命令
首先,在输出目录中初始化 `go.mod`
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
然后生成代码:
```bash
goctl rpc protoc main.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
生成的目录结构:
```
output/
├── etc
│ └── pingsvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ └── pinglogic.go
│ ├── server
│ │ └── pingserviceserver.go
│ └── svc
│ └── servicecontext.go
├── pb
│ ├── base.pb.go
│ ├── main.pb.go
│ ├── main_grpc.pb.go
│ └── middleware.pb.go
├── pingservice
│ └── pingservice.go
└── pingsvc.go
```
## 要点说明
- 三个 proto 文件(`main.proto``middleware.proto``base.proto`)形成传递导入链。
- goctl 自动递归解析所有传递导入。
- 三个文件共享相同的 `go_package = "example.com/demo/pb"`
- 只需指定入口 proto 文件goctl 和 protoc 会自动处理其余部分。
- 循环导入会被检测并报错(与 protoc 行为一致)。
@@ -0,0 +1,74 @@
# 예제 04: 전이 import
[English](README.md) | [中文](README-cn.md) | 한국어
이 예제는 A가 B를 import하고 B가 C를 import하는 전이 proto import를 보여줍니다.
## proto 정의
세 proto 파일이 전이 import 체인을 이루며, 모두 동일한 `go_package`를 공유합니다.
```protobuf
option go_package = "example.com/demo/pb";
```
- `base.proto` — 계층 C: 기본 타입(`BaseResp`)을 정의합니다.
- `middleware.proto` — 계층 B: `base.proto`를 import하고 `RequestMeta`를 정의합니다.
- `main.proto` — 계층 A: `middleware.proto`를 import하고 `PingService`(진입점)를 정의합니다.
import 체인: `main.proto``middleware.proto``base.proto`
## 생성 명령
먼저 출력 디렉터리에 `go.mod`를 초기화합니다.
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
그런 다음 코드를 생성합니다.
```bash
goctl rpc protoc main.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
생성되는 디렉터리 구조:
```
output/
├── etc
│ └── pingsvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ └── pinglogic.go
│ ├── server
│ │ └── pingserviceserver.go
│ └── svc
│ └── servicecontext.go
├── pb
│ ├── base.pb.go
│ ├── main.pb.go
│ ├── main_grpc.pb.go
│ └── middleware.pb.go
├── pingservice
│ └── pingservice.go
└── pingsvc.go
```
## 핵심 사항
- 세 proto 파일(`main.proto``middleware.proto``base.proto`)이 전이 import 체인을 이룹니다.
- goctl은 모든 전이 import를 자동으로 재귀 해결합니다.
- 세 파일 모두 동일한 `go_package = "example.com/demo/pb"`를 공유합니다.
- 진입 proto 파일만 지정하면 됩니다. 나머지는 goctl과 protoc가 처리합니다.
- 순환 import는 감지되며 오류가 발생합니다(protoc 동작과 동일).
@@ -0,0 +1,74 @@
# Example 04: Transitive Imports
English | [中文](README-cn.md) | [한국어](README-ko.md)
This example demonstrates transitive proto imports, where A imports B and B imports C.
## Proto Definition
Three proto files form a transitive import chain, all sharing the same `go_package`:
```protobuf
option go_package = "example.com/demo/pb";
```
- `base.proto` — Layer C: defines base types (`BaseResp`).
- `middleware.proto` — Layer B: imports `base.proto`, defines `RequestMeta`.
- `main.proto` — Layer A: imports `middleware.proto`, defines the `PingService` (entry point).
Import chain: `main.proto``middleware.proto``base.proto`
## Generation Commands
First, initialize the output directory with a `go.mod`:
```bash
mkdir -p output && cd output && go mod init example.com/demo && cd ..
```
Then generate the code:
```bash
goctl rpc protoc main.proto \
--go_out=output \
--go-grpc_out=output \
--zrpc_out=output \
--go_opt=module=example.com/demo \
--go-grpc_opt=module=example.com/demo \
--module=example.com/demo \
-I .
```
Generated directory structure:
```
output/
├── etc
│ └── pingsvc.yaml
├── go.mod
├── internal
│ ├── config
│ │ └── config.go
│ ├── logic
│ │ └── pinglogic.go
│ ├── server
│ │ └── pingserviceserver.go
│ └── svc
│ └── servicecontext.go
├── pb
│ ├── base.pb.go
│ ├── main.pb.go
│ ├── main_grpc.pb.go
│ └── middleware.pb.go
├── pingservice
│ └── pingservice.go
└── pingsvc.go
```
## Key Points
- Three proto files (`main.proto``middleware.proto``base.proto`) form a transitive import chain.
- goctl recursively resolves all transitive imports automatically.
- All three files share the same `go_package = "example.com/demo/pb"`.
- You only need to specify the entry proto file — goctl and protoc handle the rest.
- Circular imports are detected and will cause an error (same as protoc behavior).
@@ -0,0 +1,10 @@
syntax = "proto3";
package base;
option go_package = "example.com/demo/pb";
message BaseResp {
int32 code = 1;
string msg = 2;
}
@@ -0,0 +1,19 @@
syntax = "proto3";
package pingsvc;
option go_package = "example.com/demo/pb";
import "middleware.proto";
message PingReq {
middleware.RequestMeta meta = 1;
}
message PingReply {
string pong = 1;
}
service PingService {
rpc Ping(PingReq) returns (PingReply);
}
@@ -0,0 +1,12 @@
syntax = "proto3";
package middleware;
option go_package = "example.com/demo/pb";
import "base.proto";
message RequestMeta {
string trace_id = 1;
base.BaseResp base = 2;
}

Some files were not shown because too many files have changed in this diff Show More