mirror of
https://github.com/zeromicro/go-zero.git
synced 2026-09-09 04:56:25 +08:00
Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c803e1ec1 | |||
| 84c92d710b | |||
| df7801e920 | |||
| 88dd61e2d8 | |||
| 35734f9f2e | |||
| b6ca23c1d5 | |||
| 310f84cb06 | |||
| 4d8fa44e82 | |||
| 73d7ad752b | |||
| dae768a350 | |||
| 5f27129301 | |||
| c8ec158f44 | |||
| ba0c5096c5 | |||
| 1284f756c0 | |||
| 6928f70e60 | |||
| f1f866008f | |||
| 1788c943f6 | |||
| aa6283cb0c | |||
| 5866f8be91 | |||
| c585382bbf | |||
| 411181e701 | |||
| 691e9b3ff0 | |||
| 793dbc6337 | |||
| d4786490aa | |||
| 8f51779945 | |||
| 11f21098dd | |||
| ebe46e1ce0 | |||
| dc98c8938b | |||
| 91a4cdbaf4 | |||
| f7805d5e32 | |||
| 3c6607df7e | |||
| 9fd700ecb9 | |||
| 2d3a4d2ffe | |||
| b8ffc99bcc | |||
| b8057fe7dd | |||
| fc22f7501a | |||
| 45f113eea4 | |||
| 2bd5b8495b | |||
| 984e146c6c | |||
| 21376b408c | |||
| 8017edb671 | |||
| 925f8a2bcc | |||
| 36f2619756 | |||
| 84313f2e92 | |||
| 394ffcc19a | |||
| 565bcb3f21 | |||
| 6a6b81ef20 | |||
| 35a7ca9d98 | |||
| f910257ec9 | |||
| d318de1212 | |||
| 99515480cf | |||
| dbc71bb57b | |||
| b2e3aa1587 | |||
| 726cd0141a | |||
| c057ecd50a | |||
| 760e60c364 | |||
| 48ca7f03b5 | |||
| d4882c1da0 | |||
| 1aecffc306 | |||
| 18305200d2 | |||
| d9cb246301 | |||
| 4dd372914e | |||
| a306964403 | |||
| 0431bce2b1 | |||
| 6f46837c05 | |||
| ca8b50a7a5 | |||
| 0c79f5562e | |||
| eb33a8bb36 | |||
| 34f16a857e | |||
| 6f45879f1b | |||
| 7b5e7b1c26 | |||
| 4ad4fd43b7 | |||
| 3f3dca6f72 | |||
| 4ea0d9e9ea | |||
| 52d2bdadcd | |||
| 3738be1945 | |||
| 5b74b9ab7b | |||
| 4a67261b7b | |||
| 22bdae0787 | |||
| e8675d6a9a | |||
| e441c44975 | |||
| 3f91a79a2b |
@@ -35,11 +35,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v4
|
||||
uses: github/codeql-action/init@v4.37.9
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
uses: github/codeql-action/autobuild@v4.37.9
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -64,4 +64,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v4
|
||||
uses: github/codeql-action/analyze@v4.37.9
|
||||
|
||||
@@ -12,10 +12,10 @@ 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
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
@@ -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,10 +52,10 @@ 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
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
# make sure Go version compatible with go-zero
|
||||
go-version-file: go.mod
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@v11
|
||||
with:
|
||||
days-before-issue-stale: 365
|
||||
days-before-issue-close: 90
|
||||
|
||||
@@ -16,13 +16,13 @@ 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 }}
|
||||
goos: ${{ matrix.goos }}
|
||||
goarch: ${{ matrix.goarch }}
|
||||
goversion: "https://dl.google.com/go/go1.21.13.linux-amd64.tar.gz"
|
||||
goversion: "https://dl.google.com/go/go1.25.12.linux-amd64.tar.gz"
|
||||
project_path: "tools/goctl"
|
||||
binary_name: "goctl"
|
||||
extra_files: tools/goctl/readme.md tools/goctl/readme-cn.md
|
||||
|
||||
@@ -5,8 +5,8 @@ jobs:
|
||||
name: runner / staticcheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
|
||||
@@ -10,12 +10,12 @@ 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
|
||||
uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: '1.21'
|
||||
go-version: '1.25'
|
||||
|
||||
- name: Extract tag version
|
||||
id: get_version
|
||||
|
||||
+22
-7
@@ -2,11 +2,12 @@ package collection
|
||||
|
||||
import "sync"
|
||||
|
||||
const queueGrowThreshold = 256
|
||||
|
||||
// A Queue is a FIFO queue.
|
||||
type Queue struct {
|
||||
lock sync.Mutex
|
||||
elements []any
|
||||
size int
|
||||
head int
|
||||
tail int
|
||||
count int
|
||||
@@ -14,9 +15,12 @@ type Queue struct {
|
||||
|
||||
// NewQueue returns a Queue object.
|
||||
func NewQueue(size int) *Queue {
|
||||
if size < 1 {
|
||||
panic("size must be greater than 0")
|
||||
}
|
||||
|
||||
return &Queue{
|
||||
elements: make([]any, size),
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,12 +38,12 @@ func (q *Queue) Put(element any) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
if q.head == q.tail && q.count > 0 {
|
||||
nodes := make([]any, len(q.elements)+q.size)
|
||||
copy(nodes, q.elements[q.head:])
|
||||
copy(nodes[len(q.elements)-q.head:], q.elements[:q.head])
|
||||
if q.count == len(q.elements) {
|
||||
nodes := make([]any, nextQueueCapacity(len(q.elements)))
|
||||
n := copy(nodes, q.elements[q.head:])
|
||||
copy(nodes[n:], q.elements[:q.head])
|
||||
q.head = 0
|
||||
q.tail = len(q.elements)
|
||||
q.tail = q.count
|
||||
q.elements = nodes
|
||||
}
|
||||
|
||||
@@ -58,8 +62,19 @@ func (q *Queue) Take() (any, bool) {
|
||||
}
|
||||
|
||||
element := q.elements[q.head]
|
||||
q.elements[q.head] = nil
|
||||
q.head = (q.head + 1) % len(q.elements)
|
||||
q.count--
|
||||
|
||||
return element, true
|
||||
}
|
||||
|
||||
func nextQueueCapacity(capacity int) int {
|
||||
if capacity < queueGrowThreshold {
|
||||
return capacity << 1
|
||||
}
|
||||
|
||||
// Use a growth curve similar to Go slices: double small queues, then
|
||||
// transition smoothly toward 1.25x growth for larger queues.
|
||||
return capacity + ((capacity + 3*queueGrowThreshold) >> 2)
|
||||
}
|
||||
|
||||
+245
-78
@@ -6,96 +6,263 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFifo(t *testing.T) {
|
||||
elements := [][]byte{
|
||||
[]byte("hello"),
|
||||
[]byte("world"),
|
||||
[]byte("again"),
|
||||
}
|
||||
queue := NewQueue(8)
|
||||
for i := range elements {
|
||||
queue.Put(elements[i])
|
||||
func TestQueueOrder(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
initial []int
|
||||
takeBefore int
|
||||
additional []int
|
||||
wantCapacity int
|
||||
}{
|
||||
{
|
||||
name: "within initial capacity",
|
||||
size: 8,
|
||||
initial: []int{1, 2, 3},
|
||||
wantCapacity: 8,
|
||||
},
|
||||
{
|
||||
name: "grow from beginning",
|
||||
size: 2,
|
||||
initial: []int{1, 2, 3},
|
||||
wantCapacity: 4,
|
||||
},
|
||||
{
|
||||
name: "grow after wrapping",
|
||||
size: 4,
|
||||
initial: []int{1, 2, 3, 4},
|
||||
takeBefore: 1,
|
||||
additional: []int{5, 6},
|
||||
wantCapacity: 8,
|
||||
},
|
||||
{
|
||||
name: "grow repeatedly",
|
||||
size: 1,
|
||||
initial: sequence(20),
|
||||
wantCapacity: 32,
|
||||
},
|
||||
{
|
||||
name: "grow above threshold",
|
||||
size: queueGrowThreshold,
|
||||
initial: sequence(queueGrowThreshold + 1),
|
||||
wantCapacity: queueGrowThreshold * 2,
|
||||
},
|
||||
{
|
||||
name: "grow well above threshold",
|
||||
size: 1024,
|
||||
initial: sequence(1025),
|
||||
wantCapacity: 1472,
|
||||
},
|
||||
}
|
||||
|
||||
for _, element := range elements {
|
||||
body, ok := queue.Take()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, string(element), string(body.([]byte)))
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
queue := NewQueue(test.size)
|
||||
assert.True(t, queue.Empty())
|
||||
|
||||
for _, value := range test.initial {
|
||||
queue.Put(value)
|
||||
}
|
||||
for i := 0; i < test.takeBefore; i++ {
|
||||
value, ok := queue.Take()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, test.initial[i], value)
|
||||
}
|
||||
for _, value := range test.additional {
|
||||
queue.Put(value)
|
||||
}
|
||||
|
||||
want := append([]int(nil), test.initial[test.takeBefore:]...)
|
||||
want = append(want, test.additional...)
|
||||
for _, expected := range want {
|
||||
actual, ok := queue.Take()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
assert.Equal(t, test.wantCapacity, len(queue.elements))
|
||||
assert.True(t, queue.Empty())
|
||||
_, ok := queue.Take()
|
||||
assert.False(t, ok)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTakeTooMany(t *testing.T) {
|
||||
elements := [][]byte{
|
||||
[]byte("hello"),
|
||||
[]byte("world"),
|
||||
[]byte("again"),
|
||||
}
|
||||
queue := NewQueue(8)
|
||||
for i := range elements {
|
||||
queue.Put(elements[i])
|
||||
func TestQueueTakeClearsElement(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
operations string
|
||||
}{
|
||||
{
|
||||
name: "take from beginning",
|
||||
size: 2,
|
||||
operations: "ppt",
|
||||
},
|
||||
{
|
||||
name: "take after wrapping",
|
||||
size: 2,
|
||||
operations: "pptptt",
|
||||
},
|
||||
{
|
||||
name: "take after growing",
|
||||
size: 2,
|
||||
operations: "pppt",
|
||||
},
|
||||
}
|
||||
|
||||
for range elements {
|
||||
queue.Take()
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
queue := NewQueue(test.size)
|
||||
value := 0
|
||||
|
||||
assert.True(t, queue.Empty())
|
||||
_, ok := queue.Take()
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestPutMore(t *testing.T) {
|
||||
elements := [][]byte{
|
||||
[]byte("hello"),
|
||||
[]byte("world"),
|
||||
[]byte("again"),
|
||||
}
|
||||
queue := NewQueue(2)
|
||||
for i := range elements {
|
||||
queue.Put(elements[i])
|
||||
}
|
||||
|
||||
for _, element := range elements {
|
||||
body, ok := queue.Take()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, string(element), string(body.([]byte)))
|
||||
for _, operation := range test.operations {
|
||||
switch operation {
|
||||
case 'p':
|
||||
value++
|
||||
element := new(int)
|
||||
*element = value
|
||||
queue.Put(element)
|
||||
case 't':
|
||||
index := queue.head
|
||||
_, ok := queue.Take()
|
||||
assert.True(t, ok)
|
||||
assert.Nil(t, queue.elements[index])
|
||||
default:
|
||||
t.Fatalf("unknown operation: %q", operation)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPutMoreWithHeaderNotZero(t *testing.T) {
|
||||
elements := [][]byte{
|
||||
[]byte("hello"),
|
||||
[]byte("world"),
|
||||
[]byte("again"),
|
||||
}
|
||||
queue := NewQueue(4)
|
||||
for i := range elements {
|
||||
queue.Put(elements[i])
|
||||
func TestNewQueueWithInvalidSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
}{
|
||||
{
|
||||
name: "zero",
|
||||
},
|
||||
{
|
||||
name: "negative",
|
||||
size: -1,
|
||||
},
|
||||
}
|
||||
|
||||
// take 1
|
||||
body, ok := queue.Take()
|
||||
assert.True(t, ok)
|
||||
element, ok := body.([]byte)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, element, []byte("hello"))
|
||||
|
||||
// put more
|
||||
queue.Put([]byte("b4"))
|
||||
queue.Put([]byte("b5")) // will store in elements[0]
|
||||
queue.Put([]byte("b6")) // cause expansion
|
||||
|
||||
results := [][]byte{
|
||||
[]byte("world"),
|
||||
[]byte("again"),
|
||||
[]byte("b4"),
|
||||
[]byte("b5"),
|
||||
[]byte("b6"),
|
||||
}
|
||||
|
||||
for _, element := range results {
|
||||
body, ok := queue.Take()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, string(element), string(body.([]byte)))
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
assert.Panics(t, func() {
|
||||
NewQueue(test.size)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQueueGrowth(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
count int
|
||||
}{
|
||||
{
|
||||
name: "initial_1_count_256",
|
||||
size: 1,
|
||||
count: 256,
|
||||
},
|
||||
{
|
||||
name: "initial_1_count_4096",
|
||||
size: 1,
|
||||
count: 4096,
|
||||
},
|
||||
{
|
||||
name: "initial_1_count_65536",
|
||||
size: 1,
|
||||
count: 65536,
|
||||
},
|
||||
{
|
||||
name: "initial_8_count_4096",
|
||||
size: 8,
|
||||
count: 4096,
|
||||
},
|
||||
{
|
||||
name: "initial_256_count_4096",
|
||||
size: 256,
|
||||
count: 4096,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
b.Run(test.name, func(b *testing.B) {
|
||||
elements := make([]any, test.count)
|
||||
for i := range elements {
|
||||
elements[i] = i
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
queue := NewQueue(test.size)
|
||||
for _, element := range elements {
|
||||
queue.Put(element)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkQueueWrappedGrowth(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
size int
|
||||
}{
|
||||
{
|
||||
name: "capacity_8",
|
||||
size: 8,
|
||||
},
|
||||
{
|
||||
name: "capacity_256",
|
||||
size: 256,
|
||||
},
|
||||
{
|
||||
name: "capacity_4096",
|
||||
size: 4096,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
b.Run(test.name, func(b *testing.B) {
|
||||
elements := make([]any, test.size)
|
||||
for i := range elements {
|
||||
elements[i] = i
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
queue := NewQueue(test.size)
|
||||
for _, element := range elements {
|
||||
queue.Put(element)
|
||||
}
|
||||
for j := 0; j < test.size/2; j++ {
|
||||
queue.Take()
|
||||
}
|
||||
for j := 0; j < test.size/2; j++ {
|
||||
queue.Put(elements[j])
|
||||
}
|
||||
|
||||
// The queue is full and wrapped. One more Put triggers growth
|
||||
// and copies both sides of the ring into FIFO order.
|
||||
queue.Put(elements[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func sequence(size int) []int {
|
||||
values := make([]int, size)
|
||||
for i := range values {
|
||||
values[i] = i
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package collection
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"maps"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
copyThreshold = 1000
|
||||
@@ -39,18 +42,14 @@ func (m *SafeMap) Del(key any) {
|
||||
m.deletionNew++
|
||||
}
|
||||
if m.deletionOld >= maxDeletion && len(m.dirtyOld) < copyThreshold {
|
||||
for k, v := range m.dirtyOld {
|
||||
m.dirtyNew[k] = v
|
||||
}
|
||||
maps.Copy(m.dirtyNew, m.dirtyOld)
|
||||
m.dirtyOld = m.dirtyNew
|
||||
m.deletionOld = m.deletionNew
|
||||
m.dirtyNew = make(map[any]any)
|
||||
m.deletionNew = 0
|
||||
}
|
||||
if m.deletionNew >= maxDeletion && len(m.dirtyNew) < copyThreshold {
|
||||
for k, v := range m.dirtyNew {
|
||||
m.dirtyOld[k] = v
|
||||
}
|
||||
maps.Copy(m.dirtyOld, m.dirtyNew)
|
||||
m.dirtyNew = make(map[any]any)
|
||||
m.deletionNew = 0
|
||||
}
|
||||
|
||||
@@ -170,6 +170,12 @@ func (tw *TimingWheel) drainAll(fn func(key, value any)) {
|
||||
task := e.Value.(*timingEntry)
|
||||
next := e.Next()
|
||||
slot.Remove(e)
|
||||
if val, ok := tw.timers.Get(task.key); ok {
|
||||
timer := val.(*positionEntry)
|
||||
if timer.item == task {
|
||||
tw.timers.Del(task.key)
|
||||
}
|
||||
}
|
||||
e = next
|
||||
if !task.removed {
|
||||
runner.Schedule(func() {
|
||||
|
||||
@@ -59,6 +59,35 @@ func TestTimingWheel_Drain(t *testing.T) {
|
||||
assert.Equal(t, ErrClosed, tw.Drain(func(key, value any) {}))
|
||||
}
|
||||
|
||||
func TestTimingWheel_SetTimerAfterDrain(t *testing.T) {
|
||||
ticker := timex.NewFakeTicker()
|
||||
var count int32
|
||||
tw, _ := NewTimingWheelWithTicker(testStep, 10, func(key, value any) {
|
||||
assert.Equal(t, "foo", key)
|
||||
assert.Equal(t, 2, value)
|
||||
atomic.AddInt32(&count, 1)
|
||||
ticker.Done()
|
||||
}, ticker)
|
||||
defer tw.Stop()
|
||||
|
||||
tw.SetTimer("foo", 1, testStep*4)
|
||||
drained := make(chan lang.PlaceholderType)
|
||||
tw.Drain(func(key, value any) {
|
||||
assert.Equal(t, "foo", key)
|
||||
assert.Equal(t, 1, value)
|
||||
close(drained)
|
||||
})
|
||||
<-drained
|
||||
|
||||
tw.SetTimer("foo", 2, testStep*4)
|
||||
for i := 0; i < 4; i++ {
|
||||
ticker.Tick()
|
||||
}
|
||||
|
||||
assert.Nil(t, ticker.Wait(waitTime))
|
||||
assert.Equal(t, int32(1), atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
func TestTimingWheel_SetTimerSoon(t *testing.T) {
|
||||
run := syncx.NewAtomicBool()
|
||||
ticker := timex.NewFakeTicker()
|
||||
|
||||
@@ -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()
|
||||
@@ -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),
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
+2
-6
@@ -477,8 +477,6 @@ func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
pool := make(chan lang.PlaceholderType, option.workers)
|
||||
|
||||
for item := range s.source {
|
||||
// important, used in another goroutine
|
||||
val := item
|
||||
pool <- lang.Placeholder
|
||||
wg.Add(1)
|
||||
|
||||
@@ -489,7 +487,7 @@ func (s Stream) walkLimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
<-pool
|
||||
}()
|
||||
|
||||
fn(val, pipe)
|
||||
fn(item, pipe)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -507,13 +505,11 @@ func (s Stream) walkUnlimited(fn WalkFunc, option *rxOptions) Stream {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for item := range s.source {
|
||||
// important, used in another goroutine
|
||||
val := item
|
||||
wg.Add(1)
|
||||
// better to safely run caller defined method
|
||||
threading.GoSafe(func() {
|
||||
defer wg.Done()
|
||||
fn(val, pipe)
|
||||
fn(item, pipe)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# logx
|
||||
|
||||
[English](readme.md) | 简体中文
|
||||
[English](readme.md) | 简体中文 | [한국어](readme-ko.md)
|
||||
|
||||
## logx 配置
|
||||
|
||||
|
||||
@@ -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
@@ -2,7 +2,7 @@
|
||||
|
||||
# logx
|
||||
|
||||
English | [简体中文](readme-cn.md)
|
||||
English | [简体中文](readme-cn.md) | [한국어](readme-ko.md)
|
||||
|
||||
## logx configurations
|
||||
|
||||
|
||||
@@ -931,6 +931,113 @@ func TestUnmarshalJsonArray(t *testing.T) {
|
||||
assert.Equal(t, 18, v[0].Age)
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesPointerSliceUint64(t *testing.T) {
|
||||
t.Run("with values", func(t *testing.T) {
|
||||
var c struct {
|
||||
IDs *[]uint64 `json:"ids,optional"`
|
||||
}
|
||||
content := []byte(`{"ids":[9000,9001]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.NotNil(t, c.IDs)
|
||||
assert.Equal(t, []uint64{9000, 9001}, *c.IDs)
|
||||
})
|
||||
|
||||
t.Run("omitted", func(t *testing.T) {
|
||||
var c struct {
|
||||
IDs *[]uint64 `json:"ids,optional"`
|
||||
}
|
||||
content := []byte(`{}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Nil(t, c.IDs)
|
||||
})
|
||||
|
||||
t.Run("null", func(t *testing.T) {
|
||||
var c struct {
|
||||
IDs *[]uint64 `json:"ids,optional"`
|
||||
}
|
||||
content := []byte(`{"ids":null}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Nil(t, c.IDs)
|
||||
})
|
||||
|
||||
t.Run("empty array", func(t *testing.T) {
|
||||
var c struct {
|
||||
IDs *[]uint64 `json:"ids,optional"`
|
||||
}
|
||||
content := []byte(`{"ids":[]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.NotNil(t, c.IDs)
|
||||
assert.Equal(t, []uint64{}, *c.IDs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesPointerSliceOtherTypes(t *testing.T) {
|
||||
t.Run("pointer to []string", func(t *testing.T) {
|
||||
var c struct {
|
||||
Names *[]string `json:"names,optional"`
|
||||
}
|
||||
content := []byte(`{"names":["a","b"]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.NotNil(t, c.Names)
|
||||
assert.Equal(t, []string{"a", "b"}, *c.Names)
|
||||
})
|
||||
|
||||
t.Run("pointer to []int", func(t *testing.T) {
|
||||
var c struct {
|
||||
Values *[]int `json:"values,optional"`
|
||||
}
|
||||
content := []byte(`{"values":[1,2,3]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.NotNil(t, c.Values)
|
||||
assert.Equal(t, []int{1, 2, 3}, *c.Values)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesPointerSliceStruct(t *testing.T) {
|
||||
type Item struct {
|
||||
Name string `json:"name"`
|
||||
Age int `json:"age"`
|
||||
}
|
||||
|
||||
t.Run("with values", func(t *testing.T) {
|
||||
var c struct {
|
||||
Items *[]Item `json:"items,optional"`
|
||||
}
|
||||
content := []byte(`{"items":[{"name":"alice","age":30},{"name":"bob","age":25}]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.NotNil(t, c.Items)
|
||||
assert.Equal(t, []Item{{Name: "alice", Age: 30}, {Name: "bob", Age: 25}}, *c.Items)
|
||||
})
|
||||
|
||||
t.Run("omitted", func(t *testing.T) {
|
||||
var c struct {
|
||||
Items *[]Item `json:"items,optional"`
|
||||
}
|
||||
content := []byte(`{}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.Nil(t, c.Items)
|
||||
})
|
||||
|
||||
t.Run("empty array", func(t *testing.T) {
|
||||
var c struct {
|
||||
Items *[]Item `json:"items,optional"`
|
||||
}
|
||||
content := []byte(`{"items":[]}`)
|
||||
|
||||
assert.Nil(t, UnmarshalJsonBytes(content, &c))
|
||||
assert.NotNil(t, c.Items)
|
||||
assert.Equal(t, []Item{}, *c.Items)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalJsonBytesError(t *testing.T) {
|
||||
var v []struct {
|
||||
Name string `json:"name"`
|
||||
|
||||
+10
-10
@@ -142,11 +142,11 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value,
|
||||
return nil
|
||||
}
|
||||
|
||||
baseType := fieldType.Elem()
|
||||
baseType := Deref(fieldType).Elem()
|
||||
dereffedBaseType := Deref(baseType)
|
||||
dereffedBaseKind := dereffedBaseType.Kind()
|
||||
if refValue.Len() == 0 {
|
||||
value.Set(reflect.MakeSlice(reflect.SliceOf(baseType), 0, 0))
|
||||
SetValue(fieldType, value, reflect.MakeSlice(reflect.SliceOf(baseType), 0, 0))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ func (u *Unmarshaler) fillSlice(fieldType reflect.Type, value reflect.Value,
|
||||
}
|
||||
|
||||
if valid {
|
||||
value.Set(conv)
|
||||
SetValue(fieldType, value, conv)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -201,7 +201,7 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
||||
return errUnsupportedType
|
||||
}
|
||||
|
||||
baseFieldType := fieldType.Elem()
|
||||
baseFieldType := Deref(fieldType).Elem()
|
||||
baseFieldKind := baseFieldType.Kind()
|
||||
conv := reflect.MakeSlice(reflect.SliceOf(baseFieldType), len(slice), cap(slice))
|
||||
|
||||
@@ -211,7 +211,7 @@ func (u *Unmarshaler) fillSliceFromString(fieldType reflect.Type, value reflect.
|
||||
}
|
||||
}
|
||||
|
||||
value.Set(conv)
|
||||
SetValue(fieldType, value, conv)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ func (u *Unmarshaler) fillUnmarshalerStruct(fieldType reflect.Type,
|
||||
target := reflect.New(baseType)
|
||||
switch u.key {
|
||||
case jsonTagKey:
|
||||
unmarshaler, ok := target.Interface().(json.Unmarshaler)
|
||||
unmarshaler, ok := reflect.TypeAssert[json.Unmarshaler](target)
|
||||
if !ok {
|
||||
return errUnsupportedType
|
||||
}
|
||||
@@ -424,7 +424,7 @@ func (u *Unmarshaler) generateMap(keyType, elemType reflect.Type, mapValue any,
|
||||
func (u *Unmarshaler) implementsUnmarshaler(t reflect.Type) bool {
|
||||
switch u.key {
|
||||
case jsonTagKey:
|
||||
return t.Implements(reflect.TypeOf((*json.Unmarshaler)(nil)).Elem())
|
||||
return t.Implements(reflect.TypeFor[json.Unmarshaler]())
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -740,12 +740,12 @@ func (u *Unmarshaler) processFieldTextUnmarshaler(fieldType reflect.Type, value
|
||||
if value.Elem().Kind() == reflect.Ptr {
|
||||
target := reflect.New(Deref(fieldType))
|
||||
SetValue(fieldType.Elem(), value, target)
|
||||
tval, ok = target.Interface().(encoding.TextUnmarshaler)
|
||||
tval, ok = reflect.TypeAssert[encoding.TextUnmarshaler](target)
|
||||
} else {
|
||||
tval, ok = value.Interface().(encoding.TextUnmarshaler)
|
||||
tval, ok = reflect.TypeAssert[encoding.TextUnmarshaler](value)
|
||||
}
|
||||
} else {
|
||||
tval, ok = value.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
tval, ok = reflect.TypeAssert[encoding.TextUnmarshaler](value.Addr())
|
||||
}
|
||||
if ok {
|
||||
switch mv := mapValue.(type) {
|
||||
|
||||
@@ -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 并发工具类。
|
||||
|
||||
## 设计思路
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -185,8 +185,7 @@ func currentCgroupV1() (cgroup, error) {
|
||||
|
||||
// https://man7.org/linux/man-pages/man7/cgroups.7.html
|
||||
// comma-separated list of controllers for cgroup version 1
|
||||
fields := strings.Split(subsys, ",")
|
||||
for _, val := range fields {
|
||||
for val := range strings.SplitSeq(subsys, ",") {
|
||||
cgroups[val] = path.Join(cgroupDir, val)
|
||||
}
|
||||
}
|
||||
@@ -260,8 +259,7 @@ func parseUints(val string) ([]uint64, error) {
|
||||
|
||||
var sets []uint64
|
||||
ints := make(map[uint64]lang.PlaceholderType)
|
||||
cols := strings.Split(val, ",")
|
||||
for _, r := range cols {
|
||||
for r := range strings.SplitSeq(val, ",") {
|
||||
if strings.Contains(r, "-") {
|
||||
fields := strings.SplitN(r, "-", 2)
|
||||
minimum, err := parseUint(fields[0])
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
var ignoreCmds = map[string]lang.PlaceholderType{
|
||||
"blpop": {},
|
||||
"hello": {},
|
||||
}
|
||||
|
||||
type breakerHook struct {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
red "github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
)
|
||||
@@ -75,6 +76,45 @@ func TestBreakerHook_ProcessHook(t *testing.T) {
|
||||
}
|
||||
assert.Equal(t, someError.Error(), err.Error())
|
||||
})
|
||||
|
||||
t.Run("breakerHook_ignoreHello", func(t *testing.T) {
|
||||
// hello is issued on connection init and is in ignoreCmds, so repeated
|
||||
// failures must never trip the breaker into ErrServiceUnavailable.
|
||||
h := breakerHook{brk: breaker.NewBreaker()}
|
||||
someError := errors.New("ERR some error")
|
||||
process := h.ProcessHook(func(_ context.Context, _ red.Cmder) error {
|
||||
return someError
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
var err error
|
||||
for i := 0; i < 1000; i++ {
|
||||
err = process(ctx, red.NewCmd(ctx, "hello", 3))
|
||||
if err != nil && err.Error() != someError.Error() {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Equal(t, someError.Error(), err.Error())
|
||||
})
|
||||
|
||||
t.Run("breakerHook_notIgnored", func(t *testing.T) {
|
||||
// a regular command is not ignored, so repeated failures open the breaker.
|
||||
h := breakerHook{brk: breaker.NewBreaker()}
|
||||
someError := errors.New("ERR some error")
|
||||
process := h.ProcessHook(func(_ context.Context, _ red.Cmder) error {
|
||||
return someError
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
var err error
|
||||
for i := 0; i < 1000; i++ {
|
||||
err = process(ctx, red.NewCmd(ctx, "get", "key"))
|
||||
if err != nil && err.Error() != someError.Error() {
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Equal(t, breaker.ErrServiceUnavailable, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestBreakerHook_ProcessPipelineHook(t *testing.T) {
|
||||
|
||||
@@ -23,6 +23,30 @@ type (
|
||||
Pass string `json:",optional"`
|
||||
Tls bool `json:",optional"`
|
||||
NonBlock bool `json:",default=true"`
|
||||
// DisableIdentity is used to disable CLIENT SETINFO command on connect.
|
||||
//
|
||||
// Some redis versions/proxies do not support CLIENT SETINFO and return an
|
||||
// error on connect; since that command runs through the breaker hook it can
|
||||
// trip the breaker. Set this to true to skip it on such servers. Together
|
||||
// with the default MaintNotifications=disabled (and the always-ignored
|
||||
// HELLO command), this keeps the connect-time commands from tripping the
|
||||
// breaker on incompatible servers, without forcing RESP2.
|
||||
//
|
||||
// default: false
|
||||
DisableIdentity bool `json:",default=false"`
|
||||
// Protocol 2 or 3. Use the version to negotiate RESP version with redis-server.
|
||||
//
|
||||
// default: 3.
|
||||
Protocol int `json:",default=3"`
|
||||
// MaintNotifications controls the CLIENT MAINT_NOTIFICATIONS handshake mode
|
||||
// (go-redis MaintNotificationsConfig.Mode):
|
||||
// - disabled: never send the command (avoids tripping the breaker on servers
|
||||
// that don't support it; keeps RESP3 intact)
|
||||
// - auto: try, silently fall back on error (go-redis default)
|
||||
// - enabled: force, fail the connection on error
|
||||
//
|
||||
// default: disabled
|
||||
MaintNotifications string `json:",default=disabled,options=disabled|enabled|auto"`
|
||||
// PingTimeout is the timeout for ping redis.
|
||||
PingTimeout time.Duration `json:",default=1s"`
|
||||
}
|
||||
|
||||
+83
-21
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
red "github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis/v9/maintnotifications"
|
||||
"github.com/zeromicro/go-zero/core/breaker"
|
||||
"github.com/zeromicro/go-zero/core/errorx"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
@@ -53,13 +54,16 @@ type (
|
||||
|
||||
// Redis defines a redis node/cluster. It is thread-safe.
|
||||
Redis struct {
|
||||
Addr string
|
||||
Type string
|
||||
User string
|
||||
Pass string
|
||||
tls bool
|
||||
brk breaker.Breaker
|
||||
hooks []red.Hook
|
||||
Addr string
|
||||
Type string
|
||||
User string
|
||||
Pass string
|
||||
protocol int
|
||||
identity bool
|
||||
maintNotifications maintnotifications.Mode
|
||||
tls bool
|
||||
brk breaker.Breaker
|
||||
hooks []red.Hook
|
||||
}
|
||||
|
||||
// RedisNode interface represents a redis node.
|
||||
@@ -136,6 +140,15 @@ func NewRedis(conf RedisConf, opts ...Option) (*Redis, error) {
|
||||
if conf.Tls {
|
||||
opts = append([]Option{WithTLS()}, opts...)
|
||||
}
|
||||
if conf.Protocol > 0 {
|
||||
opts = append([]Option{WithProtocol(conf.Protocol)}, opts...)
|
||||
}
|
||||
if conf.DisableIdentity {
|
||||
opts = append([]Option{WithIdentity()}, opts...)
|
||||
}
|
||||
if len(conf.MaintNotifications) > 0 {
|
||||
opts = append([]Option{WithMaintNotifications(conf.MaintNotifications)}, opts...)
|
||||
}
|
||||
|
||||
rds := newRedis(conf.Host, opts...)
|
||||
if !conf.NonBlock {
|
||||
@@ -147,20 +160,6 @@ func NewRedis(conf RedisConf, opts ...Option) (*Redis, error) {
|
||||
return rds, nil
|
||||
}
|
||||
|
||||
func newRedis(addr string, opts ...Option) *Redis {
|
||||
r := &Redis{
|
||||
Addr: addr,
|
||||
Type: NodeType,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// NewScript returns a new Script instance.
|
||||
func NewScript(script string) *Script {
|
||||
return red.NewScript(script)
|
||||
@@ -1868,6 +1867,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)
|
||||
@@ -2671,6 +2685,18 @@ func (s *Redis) checkConnection(pingTimeout time.Duration) error {
|
||||
return conn.Ping(ctx).Err()
|
||||
}
|
||||
|
||||
// maintNotificationsConfig builds the go-redis maintenance notifications config
|
||||
// from the configured mode, defaulting to disabled when unset so that the
|
||||
// CLIENT MAINT_NOTIFICATIONS command is not issued on connect.
|
||||
func (r *Redis) maintNotificationsConfig() *maintnotifications.Config {
|
||||
mode := r.maintNotifications
|
||||
if len(mode) == 0 {
|
||||
mode = maintnotifications.ModeDisabled
|
||||
}
|
||||
|
||||
return &maintnotifications.Config{Mode: mode}
|
||||
}
|
||||
|
||||
// Cluster customizes the given Redis as a cluster.
|
||||
func Cluster() Option {
|
||||
return func(r *Redis) {
|
||||
@@ -2711,6 +2737,28 @@ func WithUser(user string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithProtocol customizes the given Redis with protocol.
|
||||
func WithProtocol(protocol int) Option {
|
||||
return func(r *Redis) {
|
||||
r.protocol = protocol
|
||||
}
|
||||
}
|
||||
|
||||
// WithIdentity customizes the given Redis with Identity enabled.
|
||||
func WithIdentity() Option {
|
||||
return func(r *Redis) {
|
||||
r.identity = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithMaintNotifications customizes the given Redis with the maintenance
|
||||
// notifications mode (disabled, enabled or auto).
|
||||
func WithMaintNotifications(mode string) Option {
|
||||
return func(r *Redis) {
|
||||
r.maintNotifications = maintnotifications.Mode(mode)
|
||||
}
|
||||
}
|
||||
|
||||
func acceptable(err error) bool {
|
||||
return err == nil || errorx.In(err, red.Nil, context.Canceled)
|
||||
}
|
||||
@@ -2726,6 +2774,20 @@ func getRedis(r *Redis) (RedisNode, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func newRedis(addr string, opts ...Option) *Redis {
|
||||
r := &Redis{
|
||||
Addr: addr,
|
||||
Type: NodeType,
|
||||
brk: breaker.NewBreaker(),
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(r)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func toPairs(vals []red.Z) []Pair {
|
||||
pairs := make([]Pair, len(vals))
|
||||
for i, val := range vals {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
red "github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis/v9/maintnotifications"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
"github.com/zeromicro/go-zero/core/stringx"
|
||||
@@ -150,6 +151,82 @@ func TestNewRedis(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClientWithProtocolAndIdentity(t *testing.T) {
|
||||
r := miniredis.RunT(t)
|
||||
defer r.Close()
|
||||
c, err := getClient(&Redis{
|
||||
Addr: r.Addr(),
|
||||
Type: NodeType,
|
||||
protocol: 2,
|
||||
identity: true,
|
||||
})
|
||||
if assert.NoError(t, err) {
|
||||
assert.NotNil(t, c)
|
||||
assert.Equal(t, 2, c.Options().Protocol)
|
||||
assert.True(t, c.Options().DisableIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRedis_ProtocolAndIdentity(t *testing.T) {
|
||||
logx.Disable()
|
||||
|
||||
s := miniredis.RunT(t)
|
||||
rds, err := NewRedis(RedisConf{
|
||||
Host: s.Addr(),
|
||||
Type: NodeType,
|
||||
Protocol: 2,
|
||||
DisableIdentity: true,
|
||||
})
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, 2, rds.protocol)
|
||||
assert.True(t, rds.identity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClientWithMaintNotifications(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mode maintnotifications.Mode
|
||||
want maintnotifications.Mode
|
||||
}{
|
||||
{name: "unset falls back to disabled", mode: "", want: maintnotifications.ModeDisabled},
|
||||
{name: "disabled", mode: maintnotifications.ModeDisabled, want: maintnotifications.ModeDisabled},
|
||||
{name: "enabled", mode: maintnotifications.ModeEnabled, want: maintnotifications.ModeEnabled},
|
||||
{name: "auto", mode: maintnotifications.ModeAuto, want: maintnotifications.ModeAuto},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := miniredis.RunT(t)
|
||||
defer r.Close()
|
||||
c, err := getClient(&Redis{
|
||||
Addr: r.Addr(),
|
||||
Type: NodeType,
|
||||
maintNotifications: test.mode,
|
||||
})
|
||||
if assert.NoError(t, err) {
|
||||
assert.NotNil(t, c)
|
||||
assert.NotNil(t, c.Options().MaintNotificationsConfig)
|
||||
assert.Equal(t, test.want, c.Options().MaintNotificationsConfig.Mode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRedis_MaintNotifications(t *testing.T) {
|
||||
logx.Disable()
|
||||
|
||||
s := miniredis.RunT(t)
|
||||
rds, err := NewRedis(RedisConf{
|
||||
Host: s.Addr(),
|
||||
Type: NodeType,
|
||||
MaintNotifications: string(maintnotifications.ModeAuto),
|
||||
})
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, maintnotifications.ModeAuto, rds.maintNotifications)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRedis_NonBlock(t *testing.T) {
|
||||
logx.Disable()
|
||||
|
||||
@@ -2294,6 +2371,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")
|
||||
|
||||
@@ -50,25 +50,31 @@ func CreateBlockingNode(r *Redis) (ClosableNode, error) {
|
||||
switch r.Type {
|
||||
case NodeType:
|
||||
client := red.NewClient(&red.Options{
|
||||
Addr: r.Addr,
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
DB: defaultDatabase,
|
||||
MaxRetries: maxRetries,
|
||||
PoolSize: 1,
|
||||
MinIdleConns: 1,
|
||||
ReadTimeout: timeout,
|
||||
Addr: r.Addr,
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
DB: defaultDatabase,
|
||||
MaxRetries: maxRetries,
|
||||
PoolSize: 1,
|
||||
MinIdleConns: 1,
|
||||
ReadTimeout: timeout,
|
||||
Protocol: r.protocol,
|
||||
DisableIdentity: r.identity,
|
||||
MaintNotificationsConfig: r.maintNotificationsConfig(),
|
||||
})
|
||||
return &clientBridge{client}, nil
|
||||
case ClusterType:
|
||||
client := red.NewClusterClient(&red.ClusterOptions{
|
||||
Addrs: splitClusterAddrs(r.Addr),
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
MaxRetries: maxRetries,
|
||||
PoolSize: 1,
|
||||
MinIdleConns: 1,
|
||||
ReadTimeout: timeout,
|
||||
Addrs: splitClusterAddrs(r.Addr),
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
MaxRetries: maxRetries,
|
||||
PoolSize: 1,
|
||||
MinIdleConns: 1,
|
||||
ReadTimeout: timeout,
|
||||
Protocol: r.protocol,
|
||||
DisableIdentity: r.identity,
|
||||
MaintNotificationsConfig: r.maintNotificationsConfig(),
|
||||
})
|
||||
return &clusterBridge{client}, nil
|
||||
default:
|
||||
|
||||
@@ -43,4 +43,32 @@ func TestBlockingNode(t *testing.T) {
|
||||
_, err = CreateBlockingNode(New(r.Addr(), badType()))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("test blocking node with protocol and identity", func(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
node, err := CreateBlockingNode(New(r.Addr(), WithProtocol(2), WithIdentity()))
|
||||
assert.NoError(t, err)
|
||||
bridge, ok := node.(*clientBridge)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 2, bridge.Options().Protocol)
|
||||
assert.True(t, bridge.Options().DisableIdentity)
|
||||
node.Close()
|
||||
})
|
||||
|
||||
t.Run("test blocking node with cluster, protocol and identity", func(t *testing.T) {
|
||||
r, err := miniredis.Run()
|
||||
assert.NoError(t, err)
|
||||
defer r.Close()
|
||||
|
||||
node, err := CreateBlockingNode(New(r.Addr(), Cluster(), WithProtocol(2), WithIdentity()))
|
||||
assert.NoError(t, err)
|
||||
bridge, ok := node.(*clusterBridge)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, 2, bridge.Options().Protocol)
|
||||
assert.True(t, bridge.Options().DisableIdentity)
|
||||
node.Close()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -30,13 +30,16 @@ func getClient(r *Redis) (*red.Client, error) {
|
||||
}
|
||||
}
|
||||
store := red.NewClient(&red.Options{
|
||||
Addr: r.Addr,
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
DB: defaultDatabase,
|
||||
MaxRetries: maxRetries,
|
||||
MinIdleConns: idleConns,
|
||||
TLSConfig: tlsConfig,
|
||||
Addr: r.Addr,
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
DB: defaultDatabase,
|
||||
MaxRetries: maxRetries,
|
||||
MinIdleConns: idleConns,
|
||||
TLSConfig: tlsConfig,
|
||||
Protocol: r.protocol,
|
||||
DisableIdentity: r.identity,
|
||||
MaintNotificationsConfig: r.maintNotificationsConfig(),
|
||||
})
|
||||
|
||||
hooks := append([]red.Hook{defaultDurationHook, breakerHook{
|
||||
|
||||
@@ -27,12 +27,15 @@ func getCluster(r *Redis) (*red.ClusterClient, error) {
|
||||
}
|
||||
}
|
||||
store := red.NewClusterClient(&red.ClusterOptions{
|
||||
Addrs: splitClusterAddrs(r.Addr),
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
MaxRetries: maxRetries,
|
||||
MinIdleConns: idleConns,
|
||||
TLSConfig: tlsConfig,
|
||||
Addrs: splitClusterAddrs(r.Addr),
|
||||
Username: r.User,
|
||||
Password: r.Pass,
|
||||
MaxRetries: maxRetries,
|
||||
MinIdleConns: idleConns,
|
||||
TLSConfig: tlsConfig,
|
||||
Protocol: r.protocol,
|
||||
DisableIdentity: r.identity,
|
||||
MaintNotificationsConfig: r.maintNotificationsConfig(),
|
||||
})
|
||||
|
||||
hooks := append([]red.Hook{defaultDurationHook, breakerHook{
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
red "github.com/redis/go-redis/v9"
|
||||
"github.com/redis/go-redis/v9/maintnotifications"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -57,3 +58,50 @@ func TestGetCluster(t *testing.T) {
|
||||
assert.NotNil(t, c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClusterWithProtocolAndIdentity(t *testing.T) {
|
||||
r := miniredis.RunT(t)
|
||||
defer r.Close()
|
||||
c, err := getCluster(&Redis{
|
||||
Addr: r.Addr(),
|
||||
Type: ClusterType,
|
||||
protocol: 2,
|
||||
identity: true,
|
||||
hooks: []red.Hook{defaultDurationHook},
|
||||
})
|
||||
if assert.NoError(t, err) {
|
||||
assert.NotNil(t, c)
|
||||
assert.Equal(t, 2, c.Options().Protocol)
|
||||
assert.True(t, c.Options().DisableIdentity)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClusterWithMaintNotifications(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mode maintnotifications.Mode
|
||||
want maintnotifications.Mode
|
||||
}{
|
||||
{name: "unset falls back to disabled", mode: "", want: maintnotifications.ModeDisabled},
|
||||
{name: "disabled", mode: maintnotifications.ModeDisabled, want: maintnotifications.ModeDisabled},
|
||||
{name: "auto", mode: maintnotifications.ModeAuto, want: maintnotifications.ModeAuto},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := miniredis.RunT(t)
|
||||
defer r.Close()
|
||||
c, err := getCluster(&Redis{
|
||||
Addr: r.Addr(),
|
||||
Type: ClusterType,
|
||||
maintNotifications: test.mode,
|
||||
hooks: []red.Hook{defaultDurationHook},
|
||||
})
|
||||
if assert.NoError(t, err) {
|
||||
assert.NotNil(t, c)
|
||||
assert.NotNil(t, c.Options().MaintNotificationsConfig)
|
||||
assert.Equal(t, test.want, c.Options().MaintNotificationsConfig.Mode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ func Filter(s string, remove func(r rune) bool) string {
|
||||
|
||||
// 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 {
|
||||
@@ -128,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
|
||||
}
|
||||
|
||||
|
||||
@@ -190,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 {
|
||||
@@ -344,6 +356,13 @@ func TestSubstr(t *testing.T) {
|
||||
err: ErrInvalidStopPosition,
|
||||
expect: "",
|
||||
},
|
||||
{
|
||||
input: "hello",
|
||||
start: 3,
|
||||
stop: 2,
|
||||
err: ErrInvalidStopPosition,
|
||||
expect: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, each := range cases {
|
||||
|
||||
@@ -70,6 +70,10 @@ func (ir *ImmutableResource) Get() (any, error) {
|
||||
}
|
||||
|
||||
func (ir *ImmutableResource) shouldRefresh() bool {
|
||||
if ir.refreshInterval <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
lastTime := ir.lastTime.Load()
|
||||
return lastTime == 0 || lastTime+ir.refreshInterval < timex.Now()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"testing/synctest"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -122,3 +123,35 @@ func TestImmutableResourceErrorRefreshAlways(t *testing.T) {
|
||||
assert.Equal(t, "any", err.Error())
|
||||
assert.Equal(t, 2, count)
|
||||
}
|
||||
|
||||
func TestImmutableResourceErrorRefreshWithoutClockAdvance(t *testing.T) {
|
||||
synctest.Test(t, func(t *testing.T) {
|
||||
var count int
|
||||
fetchErr := errors.New("fetch failed")
|
||||
r := NewImmutableResource(func() (any, error) {
|
||||
count++
|
||||
if count == 1 {
|
||||
return nil, fetchErr
|
||||
}
|
||||
|
||||
return "hello", nil
|
||||
}, WithRefreshIntervalOnFailure(0))
|
||||
|
||||
res, err := r.Get()
|
||||
assert.Nil(t, res)
|
||||
assert.ErrorIs(t, err, fetchErr)
|
||||
assert.Equal(t, 1, count)
|
||||
|
||||
// The synctest clock stays fixed between calls, reproducing identical clock readings.
|
||||
res, err = r.Get()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello", res)
|
||||
assert.Equal(t, 2, count)
|
||||
|
||||
// A successful fetch stays cached even with a zero refresh interval.
|
||||
res, err = r.Get()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "hello", res)
|
||||
assert.Equal(t, 2, count)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -290,7 +290,7 @@ func TestInjectValidTraceContext(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "unsupported trace flag bits dropped",
|
||||
traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
|
||||
traceparent: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-03",
|
||||
sc: trace.NewSpanContext(trace.SpanContextConfig{
|
||||
TraceID: traceID,
|
||||
SpanID: spanID,
|
||||
|
||||
@@ -70,9 +70,6 @@ func (s *Server) Stop() {
|
||||
|
||||
group := threading.NewRoutineGroup()
|
||||
for _, conn := range s.conns {
|
||||
// new variable to avoid closure problems, can be removed after go 1.22
|
||||
// see https://golang.org/doc/faq#closures_and_goroutines
|
||||
conn := conn
|
||||
group.Run(func() {
|
||||
// ignore the error when closing the connection
|
||||
_ = conn.Conn().Close()
|
||||
|
||||
@@ -1,139 +1,151 @@
|
||||
module github.com/zeromicro/go-zero
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/alicebob/miniredis/v2 v2.37.0
|
||||
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/fullstorydev/grpcurl v1.9.4
|
||||
github.com/go-sql-driver/mysql v1.10.1
|
||||
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.8
|
||||
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.3.0
|
||||
github.com/grafana/pyroscope-go v1.4.2
|
||||
github.com/jackc/pgx/v5 v5.9.2
|
||||
github.com/jhump/protoreflect v1.18.1
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1
|
||||
github.com/pelletier/go-toml/v2 v2.4.3
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
github.com/redis/go-redis/v9 v9.22.0
|
||||
github.com/spaolacci/murmur3 v1.1.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
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.5.0
|
||||
go.opentelemetry.io/otel v1.40.0
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.2
|
||||
go.opentelemetry.io/otel v1.46.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.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.46.0
|
||||
go.opentelemetry.io/otel/sdk v1.46.0
|
||||
go.opentelemetry.io/otel/trace v1.46.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.50.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/net v0.58.0
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/time v0.14.0
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409
|
||||
google.golang.org/grpc v1.79.3
|
||||
google.golang.org/protobuf v1.36.11
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688
|
||||
google.golang.org/grpc v1.83.2
|
||||
google.golang.org/protobuf v1.36.12
|
||||
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.34.3
|
||||
k8s.io/apimachinery v0.34.3
|
||||
k8s.io/client-go v0.34.3
|
||||
k8s.io/utils v0.0.0-20260319190234-28399d86e0b5
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3
|
||||
)
|
||||
|
||||
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/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||
github.com/coreos/go-systemd/v22 v22.7.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.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/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // 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.23.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.0 // indirect
|
||||
github.com/go-openapi/swag v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/conv v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/fileutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/loading v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/mangling v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/netutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/pools v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/google/gnostic-models v0.7.1 // indirect
|
||||
github.com/google/go-cmp v0.7.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.27.7 // indirect
|
||||
github.com/google/jsonschema-go v0.4.3 // indirect
|
||||
github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 // indirect
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.11 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // 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/jhump/protoreflect/v2 v2.0.0-beta.2 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/compress v1.19.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 // 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/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.27 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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/petermattis/goid v0.0.0-20260725062400-500c67a39b75 // 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/robertkrimen/otto v0.5.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.15.0 // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
github.com/segmentio/encoding v0.5.4 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // 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
|
||||
github.com/yuin/gopher-lua v1.1.2 // 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.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.46.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.11.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
|
||||
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
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.28.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/crypto v0.55.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/klog/v2 v2.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,11 +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.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68=
|
||||
github.com/alicebob/miniredis/v2 v2.37.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/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/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
@@ -18,121 +16,137 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
|
||||
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-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/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||
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=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
||||
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.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/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0 h1:u3riX6BoYRfF4Dr7dwSOroNfdSbEPe9Yyl09/B6wBrQ=
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.37.0/go.mod h1:DReE9MMrmecPy+YvQOAOHNYMALuowAnbjjEMkkWOi6A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3 h1:MVQghNeW+LZcmXe7SY1V36Z+WFMDjpqGAGacLe2T0ds=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.3.3/go.mod h1:TsndJ/ngyIdQRhMcVVGDDHINPLWB7C82oDArY51KfB0=
|
||||
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/fullstorydev/grpcurl v1.9.4 h1:7bC3tlRwS7dPyfhBo0Xmigns8hWH/K4fg9NrafpY57k=
|
||||
github.com/fullstorydev/grpcurl v1.9.4/go.mod h1:XSwqtBookFHuhG8guCuQUZJ4Pq0bDuFvdeg/PPQJf+M=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
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/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/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/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.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.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
|
||||
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
|
||||
github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw=
|
||||
github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg=
|
||||
github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q=
|
||||
github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
|
||||
github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=
|
||||
github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k=
|
||||
github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU=
|
||||
github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4=
|
||||
github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
|
||||
github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU=
|
||||
github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k=
|
||||
github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM=
|
||||
github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
|
||||
github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k=
|
||||
github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
|
||||
github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU=
|
||||
github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU=
|
||||
github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=
|
||||
github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
|
||||
github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
|
||||
github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
|
||||
github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME=
|
||||
github.com/go-sql-driver/mysql v1.10.1/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/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.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/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.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
|
||||
github.com/google/gnostic-models v0.7.1/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/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-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
|
||||
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
|
||||
github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0=
|
||||
github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
|
||||
github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 h1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw=
|
||||
github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
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.8 h1:UvCwIhlx9DeV7F6TW/z8q1Mi4PIm3vuUJ2ZlCEvmA4M=
|
||||
github.com/grafana/pyroscope-go v1.2.8/go.mod h1:SSi59eQ1/zmKoY/BKwa5rSFsJaq+242Bcrr4wPix1g8=
|
||||
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.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII=
|
||||
github.com/grafana/pyroscope-go v1.4.2 h1:0LW5HrUJXgGr9zF5gITP/HaFXN9/LsMiwlgVJAK75l0=
|
||||
github.com/grafana/pyroscope-go v1.4.2/go.mod h1:Ej13Jr05rRJrjWvrrFhfh6gGYXtfibuukOs3Tl3Y7QQ=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.11 h1:el5LYpXissAiCKZ5/6yjlr6mhYVV6Cp5lahTocxraXM=
|
||||
github.com/grafana/pyroscope-go/godeltaprof v0.1.11/go.mod h1:jl1V8M4cWsXciROCPIDDG7CtjSjT/ECbp6eLVuMxYRI=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
|
||||
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.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
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.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/jhump/protoreflect v1.18.1 h1:h4odAaLg9wyn7yHxMF7sSkJ7JfLwK1oy37/1Pi212GE=
|
||||
github.com/jhump/protoreflect v1.18.1/go.mod h1:I2yar2oJEMf0k4EMryPzfV0tvGwN/SejJziYBOpETQo=
|
||||
github.com/jhump/protoreflect/v2 v2.0.0-beta.2 h1:qZU+rEZUOYTz1Bnhi3xbwn+VxdXkLVeEpAeZzVXLY88=
|
||||
github.com/jhump/protoreflect/v2 v2.0.0-beta.2/go.mod h1:4tnOYkB/mq7QTyS3YKtVtNrJv4Psqout8HA1U+hZtgM=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/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/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/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.4.0 h1:u0kr8lbJc1oBcawK7Df+/ajNMpIDFE41OEPxdeTLOn8=
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.0/go.mod h1:Nxc2n+n/GdCebUaqCOhTetptS17SXXNu9IfNTaLDi1E=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=
|
||||
github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc=
|
||||
github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s=
|
||||
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=
|
||||
@@ -149,12 +163,10 @@ 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.3.0 h1:k59bC/lIZREW0/iVaQR8nDHxVq8OVlIzYCOJf421CaM=
|
||||
github.com/pelletier/go-toml/v2 v2.3.0/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/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75 h1:VmZ6mKVkxavKEhEy4ZYyV7BwBYBFBP0TwIqmLk84fpU=
|
||||
github.com/petermattis/goid v0.0.0-20260725062400-500c67a39b75/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
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=
|
||||
@@ -169,36 +181,29 @@ 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.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0=
|
||||
github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4=
|
||||
github.com/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0=
|
||||
github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8=
|
||||
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
|
||||
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
|
||||
github.com/segmentio/encoding v0.5.4/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.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/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1 h1:eXZMLsu+3MLEPJyGJkolqtVrteZfQdUpOWj6LTiDl/E=
|
||||
github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U=
|
||||
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=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.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/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
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=
|
||||
@@ -216,42 +221,42 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS
|
||||
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/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=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
github.com/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||
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.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
|
||||
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.2 h1:b6o2m7zL8g2URuO8urBedAylxojybKXNZTxgkOcl+2w=
|
||||
go.mongodb.org/mongo-driver/v2 v2.8.2/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 v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
|
||||
go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0 h1:OFnwLJr+pF3iHrlGSzbxyuo6/6HyBlnlN1CWEJmBVcw=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.46.0/go.mod h1:716wFneO0ov19A2beH5hjfh9AK5z/VWNAtDijp1Y0/g=
|
||||
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.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0 h1:KrC1YrQeSt46ITMWAbgQx1M1eV1/1TKzttrBzymPmss=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.46.0/go.mod h1:zDSEzoEqsOrgBeGvH66KRgxh90VonFyJqBHA0Pk3+rM=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0 h1:KdRxPiAoMptR3vfWzvjjvutTsSiwbC2uG0496rzZNfo=
|
||||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0/go.mod h1:K/qSA+3G7Eovxi4K09wzrAgkWRnosS0DAOZeEpve7sM=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.46.0 h1:7y0nqfbwuPdaYKwm35PRMRMOa8iYu1SXxnAJNNR2o1M=
|
||||
go.opentelemetry.io/otel/exporters/zipkin v1.46.0/go.mod h1:MGmDLXGsdDzWBOt0y5VcW2u5hRsFV4MzylPvvNkQ9qw=
|
||||
go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
|
||||
go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0 h1:h5CNQQjEbuQXY/JfZtgt3i7HVFV3aHPO2OAwO2eTYPI=
|
||||
go.opentelemetry.io/otel/sdk v1.46.0/go.mod h1:GAERFXFt5SYCEB+YiKUbMBeza6UaDH7GmGOZEfh2gSM=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0 h1:0piZ26EG4RBfebb2jhDH6ERCYHoVWduc3kLgPCwSnSE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.46.0/go.mod h1:I1PbKrdVc8Qu8HYVDNtqVIwLwjNrhsV/uFuxfwg8mO4=
|
||||
go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
|
||||
go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
|
||||
go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
|
||||
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=
|
||||
@@ -260,20 +265,20 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
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=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
|
||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
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=
|
||||
@@ -283,16 +288,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.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/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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=
|
||||
@@ -300,20 +305,18 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
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.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
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=
|
||||
@@ -321,29 +324,29 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
||||
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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
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=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
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.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
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-20260819154853-08b0e4226688 h1:ax2KzoSRIZU/M0cIxri3pKxy99vniH1PVxWC6si/eZI=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260819154853-08b0e4226688/go.mod h1:1RJ9BQGyNdZwkGc1eTqkErfRZ6RJyYPHZo73BZ1vQqI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5 h1:1VUiZAXyC+zmiFYi+WLtBzr68Cj8wOofHjjrA/kkizc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/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/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/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.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=
|
||||
@@ -361,17 +364,17 @@ 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/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
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=
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/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/structured-merge-diff/v6 v6.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/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=
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+18
-35
@@ -4,7 +4,7 @@
|
||||
|
||||
***缩短从需求到上线的距离***
|
||||
|
||||
[English](readme.md) | 简体中文
|
||||
[English](readme.md) | 简体中文 | [한국어](readme-ko.md)
|
||||
|
||||
[](https://goreportcard.com/report/github.com/zeromicro/go-zero)
|
||||
[](https://goproxy.cn/stats/github.com/zeromicro/go-zero/badges/download-count.svg)
|
||||
@@ -78,53 +78,36 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
|
||||
|
||||
## AI 原生开发
|
||||
|
||||
go-zero 团队构建了完整的 AI 工具生态,让 Claude、GitHub Copilot、Cursor 生成符合 go-zero 规范的代码。
|
||||
go-zero 团队为 Claude Code、GitHub Copilot、Cursor 等 AI 编程助手提供工作流程指南和实现模式。对于支持终端操作的助手,推荐直接运行 `goctl`,生成符合框架规范的代码。
|
||||
|
||||
### 三大核心项目
|
||||
### 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)** - 基于 MCP 的代码生成工具
|
||||
- **[ai-context](https://github.com/zeromicro/ai-context)** - 工作流程指南:如何处理 go-zero 开发任务,以及何时使用 goctl。
|
||||
- **[zero-skills](https://github.com/zeromicro/zero-skills)** - 按需查阅的实现模式、示例和 goctl 命令参考。
|
||||
- **[mcp-zero](https://github.com/zeromicro/mcp-zero)** - 可选适配器,通过模型上下文协议(MCP)提供 goctl 代码生成能力。
|
||||
|
||||
### 快速配置
|
||||
|
||||
#### GitHub Copilot
|
||||
安装 goctl,并确保它在 `$PATH` 环境变量中:
|
||||
|
||||
```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 # 更新
|
||||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||||
goctl --version
|
||||
```
|
||||
|
||||
#### Cursor
|
||||
```bash
|
||||
git submodule add https://github.com/zeromicro/ai-context.git .cursorrules
|
||||
git submodule update --remote .cursorrules # 更新
|
||||
```
|
||||
参考 [ai-context 配置指南](https://github.com/zeromicro/ai-context#manual-setup) 和 [zero-skills 集成指南](https://github.com/zeromicro/zero-skills/tree/main/getting-started),为助手配置工作流程指令和模式库。
|
||||
|
||||
#### 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
|
||||
```
|
||||
1. 查阅 **ai-context** 了解工作流程,按需参考 **zero-skills** 中的实现模式。
|
||||
2. 定义 `.api` 或 `.proto` 文件,然后在终端运行 **goctl** 生成代码。
|
||||
3. 实现业务逻辑、整理依赖、构建并测试服务。
|
||||
|
||||
### 协同工作原理
|
||||
**示例**:创建 REST API → 编写 `.api` 文件 → 运行 `goctl api go` → 参考 **zero-skills** 实现业务逻辑 → 运行 `go mod tidy`、`go build ./...` 和 `go test ./...`。
|
||||
|
||||
AI 助手通过三个工具协同配合:
|
||||
1. **ai-context** - 工作流程指导
|
||||
2. **zero-skills** - 实现模式
|
||||
3. **mcp-zero** - 实时代码生成
|
||||
### 可选的 MCP 集成
|
||||
|
||||
**示例**:创建新的 REST API → AI 读取 **ai-context** 了解工作流 → 调用 **mcp-zero** 生成代码 → 参考 **zero-skills** 实现模式 → 生成符合规范的代码 ✅
|
||||
对于 Claude Desktop 等使用 MCP 工具的客户端,可以配置 **[mcp-zero](https://github.com/zeromicro/mcp-zero)**,通过 MCP 调用 goctl。两种方式使用相同的代码生成引擎;支持终端操作的助手可以直接使用上述流程,无需 MCP 服务器。
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
# 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>
|
||||
|
||||
[](https://codecov.io/gh/zeromicro/go-zero)
|
||||
[](https://goreportcard.com/report/github.com/zeromicro/go-zero)
|
||||
[](https://github.com/zeromicro/go-zero)
|
||||
[](https://pkg.go.dev/github.com/zeromicro/go-zero)
|
||||
[](https://github.com/avelino/awesome-go)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](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-zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go-zero - A web & rpc framework written in Go. | 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 완전 호환
|
||||
* **고성능** - 속도와 효율성을 위한 최적화
|
||||
* **회복 탄력성** - 내장 서킷 브레이커, 속도 제한, 로드 셰딩, 타임아웃 제어
|
||||
* **서비스 메시** - 서비스 디스커버리, 부하 분산, 호출 추적
|
||||
* **개발자 도구** - 자동 파라미터 검증, 캐시 관리, 메트릭과 모니터링
|
||||
|
||||

|
||||
|
||||
## 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 Code, GitHub Copilot, Cursor 등의 AI 코딩 어시스턴트를 위한 워크플로 가이드와 구현 패턴을 제공합니다. 터미널을 사용할 수 있는 어시스턴트에는 `goctl`을 직접 실행하여 프레임워크 규칙을 따르는 코드를 생성하는 방식을 권장합니다.
|
||||
|
||||
### AI 도구 프로젝트
|
||||
|
||||
- **[ai-context](https://github.com/zeromicro/ai-context)** - go-zero 개발 작업의 진행 방식과 goctl 사용 시점을 안내하는 워크플로 가이드.
|
||||
- **[zero-skills](https://github.com/zeromicro/zero-skills)** - 필요할 때 참조하는 구현 패턴, 예제, goctl 명령어 레퍼런스.
|
||||
- **[mcp-zero](https://github.com/zeromicro/mcp-zero)** - Model Context Protocol(MCP)을 통해 goctl 코드 생성 기능을 제공하는 선택적 어댑터.
|
||||
|
||||
### 빠른 설정
|
||||
|
||||
goctl을 설치하고 `$PATH`에 포함되어 있는지 확인하세요.
|
||||
|
||||
```bash
|
||||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||||
goctl --version
|
||||
```
|
||||
|
||||
[ai-context 설정 가이드](https://github.com/zeromicro/ai-context#manual-setup)와 [zero-skills 통합 가이드](https://github.com/zeromicro/zero-skills/tree/main/getting-started)를 참고하여 어시스턴트의 워크플로 지침과 패턴 라이브러리를 설정하세요.
|
||||
|
||||
### 동작 방식
|
||||
|
||||
1. **ai-context**에서 워크플로를 확인하고 **zero-skills**에서 관련 패턴을 참조합니다.
|
||||
2. `.api` 또는 `.proto` 명세를 작성한 뒤 터미널에서 **goctl**을 실행하여 코드를 생성합니다.
|
||||
3. 비즈니스 로직을 구현하고 의존성을 정리한 뒤 서비스를 빌드하고 테스트합니다.
|
||||
|
||||
**예시**: REST API 생성 → `.api` 명세 작성 → `goctl api go` 실행 → **zero-skills** 패턴을 참고하여 로직 구현 → `go mod tidy`, `go build ./...`, `go test ./...` 실행.
|
||||
|
||||
### 선택적 MCP 통합
|
||||
|
||||
Claude Desktop처럼 MCP 도구를 사용하는 클라이언트에서는 **[mcp-zero](https://github.com/zeromicro/mcp-zero)**를 설정하여 MCP를 통해 goctl을 호출할 수 있습니다. 두 방식은 같은 코드 생성 엔진을 사용하며, 터미널을 사용할 수 있는 어시스턴트는 MCP 서버 없이 위의 직접 실행 워크플로를 사용할 수 있습니다.
|
||||
|
||||
## 빠른 시작
|
||||
|
||||
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
|
||||
...
|
||||
```
|
||||
|
||||
## 벤치마크
|
||||
|
||||

|
||||
|
||||
[테스트 코드 보기](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"/>
|
||||
<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를 눌러주세요. 여러분의 지원은 큰 힘이 됩니다!
|
||||
@@ -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-zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go-zero - A web & rpc framework written in Go. | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
@@ -84,53 +84,36 @@ go get -u github.com/zeromicro/go-zero
|
||||
|
||||
## AI-Native Development
|
||||
|
||||
The go-zero team provides AI tooling for Claude, GitHub Copilot, Cursor to generate framework-compliant code.
|
||||
The go-zero team provides workflow guidance and implementation patterns for AI coding assistants such as Claude Code, GitHub Copilot, and Cursor. For assistants with terminal access, the recommended workflow is to run `goctl` directly to generate framework-compliant code.
|
||||
|
||||
### Three Core Projects
|
||||
### AI Tooling Projects
|
||||
|
||||
**[ai-context](https://github.com/zeromicro/ai-context)** - Workflow guide for AI assistants
|
||||
|
||||
**[zero-skills](https://github.com/zeromicro/zero-skills)** - Pattern library with examples
|
||||
|
||||
**[mcp-zero](https://github.com/zeromicro/mcp-zero)** - Code generation tools via Model Context Protocol
|
||||
- **[ai-context](https://github.com/zeromicro/ai-context)** - Workflow guidance: how to approach go-zero development tasks and when to use goctl.
|
||||
- **[zero-skills](https://github.com/zeromicro/zero-skills)** - Implementation patterns, examples, and goctl command reference, loaded as needed.
|
||||
- **[mcp-zero](https://github.com/zeromicro/mcp-zero)** - Optional adapter that exposes goctl code generation through the Model Context Protocol (MCP).
|
||||
|
||||
### Quick Setup
|
||||
|
||||
#### GitHub Copilot
|
||||
Install goctl and ensure it is available in your `$PATH`:
|
||||
|
||||
```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 # Update
|
||||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||||
goctl --version
|
||||
```
|
||||
|
||||
#### Cursor
|
||||
```bash
|
||||
git submodule add https://github.com/zeromicro/ai-context.git .cursorrules
|
||||
git submodule update --remote .cursorrules # Update
|
||||
```
|
||||
|
||||
#### Windsurf
|
||||
```bash
|
||||
git submodule add https://github.com/zeromicro/ai-context.git .windsurfrules
|
||||
git submodule update --remote .windsurfrules # Update
|
||||
```
|
||||
|
||||
#### Claude Desktop
|
||||
```bash
|
||||
git clone https://github.com/zeromicro/mcp-zero.git && cd mcp-zero && go build
|
||||
# Configure: ~/Library/Application Support/Claude/claude_desktop_config.json
|
||||
# Or: claude mcp add --transport stdio mcp-zero --env GOCTL_PATH=/path/to/goctl -- /path/to/mcp-zero
|
||||
```
|
||||
Follow the [ai-context setup guide](https://github.com/zeromicro/ai-context#manual-setup) and [zero-skills integration guides](https://github.com/zeromicro/zero-skills/tree/main/getting-started) to configure your assistant's workflow instructions and pattern library.
|
||||
|
||||
### How It Works
|
||||
|
||||
AI assistants use these tools together:
|
||||
1. **ai-context** - workflow guidance
|
||||
2. **zero-skills** - implementation patterns
|
||||
3. **mcp-zero** - real-time code generation
|
||||
1. Consult **ai-context** for the workflow and **zero-skills** for relevant patterns.
|
||||
2. Define the `.api` or `.proto` specification, then run **goctl** in the terminal to generate code.
|
||||
3. Implement business logic, resolve dependencies, build, and test the service.
|
||||
|
||||
**Example**: Creating a REST API → AI reads **ai-context** for workflow → calls **mcp-zero** to generate code → references **zero-skills** for patterns → produces production-ready code ✅
|
||||
**Example**: Create a REST API → write the `.api` specification → run `goctl api go` → implement the logic using **zero-skills** patterns → run `go mod tidy`, `go build ./...`, and `go test ./...`.
|
||||
|
||||
### Optional MCP Integration
|
||||
|
||||
For clients that use MCP tools, such as Claude Desktop, configure **[mcp-zero](https://github.com/zeromicro/mcp-zero)** to invoke goctl through MCP. It uses the same code generation engine; assistants with terminal access can use the direct workflow above without an MCP server.
|
||||
|
||||
## Quick Start
|
||||
|
||||
|
||||
@@ -94,9 +94,7 @@ func ParseForm(r *http.Request, v any) error {
|
||||
// ParseHeader parses the request header and returns a map.
|
||||
func ParseHeader(headerValue string) map[string]string {
|
||||
ret := make(map[string]string)
|
||||
fields := strings.Split(headerValue, separator)
|
||||
|
||||
for _, field := range fields {
|
||||
for field := range strings.SplitSeq(headerValue, separator) {
|
||||
field = strings.TrimSpace(field)
|
||||
if len(field) == 0 {
|
||||
continue
|
||||
|
||||
@@ -141,7 +141,7 @@ func getMiddleware(api *spec.ApiSpec) []string {
|
||||
for _, g := range api.Service.Groups {
|
||||
middleware := g.GetAnnotation("middleware")
|
||||
if len(middleware) > 0 {
|
||||
for _, item := range strings.Split(middleware, ",") {
|
||||
for item := range strings.SplitSeq(middleware, ",") {
|
||||
result.Add(strings.TrimSpace(item))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
const inlineTagAPI = `
|
||||
syntax = "v1"
|
||||
|
||||
type (
|
||||
Auth {
|
||||
Token string ` + "`header:\"Authorization\"`" + `
|
||||
}
|
||||
Middle {
|
||||
Auth
|
||||
}
|
||||
PointerRequest {
|
||||
*Auth
|
||||
}
|
||||
NestedRequest {
|
||||
Middle
|
||||
}
|
||||
RecursiveRequest {
|
||||
Token string ` + "`header:\"X-Token\"`" + `
|
||||
*RecursiveRequest
|
||||
}
|
||||
)
|
||||
|
||||
service test-api {
|
||||
@handler Pointer
|
||||
get /pointer (PointerRequest)
|
||||
|
||||
@handler Nested
|
||||
get /nested (NestedRequest)
|
||||
|
||||
@handler Recursive
|
||||
get /recursive (RecursiveRequest)
|
||||
}
|
||||
`
|
||||
|
||||
func TestParseContentResolvesInlineTypesForTagLookup(t *testing.T) {
|
||||
apiSpec, err := ParseContent(inlineTagAPI)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, name := range []string{"PointerRequest", "NestedRequest", "RecursiveRequest"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tp := findStructByName(t, apiSpec.Types, name)
|
||||
require.NotEmpty(t, tp.GetTagMembers("header"))
|
||||
require.Empty(t, tp.GetTagMembers("path"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func findStructByName(t *testing.T, types []spec.Type, name string) spec.DefineStruct {
|
||||
t.Helper()
|
||||
for _, tp := range types {
|
||||
if tp.Name() == name {
|
||||
defined, ok := tp.(spec.DefineStruct)
|
||||
require.True(t, ok)
|
||||
return defined
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("type %s not found", name)
|
||||
return spec.DefineStruct{}
|
||||
}
|
||||
@@ -145,6 +145,17 @@ func (p parser) fillTypes() error {
|
||||
case spec.DefineStruct:
|
||||
var members []spec.Member
|
||||
for _, member := range v.Members {
|
||||
if member.IsInline {
|
||||
tp, err := p.resolveInlineType(member.Type, map[string]bool{v.RawName: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
member.Type = tp
|
||||
members = append(members, member)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := member.Type.(type) {
|
||||
case spec.DefineStruct:
|
||||
tp, err := p.findDefinedType(v.RawName)
|
||||
@@ -167,6 +178,62 @@ func (p parser) fillTypes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p parser) resolveInlineType(tp spec.Type, resolving map[string]bool) (spec.Type, error) {
|
||||
switch v := tp.(type) {
|
||||
case spec.DefineStruct:
|
||||
if resolving[v.RawName] {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
tp, err := p.findDefinedType(v.RawName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defined, ok := (*tp).(spec.DefineStruct)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type %s is not a struct", v.RawName)
|
||||
}
|
||||
|
||||
resolving[v.RawName] = true
|
||||
defer delete(resolving, v.RawName)
|
||||
for i := range defined.Members {
|
||||
if !defined.Members[i].IsInline {
|
||||
continue
|
||||
}
|
||||
|
||||
resolved, err := p.resolveInlineType(defined.Members[i].Type, resolving)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defined.Members[i].Type = resolved
|
||||
}
|
||||
return defined, nil
|
||||
case spec.NestedStruct:
|
||||
for i := range v.Members {
|
||||
if !v.Members[i].IsInline {
|
||||
continue
|
||||
}
|
||||
|
||||
resolved, err := p.resolveInlineType(v.Members[i].Type, resolving)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Members[i].Type = resolved
|
||||
}
|
||||
return v, nil
|
||||
case spec.PointerType:
|
||||
resolved, err := p.resolveInlineType(v.Type, resolving)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Type = resolved
|
||||
return v, nil
|
||||
default:
|
||||
return tp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (p parser) findDefinedType(name string) (*spec.Type, error) {
|
||||
for _, item := range p.spec.Types {
|
||||
if _, ok := item.(spec.DefineStruct); ok {
|
||||
|
||||
@@ -139,18 +139,43 @@ func (m Member) IsFormMember() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsTagMember returns true if contains given tag
|
||||
// IsTagMember returns true if the member contains the given tag.
|
||||
// For inline members, it recursively checks the members of the referenced
|
||||
// struct, since inline members themselves carry no tag and any matching tag
|
||||
// must live on one of their children. This avoids spuriously reporting the
|
||||
// presence of a tag (e.g. `header`) for an inline struct whose children do
|
||||
// not actually use that tag. See go-zero #4800.
|
||||
func (m Member) IsTagMember(tagKey string) bool {
|
||||
if m.IsInline {
|
||||
return true
|
||||
}
|
||||
|
||||
tags := m.Tags()
|
||||
for _, tag := range tags {
|
||||
if tag.Key == tagKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if m.IsInline {
|
||||
return typeContainsTag(m.Type, tagKey)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func typeContainsTag(tp Type, tagKey string) bool {
|
||||
var members []Member
|
||||
switch v := tp.(type) {
|
||||
case DefineStruct:
|
||||
members = v.Members
|
||||
case NestedStruct:
|
||||
members = v.Members
|
||||
case PointerType:
|
||||
return typeContainsTag(v.Type, tagKey)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
for _, child := range members {
|
||||
if child.IsTagMember(tagKey) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package spec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMember_IsTagMember(t *testing.T) {
|
||||
t.Run("non-inline member with matching tag returns true", func(t *testing.T) {
|
||||
m := Member{Tag: `header:"Authorization"`}
|
||||
assert.True(t, m.IsTagMember("header"))
|
||||
})
|
||||
|
||||
t.Run("non-inline member without matching tag returns false", func(t *testing.T) {
|
||||
m := Member{Tag: `json:"username"`}
|
||||
assert.False(t, m.IsTagMember("header"))
|
||||
assert.False(t, m.IsTagMember("path"))
|
||||
assert.False(t, m.IsTagMember("form"))
|
||||
})
|
||||
|
||||
t.Run("non-inline member without any tag returns false", func(t *testing.T) {
|
||||
m := Member{}
|
||||
assert.False(t, m.IsTagMember("header"))
|
||||
})
|
||||
|
||||
t.Run("inline struct without matching child tag returns false (#4800)", func(t *testing.T) {
|
||||
m := Member{
|
||||
Name: "Pagination",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{
|
||||
RawName: "Pagination",
|
||||
Members: []Member{
|
||||
{Name: "Page", Tag: `json:"page"`},
|
||||
{Name: "PageSize", Tag: `json:"pageSize"`},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.False(t, m.IsTagMember("header"))
|
||||
assert.False(t, m.IsTagMember("path"))
|
||||
assert.False(t, m.IsTagMember("form"))
|
||||
})
|
||||
|
||||
t.Run("inline struct whose child has matching tag returns true", func(t *testing.T) {
|
||||
m := Member{
|
||||
Name: "Auth",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{
|
||||
RawName: "Auth",
|
||||
Members: []Member{
|
||||
{Name: "Token", Tag: `header:"Authorization"`},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.True(t, m.IsTagMember("header"))
|
||||
})
|
||||
|
||||
t.Run("nested inline structs are recursed", func(t *testing.T) {
|
||||
m := Member{
|
||||
Name: "Outer",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{
|
||||
RawName: "Outer",
|
||||
Members: []Member{
|
||||
{
|
||||
Name: "Inner",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{
|
||||
RawName: "Inner",
|
||||
Members: []Member{
|
||||
{Name: "Token", Tag: `header:"X-Token"`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.True(t, m.IsTagMember("header"))
|
||||
})
|
||||
|
||||
t.Run("nested inline structs without matching child return false", func(t *testing.T) {
|
||||
m := Member{
|
||||
Name: "Outer",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{
|
||||
RawName: "Outer",
|
||||
Members: []Member{
|
||||
{
|
||||
Name: "Inner",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{
|
||||
RawName: "Inner",
|
||||
Members: []Member{
|
||||
{Name: "Page", Tag: `json:"page"`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.False(t, m.IsTagMember("header"))
|
||||
})
|
||||
|
||||
t.Run("inline NestedStruct whose child has matching tag returns true", func(t *testing.T) {
|
||||
m := Member{
|
||||
Name: "Auth",
|
||||
IsInline: true,
|
||||
Type: NestedStruct{
|
||||
RawName: "Auth",
|
||||
Members: []Member{
|
||||
{Name: "Token", Tag: `header:"Authorization"`},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.True(t, m.IsTagMember("header"))
|
||||
})
|
||||
|
||||
t.Run("inline PointerType whose child has matching tag returns true", func(t *testing.T) {
|
||||
m := Member{
|
||||
Name: "Auth",
|
||||
IsInline: true,
|
||||
Type: PointerType{
|
||||
RawName: "*Auth",
|
||||
Type: DefineStruct{
|
||||
RawName: "Auth",
|
||||
Members: []Member{
|
||||
{Name: "Token", Tag: `header:"Authorization"`},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.True(t, m.IsTagMember("header"))
|
||||
assert.False(t, m.IsTagMember("path"))
|
||||
})
|
||||
|
||||
t.Run("empty inline struct returns false", func(t *testing.T) {
|
||||
m := Member{
|
||||
Name: "Empty",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{RawName: "Empty"},
|
||||
}
|
||||
assert.False(t, m.IsTagMember("header"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefineStruct_GetTagMembers_InlineRegression(t *testing.T) {
|
||||
s := DefineStruct{
|
||||
RawName: "QueryUserListReq",
|
||||
Members: []Member{
|
||||
{Name: "Username", Tag: `json:"username,optional"`},
|
||||
{
|
||||
Name: "Pagination",
|
||||
IsInline: true,
|
||||
Type: DefineStruct{
|
||||
RawName: "Pagination",
|
||||
Members: []Member{
|
||||
{Name: "Page", Tag: `json:"page"`},
|
||||
{Name: "PageSize", Tag: `json:"pageSize"`},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Empty(t, s.GetTagMembers("header"),
|
||||
"inline struct without header-tagged children must not match header (#4800)")
|
||||
assert.Empty(t, s.GetTagMembers("path"))
|
||||
}
|
||||
@@ -54,6 +54,8 @@ const (
|
||||
propertyKeyDeprecated = "deprecated"
|
||||
propertyKeyPrefix = "prefix"
|
||||
propertyKeyAuthType = "authType"
|
||||
propertyKeyRespCode = "respCode"
|
||||
propertyKeyResponses = "responses"
|
||||
propertyKeyHost = "host"
|
||||
propertyKeyBasePath = "basePath"
|
||||
propertyKeyWrapCodeMsg = "wrapCodeMsg"
|
||||
|
||||
@@ -82,6 +82,7 @@ type (
|
||||
service Swagger {
|
||||
@doc (
|
||||
description: "form demo"
|
||||
respCode: "201" // HTTP status code corresponding to Swagger
|
||||
)
|
||||
@handler form
|
||||
post /form (FormReq) returns (FormResp)
|
||||
@@ -102,6 +103,27 @@ type (
|
||||
Language string `json:"language"`
|
||||
Gender string `json:"gender"`
|
||||
}
|
||||
EmbeddedUser {
|
||||
UserId int `json:"userId,example=10"`
|
||||
Username string `json:"username,example=keson.an"`
|
||||
}
|
||||
EmbeddedAudit {
|
||||
TraceId string `json:"traceId,example=trace-001"`
|
||||
CreatedBy string `json:"createdBy,optional,example=system"`
|
||||
}
|
||||
EmbeddedProfile {
|
||||
EmbeddedUser
|
||||
*EmbeddedAudit
|
||||
Nickname string `json:"nickname,optional,example=keson"`
|
||||
}
|
||||
EmbeddedJsonReq {
|
||||
EmbeddedProfile
|
||||
RequestId string `json:"requestId,example=req-001"`
|
||||
}
|
||||
EmbeddedJsonResp {
|
||||
EmbeddedProfile
|
||||
Success bool `json:"success,example=true"`
|
||||
}
|
||||
ComplexJsonLevel2 {
|
||||
// basic
|
||||
Integer int `json:"integer,example=1"`
|
||||
@@ -237,5 +259,10 @@ service Swagger {
|
||||
)
|
||||
@handler jsonComplex
|
||||
post /json/complex (ComplexJsonReq) returns (ComplexJsonResp)
|
||||
}
|
||||
|
||||
@doc (
|
||||
description: "embedded json request body API"
|
||||
)
|
||||
@handler jsonEmbedded
|
||||
post /json/embedded (EmbeddedJsonReq) returns (EmbeddedJsonResp)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
|
||||
@@ -84,6 +84,7 @@ type (
|
||||
service Swagger {
|
||||
@doc (
|
||||
description: "form 接口"
|
||||
respCode: "201" // 对应 Swagger 的 HTTP 状态码
|
||||
)
|
||||
@handler form
|
||||
post /form (FormReq) returns (FormResp)
|
||||
@@ -244,4 +245,3 @@ service Swagger {
|
||||
@handler jsonComplex
|
||||
post /json/complex (ComplexJsonReq) returns (ComplexJsonResp)
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
|
||||
@@ -2,64 +2,108 @@ package swagger
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/spec"
|
||||
apiSpec "github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
func jsonResponseFromType(ctx Context, atDoc apiSpec.AtDoc, tp apiSpec.Type) *spec.Responses {
|
||||
statusCode := responseStatusCode(atDoc)
|
||||
var response spec.Response
|
||||
if tp == nil {
|
||||
return &spec.Responses{
|
||||
ResponsesProps: spec.ResponsesProps{
|
||||
StatusCodeResponses: map[int]spec.Response{
|
||||
http.StatusOK: {
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: "",
|
||||
Schema: &spec.Schema{},
|
||||
},
|
||||
},
|
||||
},
|
||||
response = spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
props := spec.SchemaProps{
|
||||
AdditionalProperties: mapFromGoType(ctx, tp),
|
||||
Items: itemsFromGoType(ctx, tp),
|
||||
}
|
||||
if ctx.UseDefinitions {
|
||||
structName, ok := containsStruct(tp)
|
||||
if ok {
|
||||
props.Ref = spec.MustCreateRef(getRefName(structName))
|
||||
return &spec.Responses{
|
||||
ResponsesProps: spec.ResponsesProps{
|
||||
StatusCodeResponses: map[int]spec.Response{
|
||||
http.StatusOK: {
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
props := spec.SchemaProps{
|
||||
AdditionalProperties: mapFromGoType(ctx, tp),
|
||||
Items: itemsFromGoType(ctx, tp),
|
||||
}
|
||||
}
|
||||
|
||||
p, _ := propertiesFromType(ctx, tp)
|
||||
props.Type = typeFromGoType(ctx, tp)
|
||||
props.Properties = p
|
||||
return &spec.Responses{
|
||||
ResponsesProps: spec.ResponsesProps{
|
||||
StatusCodeResponses: map[int]spec.Response{
|
||||
http.StatusOK: {
|
||||
if ctx.UseDefinitions {
|
||||
structName, ok := containsStruct(tp)
|
||||
if ok {
|
||||
props.Ref = spec.MustCreateRef(getRefName(structName))
|
||||
response = spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
|
||||
},
|
||||
},
|
||||
}
|
||||
return responsesFromStatusCode(atDoc, statusCode, response)
|
||||
}
|
||||
}
|
||||
|
||||
p, _ := propertiesFromType(ctx, tp)
|
||||
props.Type = typeFromGoType(ctx, tp)
|
||||
props.Properties = p
|
||||
response = spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: wrapCodeMsgProps(ctx, props, atDoc),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return responsesFromStatusCode(atDoc, statusCode, response)
|
||||
}
|
||||
|
||||
func responsesFromStatusCode(atDoc apiSpec.AtDoc, statusCode int, response spec.Response) *spec.Responses {
|
||||
statusCodeResponses := map[int]spec.Response{
|
||||
statusCode: response,
|
||||
}
|
||||
for code, description := range responseDescriptions(atDoc) {
|
||||
if code == statusCode {
|
||||
responseWithDescription := response
|
||||
responseWithDescription.Description = description
|
||||
statusCodeResponses[code] = responseWithDescription
|
||||
} else {
|
||||
statusCodeResponses[code] = spec.Response{
|
||||
ResponseProps: spec.ResponseProps{
|
||||
Description: description,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &spec.Responses{
|
||||
ResponsesProps: spec.ResponsesProps{
|
||||
StatusCodeResponses: statusCodeResponses,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func responseStatusCode(atDoc apiSpec.AtDoc) int {
|
||||
return getOrDefault(atDoc.Properties, propertyKeyRespCode, http.StatusOK, func(str string, def int) int {
|
||||
statusCode, err := strconv.Atoi(str)
|
||||
if err != nil || statusCode < http.StatusContinue || statusCode > 599 {
|
||||
return def
|
||||
}
|
||||
|
||||
return statusCode
|
||||
})
|
||||
}
|
||||
|
||||
func responseDescriptions(atDoc apiSpec.AtDoc) map[int]string {
|
||||
descriptions := make(map[int]string)
|
||||
for item := range strings.SplitSeq(getStringFromKVOrDefault(atDoc.Properties, propertyKeyResponses, ""), "<br>") {
|
||||
codeText, description, ok := strings.Cut(item, "-")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
code, err := strconv.Atoi(strings.TrimSpace(codeText))
|
||||
if err != nil || code < http.StatusContinue || code > 599 {
|
||||
continue
|
||||
}
|
||||
|
||||
descriptions[code] = strings.TrimSpace(description)
|
||||
}
|
||||
|
||||
return descriptions
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
func TestJsonResponseFromTypeStatusCode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
properties map[string]string
|
||||
response spec.Type
|
||||
want int
|
||||
}{
|
||||
{
|
||||
name: "defaults to ok",
|
||||
response: spec.PrimitiveType{RawName: "string"},
|
||||
want: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "uses custom status code",
|
||||
properties: map[string]string{
|
||||
propertyKeyRespCode: "201",
|
||||
},
|
||||
response: spec.PrimitiveType{RawName: "string"},
|
||||
want: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
name: "supports quoted custom status code",
|
||||
properties: map[string]string{
|
||||
propertyKeyRespCode: `"204"`,
|
||||
},
|
||||
want: http.StatusNoContent,
|
||||
},
|
||||
{
|
||||
name: "defaults for invalid status code",
|
||||
properties: map[string]string{
|
||||
propertyKeyRespCode: "600",
|
||||
},
|
||||
response: spec.PrimitiveType{RawName: "string"},
|
||||
want: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "defaults for non-numeric status code",
|
||||
properties: map[string]string{
|
||||
propertyKeyRespCode: "created",
|
||||
},
|
||||
response: spec.PrimitiveType{RawName: "string"},
|
||||
want: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "uses custom status code without response body",
|
||||
properties: map[string]string{
|
||||
propertyKeyRespCode: "204",
|
||||
},
|
||||
want: http.StatusNoContent,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
responses := jsonResponseFromType(testingContext(t), spec.AtDoc{
|
||||
Properties: test.properties,
|
||||
}, test.response)
|
||||
|
||||
assert.Len(t, responses.StatusCodeResponses, 1)
|
||||
assert.Contains(t, responses.StatusCodeResponses, test.want)
|
||||
if test.response == nil {
|
||||
assert.Nil(t, responses.StatusCodeResponses[test.want].Schema)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonResponseFromTypeMultipleStatusCodes(t *testing.T) {
|
||||
responses := jsonResponseFromType(testingContext(t), spec.AtDoc{
|
||||
Properties: map[string]string{
|
||||
propertyKeyResponses: "200-OK<br>401-Unauthorized<br>404-User not found",
|
||||
},
|
||||
}, spec.PrimitiveType{RawName: "string"})
|
||||
|
||||
assert.Len(t, responses.StatusCodeResponses, 3)
|
||||
assert.Equal(t, "OK", responses.StatusCodeResponses[http.StatusOK].Description)
|
||||
assert.NotNil(t, responses.StatusCodeResponses[http.StatusOK].Schema)
|
||||
assert.Equal(t, "Unauthorized", responses.StatusCodeResponses[http.StatusUnauthorized].Description)
|
||||
assert.Nil(t, responses.StatusCodeResponses[http.StatusUnauthorized].Schema)
|
||||
assert.Equal(t, "User not found", responses.StatusCodeResponses[http.StatusNotFound].Description)
|
||||
assert.Nil(t, responses.StatusCodeResponses[http.StatusNotFound].Schema)
|
||||
}
|
||||
@@ -202,6 +202,8 @@ func expandMembers(ctx Context, tp apiSpec.Type) []apiSpec.Member {
|
||||
}
|
||||
members = append(members, v)
|
||||
}
|
||||
case apiSpec.PointerType:
|
||||
members = expandMembers(ctx, val.Type)
|
||||
}
|
||||
|
||||
return members
|
||||
|
||||
@@ -3,8 +3,8 @@ package swagger
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
func Test_pathVariable2SwaggerVariable(t *testing.T) {
|
||||
@@ -138,3 +138,55 @@ func TestArrayWithoutDefinitions(t *testing.T) {
|
||||
assert.Contains(t, arrayField.Items.Schema.Properties, "itemName")
|
||||
assert.Equal(t, []string{"itemName"}, arrayField.Items.Schema.Required)
|
||||
}
|
||||
|
||||
func TestPropertiesFromTypeInlinePointerMembers(t *testing.T) {
|
||||
ctx := testingContext(t)
|
||||
|
||||
baseStruct := spec.DefineStruct{
|
||||
RawName: "EmbeddedUser",
|
||||
Members: []spec.Member{
|
||||
{
|
||||
Name: "UserId",
|
||||
Type: spec.PrimitiveType{RawName: "int"},
|
||||
Tag: `json:"userId"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
auditStruct := spec.DefineStruct{
|
||||
RawName: "EmbeddedAudit",
|
||||
Members: []spec.Member{
|
||||
{
|
||||
Name: "TraceId",
|
||||
Type: spec.PrimitiveType{RawName: "string"},
|
||||
Tag: `json:"traceId"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
testStruct := spec.DefineStruct{
|
||||
RawName: "EmbeddedProfile",
|
||||
Members: []spec.Member{
|
||||
{
|
||||
Type: baseStruct,
|
||||
IsInline: true,
|
||||
},
|
||||
{
|
||||
Type: spec.PointerType{
|
||||
Type: auditStruct,
|
||||
},
|
||||
IsInline: true,
|
||||
},
|
||||
{
|
||||
Name: "Nickname",
|
||||
Type: spec.PrimitiveType{RawName: "string"},
|
||||
Tag: `json:"nickname,optional"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
properties, required := propertiesFromType(ctx, testStruct)
|
||||
|
||||
assert.Contains(t, properties, "userId")
|
||||
assert.Contains(t, properties, "traceId")
|
||||
assert.Contains(t, properties, "nickname")
|
||||
assert.ElementsMatch(t, []string{"userId", "traceId"}, required)
|
||||
}
|
||||
|
||||
+81
-67
@@ -1,119 +1,133 @@
|
||||
module github.com/zeromicro/go-zero/tools/goctl
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.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-openapi/spec v1.0.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
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/stretchr/testify v1.12.1
|
||||
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.34.0
|
||||
google.golang.org/grpc v1.79.3
|
||||
google.golang.org/protobuf v1.36.11
|
||||
github.com/zeromicro/go-zero v1.10.3
|
||||
golang.org/x/text v0.41.0
|
||||
google.golang.org/grpc v1.83.2
|
||||
google.golang.org/protobuf v1.36.12
|
||||
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/coreos/go-systemd/v22 v22.7.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.13.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.2 // indirect
|
||||
github.com/go-logr/logr v1.4.4 // 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/jsonpointer v1.0.0 // indirect
|
||||
github.com/go-openapi/jsonreference v1.0.1 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/swag/conv v0.29.1 // indirect
|
||||
github.com/go-openapi/swag/jsonutils v0.29.1 // indirect
|
||||
github.com/go-openapi/swag/loading v0.29.1 // indirect
|
||||
github.com/go-openapi/swag/pools v0.29.1 // indirect
|
||||
github.com/go-openapi/swag/stringutils v0.29.1 // indirect
|
||||
github.com/go-openapi/swag/typeutils v0.29.1 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.29.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.1 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 // 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.28.0 // 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.9.2 // 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.19.1 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 // 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/mailru/easyjson v0.9.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
||||
github.com/mattn/go-isatty v0.0.24 // 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.4.3 // 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.21.0 // indirect
|
||||
github.com/robertkrimen/otto v0.5.1 // indirect
|
||||
github.com/rogpeppe/go-internal v1.15.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
|
||||
github.com/yuin/gopher-lua v1.1.2 // 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.39.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.39.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
|
||||
go.uber.org/atomic v1.10.0 // indirect
|
||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.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.44.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.44.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.10.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.46.0 // indirect
|
||||
golang.org/x/net v0.48.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
golang.org/x/time v0.10.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.28.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/net v0.58.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/term v0.45.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.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.140.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
+184
-159
@@ -1,86 +1,100 @@
|
||||
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=
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/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=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
|
||||
github.com/emicklei/go-restful/v3 v3.13.0/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.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78=
|
||||
github.com/fxamacker/cbor/v2 v2.9.2/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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
|
||||
github.com/go-logr/logr v1.4.4/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=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e h1:auobAirzhPsLHMso0NVMqK0QunuLDYCK83KnaVUM/RU=
|
||||
github.com/go-openapi/spec v0.21.1-0.20250328170532-a3928469592e/go.mod h1:NAKTe9SplQBxIUlHlsuId1jk1I7bWTVV/2q/GtdRi6g=
|
||||
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
|
||||
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
|
||||
github.com/go-openapi/jsonreference v1.0.1 h1:4zJ7AmYDKNmD3aSpfPnFNCFA5E80/xMHUNKgydaLh38=
|
||||
github.com/go-openapi/jsonreference v1.0.1/go.mod h1:dYplQXa6p5lXprLcJ8LE2iU7vNpXsAHDQ5ZAgL+Qx3A=
|
||||
github.com/go-openapi/spec v1.0.0 h1:JtB/GHOj+eetjse6YvxqLze88oEekl/4uPBethvzRrA=
|
||||
github.com/go-openapi/spec v1.0.0/go.mod h1:boj1PRhqS0x5jylgcNp9BRWxenphrh7vVNYZ90IRCoo=
|
||||
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-openapi/swag/conv v0.29.1 h1:AC4Eh/5c/eUDOUCzzsRC9ghmFgOSBHeRMGIngY0ZUGA=
|
||||
github.com/go-openapi/swag/conv v0.29.1/go.mod h1:S1X7/ZrBEZOC0Wc8AGxjbcGS92l3WEjA7aPtpl+RaqM=
|
||||
github.com/go-openapi/swag/jsonutils v0.29.1 h1:AFCxs0eQZ24/QyfhVHM2t49rMz7Vv3XCsZQI6yrNy+c=
|
||||
github.com/go-openapi/swag/jsonutils v0.29.1/go.mod h1:u3+sCfJpttDpcmS5kpm0yxL6GK0eWgODsx8Yw8fcqNM=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1 h1:BiiXE31Bx9SfpsMmOQj5KYpUhTZBpLVriVhJDuLuY2o=
|
||||
github.com/go-openapi/swag/jsonutils/fixtures_test v0.29.1/go.mod h1:julgTUKZ9/D0j6O7GKajmRs+812FWxQg/mMpGunWSjg=
|
||||
github.com/go-openapi/swag/loading v0.29.1 h1:FCv5fG8UhTdDJa2R7w+5O9Ekpcbw7tt0nFWvmDKGBjc=
|
||||
github.com/go-openapi/swag/loading v0.29.1/go.mod h1:N0ESuem4p2oedKal8EJhciqnJ9Q9Wmt83L1CRB3Fouw=
|
||||
github.com/go-openapi/swag/pools v0.29.1 h1:NRogYxdEW9SjRM4mkAOji9iefO4MRXq3p/ZJcoQbUKg=
|
||||
github.com/go-openapi/swag/pools v0.29.1/go.mod h1:leDcaghjkRAhCuCRv9NfJU5f0mjoU3cT/XZObhMk3pc=
|
||||
github.com/go-openapi/swag/stringutils v0.29.1 h1:1ykunK7iJQk1uOO7+oUH1ukbsK85fFCOiCFMOVSY+F0=
|
||||
github.com/go-openapi/swag/stringutils v0.29.1/go.mod h1:7fSqZ+z8Qc0tOfAAK0jVa5qFGrnIlRi6n7NeGGrr1vc=
|
||||
github.com/go-openapi/swag/typeutils v0.29.1 h1:Nzv9nhnlLCRBPQqfOX+7lB6Guju370or8StT+lIOf6M=
|
||||
github.com/go-openapi/swag/typeutils v0.29.1/go.mod h1:hxpgDZJVBkBsi/d3MIUosafoFdE5exaQRmVp0zwu3YE=
|
||||
github.com/go-openapi/swag/yamlutils v0.29.1 h1:69w3tsBajm7MR/fejLy7HD/3J68Ys1SeeZMEzZ3w2sk=
|
||||
github.com/go-openapi/swag/yamlutils v0.29.1/go.mod h1:rgsp3vT/QdWzKwn43CigDwjOGIenPyTZMKnxEM8jZOA=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.7.0 h1:wPW6YRgx3+SID1yUy/Xwa17L8kFEaEKod2VRbJDZNUs=
|
||||
github.com/go-openapi/testify/enable/yaml/v2 v2.7.0/go.mod h1:mI1M88etYbc3PhgHsWQK2kwvNwW5aGFqMPbmib+SGIs=
|
||||
github.com/go-openapi/testify/v2 v2.7.0 h1:bycOreEj6wfBvijg3YFogZ/sFjTCDmQnwSodSzHa3X8=
|
||||
github.com/go-openapi/testify/v2 v2.7.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
|
||||
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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
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/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.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c=
|
||||
github.com/google/gnostic-models v0.7.1/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-20260709232956-b9395ee17fa0 h1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw=
|
||||
github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI=
|
||||
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.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c=
|
||||
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 +105,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.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
|
||||
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
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 +116,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.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||
github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
|
||||
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
|
||||
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=
|
||||
@@ -112,30 +128,28 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
|
||||
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
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=
|
||||
@@ -148,10 +162,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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
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/robertkrimen/otto v0.5.1 h1:avDI4ToRk8k1hppLdYFTuuzND41n37vPGJU7547dGf0=
|
||||
github.com/robertkrimen/otto v0.5.1/go.mod h1:bS433I4Q9p+E5pZLu7r17vP6FkE6/wLxBdmKjoqJXF8=
|
||||
github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
|
||||
github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||
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=
|
||||
@@ -161,76 +177,82 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
||||
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/yuin/gopher-lua v1.1.2 h1:yF/FjE3hD65tBbt0VXLE13HWS9h34fdzJmrWRXwobGA=
|
||||
github.com/yuin/gopher-lua v1.1.2/go.mod h1:7aRmXIWl37SqRf0koeyylBEzJ+aPt8A+mmkQ4f1ntR8=
|
||||
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=
|
||||
github.com/zeromicro/go-zero v1.10.3 h1:fm4+jUuUF77IWtFeAyf2xVoBRcgEpF1NZJUqTvZ3dw0=
|
||||
github.com/zeromicro/go-zero v1.10.3/go.mod h1:Gnac2bT/JGb9Ja79wchssVeYtJxuWWzL98DuLH11kds=
|
||||
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.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
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.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
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=
|
||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A=
|
||||
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.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
|
||||
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.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||
go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
|
||||
go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
|
||||
go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
|
||||
go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
|
||||
go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
|
||||
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=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||
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.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
|
||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
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.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
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=
|
||||
@@ -239,78 +261,81 @@ 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.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||
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/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
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.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
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.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
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.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
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.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
|
||||
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
|
||||
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=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
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-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||
google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU=
|
||||
google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8=
|
||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||
google.golang.org/protobuf v1.36.12/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.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo=
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.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.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
|
||||
k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
|
||||
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-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
|
||||
k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg=
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/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.4.2 h1:qdOxHwrl2Kaag1aQEarlYcOA9vSyGCp3CIki3aW8c4Q=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.4.2/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=
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// BuildVersion is the version of goctl.
|
||||
const BuildVersion = "1.11.0"
|
||||
const BuildVersion = "1.10.2"
|
||||
|
||||
var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-beta": 2, "beta": 3, "released": 4, "": 5}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ OPTIONS:
|
||||
|
||||
```
|
||||
|
||||
示例用法请参考[用法](./example/generator.sh)
|
||||
示例用法请参考[用法](./example/makefile)
|
||||
|
||||
> NOTE: goctl model mysql ddl/datasource 均新增了一个`--style`参数,用于标记文件命名风格。
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ fromDDLWithoutCache:
|
||||
|
||||
|
||||
# generate model with cache from data source
|
||||
user=app_user
|
||||
password=PLO75FbcfmFYRuQEGmygZ9PyQCQbmgeD5
|
||||
datasource=k8s-istiosys-unifydev-2cdcebd306-b64e09be84220820.elb.ap-southeast-1.amazonaws.com:3306
|
||||
database=db_fiat
|
||||
user=root
|
||||
password=password
|
||||
datasource=127.0.0.1:3306
|
||||
database=gozero
|
||||
|
||||
fromDataSource:
|
||||
goctl template clean
|
||||
goctl model mysql datasource -url="$(user):$(password)@tcp($(datasource))/$(database)" -table="biz_switch" -dir ./model/cache -c -style gozero --prefix gozero
|
||||
goctl model mysql datasource -url="$(user):$(password)@tcp($(datasource))/$(database)" -table="*" -dir ./model/cache -c -style gozero
|
||||
|
||||
Vendored
+1
-2
@@ -135,9 +135,8 @@ func readEnv(goctlHome string) *sortedmap.SortedMap {
|
||||
return nil
|
||||
}
|
||||
dataStr := string(data)
|
||||
lines := strings.Split(dataStr, "\n")
|
||||
sm := sortedmap.New()
|
||||
for _, line := range lines {
|
||||
for line := range strings.SplitSeq(dataStr, "\n") {
|
||||
_, _, err = sm.SetExpression(line)
|
||||
if err != nil {
|
||||
continue
|
||||
|
||||
@@ -178,7 +178,7 @@ func (p *printer) print(x reflect.Value) {
|
||||
p.printf("}")
|
||||
|
||||
case reflect.Slice:
|
||||
if s, ok := x.Interface().([]byte); ok {
|
||||
if s, ok := reflect.TypeAssert[[]byte](x); ok {
|
||||
p.printf("%#q", s)
|
||||
return
|
||||
}
|
||||
@@ -196,7 +196,7 @@ func (p *printer) print(x reflect.Value) {
|
||||
p.printf("}")
|
||||
|
||||
case reflect.Struct:
|
||||
if val, ok := x.Interface().(apitoken.Position); ok {
|
||||
if val, ok := reflect.TypeAssert[apitoken.Position](x); ok {
|
||||
p.printf("%s", val.String())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -349,6 +349,17 @@ func (a *Analyzer) fillTypes() error {
|
||||
case spec.DefineStruct:
|
||||
var members []spec.Member
|
||||
for _, member := range v.Members {
|
||||
if member.IsInline {
|
||||
tp, err := a.resolveInlineType(member.Type, map[string]bool{v.RawName: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
member.Type = tp
|
||||
members = append(members, member)
|
||||
continue
|
||||
}
|
||||
|
||||
switch v := member.Type.(type) {
|
||||
case spec.DefineStruct:
|
||||
tp, err := a.findDefinedType(v.RawName)
|
||||
@@ -371,6 +382,62 @@ func (a *Analyzer) fillTypes() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Analyzer) resolveInlineType(tp spec.Type, resolving map[string]bool) (spec.Type, error) {
|
||||
switch v := tp.(type) {
|
||||
case spec.DefineStruct:
|
||||
if resolving[v.RawName] {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
tp, err := a.findDefinedType(v.RawName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defined, ok := tp.(spec.DefineStruct)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("type %s is not a struct", v.RawName)
|
||||
}
|
||||
|
||||
resolving[v.RawName] = true
|
||||
defer delete(resolving, v.RawName)
|
||||
for i := range defined.Members {
|
||||
if !defined.Members[i].IsInline {
|
||||
continue
|
||||
}
|
||||
|
||||
resolved, err := a.resolveInlineType(defined.Members[i].Type, resolving)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defined.Members[i].Type = resolved
|
||||
}
|
||||
return defined, nil
|
||||
case spec.NestedStruct:
|
||||
for i := range v.Members {
|
||||
if !v.Members[i].IsInline {
|
||||
continue
|
||||
}
|
||||
|
||||
resolved, err := a.resolveInlineType(v.Members[i].Type, resolving)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Members[i].Type = resolved
|
||||
}
|
||||
return v, nil
|
||||
case spec.PointerType:
|
||||
resolved, err := a.resolveInlineType(v.Type, resolving)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Type = resolved
|
||||
return v, nil
|
||||
default:
|
||||
return tp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Analyzer) fillTypeExpr(expr *ast.TypeExpr) error {
|
||||
head, _ := expr.CommentGroup()
|
||||
switch val := expr.DataType.(type) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zeromicro/go-zero/tools/goctl/api/spec"
|
||||
)
|
||||
|
||||
const inlineTagAPI = `
|
||||
syntax = "v1"
|
||||
|
||||
type (
|
||||
Auth {
|
||||
Token string ` + "`header:\"Authorization\"`" + `
|
||||
}
|
||||
Middle {
|
||||
Auth
|
||||
}
|
||||
PointerRequest {
|
||||
*Auth
|
||||
}
|
||||
NestedRequest {
|
||||
Middle
|
||||
}
|
||||
RecursiveRequest {
|
||||
Token string ` + "`header:\"X-Token\"`" + `
|
||||
*RecursiveRequest
|
||||
}
|
||||
)
|
||||
|
||||
service test-api {
|
||||
@handler Pointer
|
||||
get /pointer (PointerRequest)
|
||||
|
||||
@handler Nested
|
||||
get /nested (NestedRequest)
|
||||
|
||||
@handler Recursive
|
||||
get /recursive (RecursiveRequest)
|
||||
}
|
||||
`
|
||||
|
||||
func TestParseResolvesInlineTypesForTagLookup(t *testing.T) {
|
||||
apiSpec, err := Parse("inline.api", inlineTagAPI)
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, name := range []string{"PointerRequest", "NestedRequest", "RecursiveRequest"} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tp := findStructByName(t, apiSpec.Types, name)
|
||||
require.NotEmpty(t, tp.GetTagMembers("header"))
|
||||
require.Empty(t, tp.GetTagMembers("path"))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func findStructByName(t *testing.T, types []spec.Type, name string) spec.DefineStruct {
|
||||
t.Helper()
|
||||
for _, tp := range types {
|
||||
if tp.Name() == name {
|
||||
defined, ok := tp.(spec.DefineStruct)
|
||||
require.True(t, ok)
|
||||
return defined
|
||||
}
|
||||
}
|
||||
|
||||
t.Fatalf("type %s not found", name)
|
||||
return spec.DefineStruct{}
|
||||
}
|
||||
@@ -1172,12 +1172,12 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
|
||||
|
||||
slashTok := p.curTok
|
||||
var pathText = slashTok.Text
|
||||
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
||||
if !p.advanceIfPeekTokenIs(token.IDENT, token.SUB) {
|
||||
return nil
|
||||
}
|
||||
|
||||
pathText += p.curTok.Text
|
||||
if p.peekTokenIs(token.SUB) { // parse abc-efg format
|
||||
if p.peekTokenIs(token.SUB) && p.curTokenIs(token.IDENT) { // parse abc-efg format
|
||||
if !p.nextToken() {
|
||||
return nil
|
||||
}
|
||||
@@ -1303,12 +1303,21 @@ func (p *Parser) parseAtServerKVExpression() *ast.KVExpr {
|
||||
slashTok := p.curTok
|
||||
var pathText = valueTok.Text
|
||||
pathText += slashTok.Text
|
||||
if !p.advanceIfPeekTokenIs(token.IDENT) {
|
||||
// Allow a trailing slash at the end of an @server path value.
|
||||
if p.peekTok.Line() > p.curTok.Line() || p.peekTokenIs(token.RPAREN) {
|
||||
valueTok = token.Token{
|
||||
Text: pathText,
|
||||
Position: valueTok.Position,
|
||||
}
|
||||
leadingCommentGroup = p.curTokenNode().LeadingCommentGroup
|
||||
break
|
||||
}
|
||||
if !p.advanceIfPeekTokenIs(token.IDENT, token.SUB) {
|
||||
return nil
|
||||
}
|
||||
|
||||
pathText += p.curTok.Text
|
||||
if p.peekTokenIs(token.SUB) { // parse abc-efg format
|
||||
if p.peekTokenIs(token.SUB) && p.curTokenIs(token.IDENT) { // parse abc-efg format
|
||||
if !p.nextToken() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -306,6 +306,10 @@ func TestParser_Parse_atServerStmt(t *testing.T) {
|
||||
"prefix2": "v1/v2_test/v2-beta",
|
||||
"prefix3": "v1/v2_",
|
||||
"prefix4": "a-b-c",
|
||||
"prefix5": "/-",
|
||||
"prefix6": "/-/",
|
||||
"prefix7": "/abc/",
|
||||
"prefix8": "/comment/",
|
||||
"summary": `"test"`,
|
||||
"key": `"bar"`,
|
||||
}
|
||||
@@ -359,6 +363,8 @@ func TestParser_Parse_atServerStmt(t *testing.T) {
|
||||
`@server(foo: m1,`,
|
||||
`@server(foo: m1,)`,
|
||||
`@server(foo: v1/v2-)`,
|
||||
`@server(prefix:/--)`,
|
||||
`@server(prefix:/-abc)`,
|
||||
`@server(foo:"test")`,
|
||||
}
|
||||
for _, v := range testData {
|
||||
|
||||
@@ -18,6 +18,10 @@
|
||||
prefix2: v1/v2_test/v2-beta
|
||||
prefix3: v1/v2_
|
||||
prefix4: a-b-c
|
||||
prefix5: /-
|
||||
prefix6: /-/
|
||||
prefix7: /abc/
|
||||
prefix8: /comment/ // trailing slash before a comment and the next key
|
||||
summary:"test"
|
||||
key:"bar"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# goctl
|
||||
|
||||
[English](readme.md) | 简体中文
|
||||
[English](readme.md) | 简体中文 | [한국어](readme-ko.md)
|
||||
|
||||
goctl 使用见文档 https://go-zero.dev/docs/tutorials/cli/overview
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# goctl
|
||||
|
||||
[English](readme.md) | [简体中文](readme-cn.md) | 한국어
|
||||
|
||||
다음 문서에서 확인하세요: https://go-zero.dev/docs/tutorials/cli/overview
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 变更日志
|
||||
|
||||
[English](CHANGELOG.md) | 中文 | [한국어](CHANGELOG-ko.md)
|
||||
|
||||
## 未发布
|
||||
|
||||
### 新功能
|
||||
@@ -40,7 +42,7 @@
|
||||
- `generator/typeref.go` — 新文件,核心类型解析引擎:
|
||||
- `resolveRPCTypeRef()` — 将 proto RPC 类型(简单类型、同包点号类型、跨包点号类型、Google WKT)解析为带正确导入路径的 Go 类型引用。
|
||||
- `resolveCallTypeRef()` — 客户端代码生成变体,支持类型别名。
|
||||
- `googleWKTTable` — 全部 16 种 Google 知名类型到 Go 等价类型的映射表。
|
||||
- `googleWKTTable` — 支持的 Google 知名类型到 Go 等价类型的映射表。
|
||||
- `generator/genserver.go` — `genFunctions()` 调用 `resolveRPCTypeRef()` 解析请求/响应类型并收集额外导入路径。
|
||||
- `generator/genlogic.go` — `genLogicFunction()` 使用 `resolveRPCTypeRef()`;新增 `addLogicImports()` 按需添加主 pb 导入和跨包导入。
|
||||
- `generator/gencall.go` — `genFunction()` 和 `getInterfaceFuncs()` 使用 `resolveCallTypeRef()` 处理类型别名和额外导入;新增 `buildExtraImportLines()` 辅助函数。
|
||||
@@ -67,7 +69,7 @@
|
||||
Google protobuf 知名类型现在可以直接用作 RPC 的请求/响应类型(而不仅仅是消息字段)。
|
||||
|
||||
**涉及文件:**
|
||||
- `generator/typeref.go` — `resolveGoogleWKT()` + `googleWKTTable` 处理所有标准类型。
|
||||
- `generator/typeref.go` — `resolveGoogleWKT()` + `googleWKTTable` 处理支持的标准类型。
|
||||
|
||||
**前后对比:**
|
||||
|
||||
|
||||
@@ -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`을 이름을 변경하고 업데이트하여 점 표기 타입이 이제 성공적으로 파싱되는지 검증합니다.
|
||||
@@ -1,5 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
English | [中文](CHANGELOG-cn.md) | [한국어](CHANGELOG-ko.md)
|
||||
|
||||
## Unreleased
|
||||
|
||||
### New Features
|
||||
@@ -40,7 +42,7 @@ When an imported proto has a **different** `go_package` from the main proto, goc
|
||||
- `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 all 16 Google well-known types to their Go equivalents.
|
||||
- `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.
|
||||
@@ -67,7 +69,7 @@ When an imported proto has a **different** `go_package` from the main proto, goc
|
||||
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 all standard types.
|
||||
- `generator/typeref.go` — `resolveGoogleWKT()` + `googleWKTTable` handles supported standard types.
|
||||
|
||||
**Before vs After:**
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# goctl rpc — RPC 代码生成
|
||||
|
||||
[English](README.md) | 中文
|
||||
[English](README.md) | 中文 | [한국어](README-ko.md)
|
||||
|
||||
goctl rpc 是 `goctl` 脚手架下的 RPC 服务代码生成模块,基于 `.proto` 文件生成完整的 zRPC 服务代码。你只需编写 proto 定义和业务逻辑,其余代码均由工具自动生成。
|
||||
|
||||
@@ -10,7 +10,7 @@ goctl rpc 是 `goctl` 脚手架下的 RPC 服务代码生成模块,基于 `.pr
|
||||
- **外部 Proto 导入**:支持跨目录、跨包的 proto 导入,自动解析传递性依赖
|
||||
- **多服务模式**:单个 proto 文件中定义多个 service,按服务名自动分组
|
||||
- **流式支持**:支持服务端流、客户端流和双向流
|
||||
- **Google 标准类型**:自动识别 `google.protobuf.*` 类型并生成正确的 Go 导入
|
||||
- **Google 标准类型**:自动识别支持的 `google.protobuf.*` 类型并生成正确的 Go 导入
|
||||
- **客户端生成**:自动生成封装好的 RPC 客户端代码
|
||||
|
||||
## 前置条件
|
||||
@@ -311,5 +311,5 @@ goctl 自动识别并正确处理 Google protobuf 标准类型:
|
||||
| 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 | [标准类型作参数](example/09-google-types-as-rpc/) | Empty/Timestamp 作为 RPC 参数 |
|
||||
| 09 | [Google 标准类型作参数](example/09-google-types-as-rpc/) | Empty/Timestamp 作为 RPC 参数 |
|
||||
| 10 | [流式通信](example/10-streaming/) | 服务端/客户端/双向流 |
|
||||
|
||||
@@ -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/) | 서버/클라이언트/양방향 스트리밍 |
|
||||
@@ -1,6 +1,6 @@
|
||||
# goctl rpc — RPC Code Generation
|
||||
|
||||
English | [中文](README-cn.md)
|
||||
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.
|
||||
|
||||
@@ -10,7 +10,7 @@ goctl rpc is the RPC service code generation module of the `goctl` scaffold. It
|
||||
- **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 `google.protobuf.*` types with correct Go imports
|
||||
- **Google well-known types**: Automatic recognition of supported `google.protobuf.*` types with correct Go imports
|
||||
- **Client generation**: Auto-generated RPC client wrapper code
|
||||
|
||||
## Prerequisites
|
||||
@@ -311,5 +311,5 @@ See the [example/](example/) directory for 10 complete examples covering all gen
|
||||
| 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 types as params](example/09-google-types-as-rpc/) | Empty/Timestamp as RPC parameters |
|
||||
| 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,5 +1,7 @@
|
||||
# 示例 01:基础 RPC 服务
|
||||
|
||||
[English](README.md) | 中文 | [한국어](README-ko.md)
|
||||
|
||||
这是使用 goctl 生成 RPC 服务的最简单示例。
|
||||
|
||||
## Proto 定义
|
||||
|
||||
@@ -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`)을 수정하세요.
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 示例 02:导入同级 Proto 文件
|
||||
|
||||
[English](README.md) | 中文 | [한국어](README-ko.md)
|
||||
|
||||
本示例演示如何导入同一目录下的 proto 文件。
|
||||
|
||||
## Proto 定义
|
||||
|
||||
@@ -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이 해결합니다.
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 示例 03:导入子目录中的 Proto 文件
|
||||
|
||||
[English](README.md) | 中文 | [한국어](README-ko.md)
|
||||
|
||||
本示例演示如何导入子目录中的 proto 文件。
|
||||
|
||||
## 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`를 찾을 수 있게 합니다.
|
||||
@@ -1,5 +1,7 @@
|
||||
# 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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# 示例 04:传递性导入
|
||||
|
||||
[English](README.md) | 中文 | [한국어](README-ko.md)
|
||||
|
||||
本示例演示 proto 的传递性导入,即 A 导入 B,B 导入 C。
|
||||
|
||||
## Proto 定义
|
||||
@@ -65,7 +67,7 @@ output/
|
||||
|
||||
## 要点说明
|
||||
|
||||
- 三个 proto 文件(`base.proto` → `middleware.proto` → `main.proto`)形成传递导入链。
|
||||
- 三个 proto 文件(`main.proto` → `middleware.proto` → `base.proto`)形成传递导入链。
|
||||
- goctl 自动递归解析所有传递导入。
|
||||
- 三个文件共享相同的 `go_package = "example.com/demo/pb"`。
|
||||
- 只需指定入口 proto 文件,goctl 和 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 동작과 동일).
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user