From ced18b781aafa75ad32e10c16ccfbcb1f5a881ad Mon Sep 17 00:00:00 2001 From: what Date: Fri, 21 Aug 2026 09:31:45 +0800 Subject: [PATCH] =?UTF-8?q?init:=20=E6=9D=A1=E4=BB=B6=E6=B5=81=E5=BC=95?= =?UTF-8?q?=E6=93=8E=E5=88=9D=E5=A7=8B=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 11 + action.go | 121 ++++ condcase.go | 158 +++++ condflow.go | 210 +++++++ context.go | 115 ++++ go.mod | 75 +++ go.sum | 585 +++++++++++++++++ lookuper.go | 73 +++ tests/condflow_test.go | 309 +++++++++ tests/example_test.go | 591 ++++++++++++++++++ tests/fixtures/approve_executor.go | 55 ++ tests/fixtures/example_executor.go | 114 ++++ tests/fixtures/init_condflow_cache.gobak | 97 +++ tests/fixtures/init_condflow_case_cache.gobak | 207 ++++++ .../init_condflow_decision_cache.gobak | 211 +++++++ utils.go | 60 ++ 16 files changed, 2992 insertions(+) create mode 100644 .gitignore create mode 100644 action.go create mode 100644 condcase.go create mode 100644 condflow.go create mode 100644 context.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 lookuper.go create mode 100644 tests/condflow_test.go create mode 100644 tests/example_test.go create mode 100644 tests/fixtures/approve_executor.go create mode 100644 tests/fixtures/example_executor.go create mode 100644 tests/fixtures/init_condflow_cache.gobak create mode 100644 tests/fixtures/init_condflow_case_cache.gobak create mode 100644 tests/fixtures/init_condflow_decision_cache.gobak create mode 100644 utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f306a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +.DS_Store +go.work +CLAUDE.md +.claude/ diff --git a/action.go b/action.go new file mode 100644 index 0000000..2f52b06 --- /dev/null +++ b/action.go @@ -0,0 +1,121 @@ +package condflow + +import ( + "fmt" + "reflect" + + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/valuex" + "git.fsdpf.net/go/req" +) + +// Action 条件分支处理函数 +type Action func(ctx Context, cfg valuex.Accessor) (reflux.R, error) + +// ActionInvoker 调用器接口,用于管理和调用 Action 方法 +type ActionInvoker interface { + // Res 获取默认资源 + Res() req.Resource + + // GetActionResource 获取指定 action 的资源,如果未设置则返回默认资源 + GetActionResource(actionName string) req.Resource + + // GetAction 获取指定名称的 Action 函数 + GetAction(name string) (Action, error) + + // Call 调用指定名称的 Action 方法 + Call(ctx FlowContext, name string, cfg valuex.Accessor) (reflux.R, error) +} + +// actionInvoker ActionInvoker 的默认实现 +type actionInvoker struct { + defRes req.Resource // 默认资源 + actRes map[string]req.Resource // 每个 action 对应的资源 + executorValue reflect.Value // 缓存 Executor 反射值 +} + +// NewActionInvoker 创建新的 ActionInvoker +func NewActionInvoker(e Executor, actRes map[string]req.Resource) ActionInvoker { + if actRes == nil { + actRes = make(map[string]req.Resource) + } + return &actionInvoker{ + defRes: e.Res(), + actRes: actRes, + executorValue: reflect.ValueOf(e), + } +} + +// NewActionInvokerWithValue 使用 reflect.Value 创建新的 ActionInvoker +func NewActionInvokerWithValue(executorValue reflect.Value, actRes map[string]req.Resource) ActionInvoker { + if actRes == nil { + actRes = make(map[string]req.Resource) + } + + // 通过反射调用 Res() 方法获取默认资源 + resMethod := executorValue.MethodByName("Res") + if !resMethod.IsValid() { + panic("executor must have Res() method") + } + + results := resMethod.Call(nil) + defRes := results[0].Interface().(req.Resource) + + return &actionInvoker{ + defRes: defRes, + actRes: actRes, + executorValue: executorValue, + } +} + +// Res 获取默认资源 +func (ai *actionInvoker) Res() req.Resource { + return ai.defRes +} + +// Call 调用指定名称的 Action 方法 +func (ai *actionInvoker) Call(ctx FlowContext, name string, cfg valuex.Accessor) (reflux.R, error) { + action, err := ai.GetAction(name) + + if err != nil { + return nil, err + } + + return action(toActionContext(ctx, ai.GetActionResource(name)), cfg) +} + +// GetActionResource 获取指定 action 的资源,如果未设置则返回默认资源 +func (ai *actionInvoker) GetActionResource(actionName string) req.Resource { + if res, exists := ai.actRes[actionName]; exists { + return res + } + return ai.defRes +} + +// GetAction 通过反射获取 Executor 中指定名称的 Action 方法 +// - name: 方法名称,如 "AutoApproveAction" +func (ai *actionInvoker) GetAction(name string) (Action, error) { + // 使用缓存的 executorValue + method := ai.executorValue.MethodByName(name) + if !method.IsValid() { + return nil, fmt.Errorf("action method '%s' not found in '%s", name, ai.executorValue.Type().String()) + } + + // 获取方法类型信息 + methodType := method.Type() + + // 验证方法签名: func(Context, valuex.Accessor) (reflux.R, error) + // NumIn: 2 个参数 (不包括接收者) + // NumOut: 2 个返回值 + if methodType.NumIn() != 2 || methodType.NumOut() != 2 { + return nil, fmt.Errorf("action method '%s' has invalid signature, expected func(Context, valuex.Accessor) (reflux.R, error)", name) + } + + // 转换为 Action 类型 + action, ok := method.Interface().(func(ctx Context, cfg valuex.Accessor) (reflux.R, error)) + if !ok { + return nil, fmt.Errorf("failed to convert method '%s' to Action type", name) + } + + return action, nil +} diff --git a/condcase.go b/condcase.go new file mode 100644 index 0000000..172446e --- /dev/null +++ b/condcase.go @@ -0,0 +1,158 @@ +package condflow + +import ( + "strings" + + "git.fsdpf.net/go/condition" + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/fieldx" + "git.fsdpf.net/go/reflux/valuex" +) + +// CondCase 条件分支 +type CondCase struct { + // name 分支名称 + name string + + // priority 优先级(数值越大优先级越高) + priority int + + // action 执行动作标识 + // DeviceExecuter/LX2003C@Up + // DeviceExecuter/LX2003C@Down + action string + + // 执行参数 + actParams fieldx.Schema + + // 执行配置值 + actConfig fieldx.Schema + + // 条件判断 + cond *condition.Condition + + // 继续下一个 case 匹配 + nextCaseOnSuccess bool + + // 下一个 flow 匹配流 + nextFlowOnSuccess *CondFlow + + // 下一个 flow 匹配流 参数 + nextFlowParams fieldx.Schema +} + +func (cc *CondCase) Execute(ctx *flowContext) (err error) { + index := strings.SplitN(cc.action, "@", 2) + + invoker, err := ctx.GetActionInvoker(index[0]) + + if err != nil { + return err + } + + actCtx := ctx + if len(cc.actParams) > 0 { + data, err := cc.actParams.Generate(ctx) + if err != nil { + return err + } + actCtx = ctx.Fork(reflux.New(data)) + } + + var actCfg valuex.Accessor = valuex.Nil + if len(cc.actConfig) > 0 { + if data, err := cc.actConfig.Generate(ctx); err == nil { + actCfg = reflux.New(data) + } + } + + result, err := invoker.Call(actCtx, index[1], actCfg) + + if err != nil { + return err + } + + ctx.PushResult(invoker.Res().GetCode(), result) + + if cc.nextFlowOnSuccess != nil { + if len(cc.nextFlowParams) > 0 { + data, err := cc.nextFlowParams.Generate(ctx) + + if err != nil { + return err + } + + ctx = ctx.Fork(reflux.New(data)) + } + + err = cc.nextFlowOnSuccess.Execute(ctx) + } + + return err +} + +// NewCondCase 创建新的条件分支 +func NewCondCase(name string, cond *condition.Condition, action string) *CondCase { + return &CondCase{ + name: name, + priority: 0, + cond: cond, + action: action, + } +} + +// WithPriority 设置优先级 +func (c *CondCase) WithPriority(priority int) *CondCase { + c.priority = priority + return c +} + +// WithNextCaseOnSuccess 设置成功后继续执行下一个 case +func (c *CondCase) WithNextCaseOnSuccess(next bool) *CondCase { + c.nextCaseOnSuccess = next + return c +} + +// WithActionParams 设置 Action 执行参数 +func (c *CondCase) WithActionParams(params fieldx.Schema) *CondCase { + c.actParams = params + return c +} + +// WithActionConfig 设置 Action 执行配置 +func (c *CondCase) WithActionConfig(config fieldx.Schema) *CondCase { + c.actConfig = config + return c +} + +// WithNextFlowOnSuccess 设置成功后执行的下一个流 +func (c *CondCase) WithNextFlowOnSuccess(flow *CondFlow, params fieldx.Schema) *CondCase { + c.nextFlowOnSuccess = flow + c.nextFlowParams = params + return c +} + +// Name 获取分支名称 +func (c *CondCase) Name() string { + return c.name +} + +// Priority 获取优先级 +func (c *CondCase) Priority() int { + return c.priority +} + +// Condition 获取条件 +func (c *CondCase) Condition() *condition.Condition { + return c.cond +} + +// NextCaseOnSuccess 获取是否继续下一个 case +func (c *CondCase) NextCaseOnSuccess() bool { + return c.nextCaseOnSuccess +} + +// NextFlowOnSuccess 获取下一个流 +func (c *CondCase) NextFlowOnSuccess() *CondFlow { + return c.nextFlowOnSuccess +} diff --git a/condflow.go b/condflow.go new file mode 100644 index 0000000..b70957e --- /dev/null +++ b/condflow.go @@ -0,0 +1,210 @@ +package condflow + +import ( + "database/sql" + "fmt" + "sort" + "strconv" + + "git.fsdpf.net/go/contracts" + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/valuex" + "git.fsdpf.net/go/req" + "github.com/samber/do/v2" + + "git.fsdpf.net/go/db" + sqlite3dialect "git.fsdpf.net/go/db/dialect/sqlite3" +) + +type Executor interface { + Res() req.Resource + // Call(ctx Context, cfg valuex.Accessor) (reflux.R, error) +} + +// Middleware 条件流中间件,用于在执行流程前后扩展处理逻辑 +type Middleware func(ctx Context, cfg valuex.Accessor) error + +// CondFlow 条件流服务 +type CondFlow struct { + // name 条件流名称 + name string + + // cases 条件分支列表 + cases []*CondCase + + // casesDirty 标记条件分支列表是否已变更,用于延迟排序 + casesDirty bool + + // relations 关联数据, 通过 Orm 查询 + relations []contracts.Orm +} + +var conn *db.Database + +func init() { + if dialect, err := sql.Open(sqlite3dialect.DriverWithIF, ":memory:"); err == nil { + conn = db.New("sqlite3", dialect) + } +} + +// New 创建新的条件流实例 +func New(name string) *CondFlow { + return &CondFlow{ + name: name, + cases: make([]*CondCase, 0), + } +} + +// AddCase 添加条件分支 +func (cf *CondFlow) AddCase(c *CondCase) *CondFlow { + cf.cases = append(cf.cases, c) + cf.casesDirty = true + return cf +} + +// Execute 执行条件流 +// ctx: 执行上下文 +// 按照优先级顺序执行匹配的条件分支 +func (cf *CondFlow) Execute(ctx *flowContext) error { + + cases, err := cf.GetValidCondCases(ctx) + + if err != nil { + return err + } + + for _, ca := range cases { + if err := ca.Execute(ctx); err != nil { + return err + } + if !ca.nextCaseOnSuccess { + break + } + } + + return nil +} + +// Run 使用输入数据和用户信息执行条件流 +// +// - input 初始输入参数 +// - user 当前用户信息 +func (cf *CondFlow) Run(app do.Injector, input reflux.R, user req.User) error { + ctx := NewFlowContext(app, input, user) + return cf.Execute(ctx) +} + +// GetValidCondCases 获取有效的条件分支 +func (cf *CondFlow) GetValidCondCases(ctx *flowContext) ([]*CondCase, error) { + input := reflux.New(ctx.Input()) + + sd, err := cf.makeTable(input) + + if err != nil { + return nil, err + } + + cases := cf.getSortedCases() + + cols := []any{} + for i, cs := range cases { + cols = append(cols, db.Func("IF", cs.cond.ToSql(ctx), 1, 0).As(strconv.Itoa(i))) + } + + if len(cols) == 0 { + return nil, nil + } + + fmt.Println(sd.Select(cols...).Executor().ToSQL()) + + result, err := sd.Select(cols...).Executor().GetRecord() + + if err != nil { + return nil, err + } + + items := []*CondCase{} + + for k, v := range result { + if ptr, ok := v.(*any); ok { + v = *ptr + } + val, ok := v.(int32) + if !ok { + continue + } + if val == 1 { + if n, err := strconv.Atoi(k); err == nil { + items = append(items, cases[n]) + } else { + return items, err + } + } + } + + return items, nil +} + +// makeTable 生成条件判断数据表 +func (cf *CondFlow) makeTable(input reflux.R) (*db.SelectDataset, error) { + tables := []db.Expression{db.T("Flow"), db.T("Flow").As("@")} + + sd := conn.Select().With("Flow", cf.makeCTE(input)) + + for _, re := range cf.relations { + result, err := re.Execute(contracts.OrmShow) + if err != nil { + return nil, err + } + t := re.GetModel().GetCode() + tables = append(tables, db.T(t)) + sd = sd.With(t, cf.makeCTE(result)) + } + + return sd.From(tables), nil +} + +// makeCTE 将 reflux.R 数据转换为数据库表达式 +func (cf *CondFlow) makeCTE(src reflux.R) db.Expression { + items := []db.Expression{} + + for _, k := range src.Keys() { + v := src.Get(k) + sqliteType := kindToSQLite3Type(v.Raw().Kind()) + + // 根据类型决定使用的值 + var value any + switch sqliteType { + case "TEXT": + value = v.String() + default: + value = v.Any() + } + + items = append(items, db.Cast(db.V(value), sqliteType).As(k)) + } + + return db.Select(items) +} + +// getSortedCases 获取按优先级排序的条件分支 +func (cf *CondFlow) getSortedCases() []*CondCase { + if cf.casesDirty == true { + cf.casesDirty = false + sort.Slice(cf.cases, func(i, j int) bool { + return cf.cases[i].priority > cf.cases[j].priority + }) + } + + return cf.cases +} + +// Name 获取条件流名称 +func (cf *CondFlow) Name() string { + return cf.name +} + +// CaseCount 获取条件分支数量 +func (cf *CondFlow) CaseCount() int { + return len(cf.cases) +} diff --git a/context.go b/context.go new file mode 100644 index 0000000..0c46741 --- /dev/null +++ b/context.go @@ -0,0 +1,115 @@ +package condflow + +import ( + "strings" + + "git.fsdpf.net/go/reflux/valuex" + "git.fsdpf.net/go/req" + "github.com/samber/do/v2" +) + +const inputKey = "input" + +// FlowContext 流程执行上下文接口 +// 用于在执行器方法调用时传递上下文信息 +type FlowContext interface { + Get(name string) valuex.Accessor + + // GetParam, Get 的别名 + GetParam(key string) valuex.Accessor + + // Input 获取初始输入数据 + Input() valuex.Accessor + + // LastResult 获取上一个 PushResult 的值 + LastResult() valuex.Accessor + + // User 获取当前用户信息 + User() req.User +} + +// Context 执行器调用上下文接口,包含资源信息 +type Context interface { + FlowContext + Res() req.Resource +} + +// flowContext 执行器上下文实现 +type flowContext struct { + lookuper + + // user 用户信息 + user req.User + + // 执行动作容器 + executors do.Injector +} + +type context struct { + FlowContext + res req.Resource +} + +// NewFlowContext 创建新的执行器上下文 +// input: 初始传入参数 +// user: 用户信息 +func NewFlowContext(app do.Injector, input valuex.Accessor, user req.User) *flowContext { + return &flowContext{ + lookuper: lookuper{ + results: []result{ + { + key: inputKey, + value: input, + }, + }, + }, + executors: app, + user: user, + } +} + +// User 获取当前用户信息 +func (c *flowContext) User() req.User { + return c.user +} + +func (c *flowContext) Get(key string) valuex.Accessor { + if v, ok := c.Lookup(key); ok { + return v + } + return valuex.Nil +} + +func (c *flowContext) GetParam(key string) valuex.Accessor { + return c.Get(key) +} + +func (c *flowContext) GetActionInvoker(key string) (ActionInvoker, error) { + index := strings.SplitN(key, "@", 2) + invoker, err := do.InvokeNamed[ActionInvoker](c.executors, index[0]) + if err != nil { + return nil, err + } + return invoker, nil +} + +// Fork 基于当前上下文创建新的上下文,使用新的 input +// 新上下文会继承当前上下文的 user 和 executors,但使用新的 input 和空的 results +func (c *flowContext) Fork(input valuex.Accessor) *flowContext { + return &flowContext{ + lookuper: lookuper{ + results: []result{ + { + key: inputKey, + value: input, + }, + }, + }, + executors: c.executors, + user: c.user, + } +} + +func (ac *context) Res() req.Resource { + return ac.res +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..fd5b3ad --- /dev/null +++ b/go.mod @@ -0,0 +1,75 @@ +module git.fsdpf.net/go/condflow + +go 1.25.5 + +replace ( + git.fsdpf.net/go/condition => ../condition-v2 + git.fsdpf.net/go/contracts => ../contracts-v2 + git.fsdpf.net/go/db => ../db-v2 + git.fsdpf.net/go/orm => ../orm-v2 + git.fsdpf.net/go/reflux => ../reflux + git.fsdpf.net/go/req => ../req-v2 +) + +require ( + git.fsdpf.net/go/condition v0.0.0-00010101000000-000000000000 + git.fsdpf.net/go/contracts v0.0.0-00010101000000-000000000000 + git.fsdpf.net/go/db v0.0.0-00010101000000-000000000000 + git.fsdpf.net/go/reflux v0.0.0-00010101000000-000000000000 + git.fsdpf.net/go/req v0.0.0-00010101000000-000000000000 + github.com/samber/do/v2 v2.0.0 + github.com/stretchr/testify v1.11.1 +) + +require ( + git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff // indirect + github.com/bufbuild/protocompile v0.4.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-chi/chi/v5 v5.0.12 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/jhump/protoreflect v1.15.1 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.1 // indirect + github.com/lestrrat-go/jwx v1.2.25 // indirect + github.com/lestrrat-go/option v1.0.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mark3labs/mcp-go v0.54.1 // indirect + github.com/mattn/go-sqlite3 v1.14.42 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/samber/go-type-to-string v1.8.0 // indirect + github.com/samber/lo v1.49.1 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/sashabaranov/go-openai v1.41.2 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.15.0 // indirect + github.com/subosito/gotenv v1.4.2 // indirect + github.com/tidwall/gjson v1.17.1 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect + google.golang.org/grpc v1.69.2 // indirect + google.golang.org/protobuf v1.36.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..afd1858 --- /dev/null +++ b/go.sum @@ -0,0 +1,585 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff h1:1hokQGKGIstoI7WDBir2N24OGgnussZ3GmucK4TQRuI= +git.fsdpf.net/go/utils v0.0.0-20240509025914-c03a9cb48aff/go.mod h1:NUoyQtFr905YT+pi850mvSf4YX0WghQIcMQkTvize5o= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= +github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= +github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= +github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= +github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.25 h1:tAx93jN2SdPvFn08fHNAhqFJazn5mBBOB8Zli0g0otA= +github.com/lestrrat-go/jwx v1.2.25/go.mod h1:zoNuZymNl5lgdcu6P7K6ie2QRll5HVfF4xwxBBK1NxY= +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= +github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mark3labs/mcp-go v0.54.1 h1:Ap/ptEB9FtWzFKM8NDsTA7QDxerQOC06eZigrTldVj0= +github.com/mark3labs/mcp-go v0.54.1/go.mod h1:+8WclSK1ZUweCP3hvktSji8n8ABG/95QaEkeVE/Uwas= +github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= +github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +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/samber/do/v2 v2.0.0 h1:tnunwWaoqSfJ9hxVIaJawIo7JXHQlqT9d9YBXlE9Keg= +github.com/samber/do/v2 v2.0.0/go.mod h1:ZSBCE7Xr6nTNIOVo4DBrkl2+ydUbIOzJjjdV8En5XO4= +github.com/samber/go-type-to-string v1.8.0 h1:5z6tDTjtXxkIAoAuHAZYMYR8mkBZjVgeSH7jcSLqc8w= +github.com/samber/go-type-to-string v1.8.0/go.mod h1:jpU77vIDoIxkahknKDoEx9C8bQ1ADnh2sotZ8I4QqBU= +github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= +github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sashabaranov/go-openai v1.41.2 h1:vfPRBZNMpnqu8ELsclWcAvF19lDNgh1t6TVfFFOPiSM= +github.com/sashabaranov/go-openai v1.41.2/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +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/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= +github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +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.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= +google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/lookuper.go b/lookuper.go new file mode 100644 index 0000000..933c954 --- /dev/null +++ b/lookuper.go @@ -0,0 +1,73 @@ +package condflow + +import ( + "strings" + + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/valuex" +) + +type result struct { + key string + value valuex.Accessor +} + +// lookuper 从 flowContext 独立出来的查找器实现 +// 用于为 valuex.Schema 提供数据源 +type lookuper struct { + // results 记录每一次 PushResult + results []result +} + +// MustLookup 根据路径查找并直接返回对应值的访问器 +// 如果路径不存在,返回 Nil 访问器(所有方法返回零值) +func (c *lookuper) MustLookup(key string) valuex.Accessor { + if v, ok := c.Lookup(key); ok { + return v + } + return valuex.Nil +} + +// Lookup 根据路径查找并返回对应值的访问器 +// 支持点号分隔的路径查找,格式: "key.field" +// - 如果 key 不包含点号,从 Input 中查找 +// - 如果 key 包含点号,从 results 中从后往前查找匹配的 key +func (c *lookuper) Lookup(key string) (valuex.Accessor, bool) { + index := strings.SplitN(key, ".", 2) + + if len(index) == 2 { + // 先从结果记录中从后往前查找 + for i := len(c.results) - 1; i >= 0; i-- { + if c.results[i].key == index[0] { + return c.results[i].value.Lookup(index[1]) + } + } + } + + return c.Input().Lookup(key) +} + +// LastResult 获取上一个 PushResult 的值 +func (c *lookuper) LastResult() valuex.Accessor { + if len(c.results) == 0 { + return valuex.Nil + } + return c.results[len(c.results)-1].value +} + +// PushResult 设置上一个 Flow Handler 执行结果 +func (c *lookuper) PushResult(key string, value reflux.R) { + c.results = append(c.results, result{ + key: key, + value: value, + }) +} + +// Input 获取初始输入数据 +func (c *lookuper) Input() valuex.Accessor { + // input 总是存储在第一个位置 + if len(c.results) > 0 && c.results[0].key == inputKey { + return c.results[0].value + } + return nil +} diff --git a/tests/condflow_test.go b/tests/condflow_test.go new file mode 100644 index 0000000..555361a --- /dev/null +++ b/tests/condflow_test.go @@ -0,0 +1,309 @@ +package tests + +import ( + "fmt" + "testing" + + "git.fsdpf.net/go/condflow" + "git.fsdpf.net/go/condflow/tests/fixtures" + "git.fsdpf.net/go/condition" + "git.fsdpf.net/go/contracts/base" + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/fieldx" + "git.fsdpf.net/go/req" + "git.fsdpf.net/go/req/resx" + "github.com/samber/do/v2" + "github.com/stretchr/testify/suite" +) + +// CondFlowTestSuite 条件流测试套件 +type CondFlowTestSuite struct { + suite.Suite + app do.Injector + flow *condflow.CondFlow + executor condflow.ActionInvoker +} + +// SetupTest 在每个测试前运行 +func (s *CondFlowTestSuite) SetupTest() { + // 初始化 DI 容器 + s.app = do.New() + + // 创建 executor + approveRes := resx.New(s.app, "Approval", "approvals", + resx.WithUuid("550e8400-e29b-41d4-a716-446655440001"), + resx.WithName("审批"), + resx.WithFields( + resx.NewResField("id", "", resx.FieldWithName("ID"), resx.FieldWithDataType(req.ResInteger)), + resx.NewResField("created_user", "", resx.FieldWithName("创建者"), resx.FieldWithDefault("00000000-0000-0000-0000-000000000000"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("owned_user", "", resx.FieldWithName("拥有者"), resx.FieldWithDefault("00000000-0000-0000-0000-000000000000"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("created_at", "", resx.FieldWithName("创建时间"), resx.FieldWithDefault("sql:CURRENT_TIMESTAMP"), resx.FieldWithDataType(req.ResTimestamp)), + resx.NewResField("updated_at", "", resx.FieldWithName("更新时间"), resx.FieldWithDefault("sql:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), resx.FieldWithDataType(req.ResTimestamp)), + resx.NewResField("uuid", "", resx.FieldWithName("UUID"), resx.FieldWithDefault("sql:uuid()"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("status", "", resx.FieldWithName("状态"), resx.FieldWithDefault("pending"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("amount", "", resx.FieldWithName("审批金额"), resx.FieldWithDefault("0"), resx.FieldWithDataType(req.ResDecimal)), + resx.NewResField("approver_role", "", resx.FieldWithName("审批人角色"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("approval_method", "", resx.FieldWithName("审批方式"), resx.FieldWithDataType(req.ResString)), + ), + ) + + s.executor = condflow.NewActionInvoker(fixtures.NewApproveExecutor(approveRes), nil) + + do.ProvideNamedValue(s.app, fmt.Sprintf("%s/ApproveExecutor", s.executor.Res().GetCode()), s.executor) + + // 通过字符串验证预期的 action 是否注册成功 + expectedActions := []string{ + "AutoApprove", + "Manager", + "Director", + "Reject", + } + + for _, actionName := range expectedActions { + _, err := s.executor.GetAction(actionName) + s.Require().NoError(err, "获取 action '%s' 失败", actionName) + } + + // 创建条件流 + s.flow = condflow.New("test-flow") +} + +// TearDownTest 在每个测试后运行 +func (s *CondFlowTestSuite) TearDownTest() { + if s.app != nil { + s.app.Shutdown() + } +} + +// TestSmallAmountAutoApproval 测试小额自动审批 +func (s *CondFlowTestSuite) TestSmallAmountAutoApproval() { + // 创建条件: 金额 <= 1000 + cond := condition.New(condition.Describe("小额自动审批")) + cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("1000", condition.STRING))) + + config, _ := fieldx.SchemaFromMap(map[string]any{ + "notify": map[string]any{ + "type": "string", + "value": "true", + }, + "message": map[string]any{ + "type": "string", + "value": "申请已自动通过", + }, + }) + + // 创建条件分支,使用 action 名称字符串 + condCase := condflow.NewCondCase("auto-approve-small", cond, "Approval/ApproveExecutor@AutoApprove"). + WithPriority(100). + WithActionConfig(config) + + // 添加到流程 + s.flow.AddCase(condCase) + + // 准备输入数据 + input := reflux.New(map[string]any{ + "amount": 500.0, + "user": map[string]any{ + "id": 1, + "credit_score": 80, + }, + }) + + // 执行流程 + err := s.flow.Run(s.app, input, base.GetSystemUser()) + s.NoError(err) +} + +// TestManagerApproval 测试经理审批 +func (s *CondFlowTestSuite) TestManagerApproval() { + // 创建条件: 1000 < 金额 <= 10000 + cond := condition.New(condition.Describe("经理审批")) + cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("1000", condition.STRING))). + SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("10000", condition.STRING))) + + config, _ := fieldx.SchemaFromMap(map[string]any{ + "approver_role": map[string]any{ + "type": "string", + "value": "manager", + }, + "timeout_hours": map[string]any{ + "type": "string", + "value": "24", + }, + }) + + // 创建条件分支,使用 action 名称字符串 + condCase := condflow.NewCondCase("manager-approval", cond, "Approval/ApproveExecutor@Manager"). + WithPriority(90). + WithActionConfig(config) + + // 添加到流程 + s.flow.AddCase(condCase) + + // 准备输入数据 + input := reflux.New(map[string]any{ + "amount": 5000.0, + "user": map[string]any{ + "id": 1, + "credit_score": 80, + }, + }) + + // 执行流程 + err := s.flow.Run(s.app, input, base.GetSystemUser()) + s.NoError(err) +} + +// TestLowCreditReject 测试低信用评分拒绝 +func (s *CondFlowTestSuite) TestLowCreditReject() { + // 创建条件: 用户信用评分 < 60 + cond := condition.New(condition.Describe("信用不足拒绝")) + cond.SetExpr(condition.NewExpr("@", "user->>'credit_score'", condition.Operator(condition.LT), condition.Token("60", condition.STRING))) + + config, _ := fieldx.SchemaFromMap(map[string]any{ + "reason": map[string]any{ + "type": "string", + "value": "信用评分不足", + }, + "notify": map[string]any{ + "type": "string", + "value": "true", + }, + "suggest_retry": map[string]any{ + "type": "string", + "value": "false", + }, + }) + + // 创建条件分支,使用 action 名称字符串 + condCase := condflow.NewCondCase("reject-low-credit", cond, "Approval/ApproveExecutor@Reject"). + WithPriority(200). + WithActionConfig(config) + + // 添加到流程 + s.flow.AddCase(condCase) + + // 准备输入数据 + input := reflux.New(map[string]any{ + "amount": 500.0, + "user": map[string]any{ + "id": 1, + "credit_score": 50, + }, + }) + + // 执行流程 + err := s.flow.Run(s.app, input, base.GetSystemUser()) + s.NoError(err) +} + +// TestMultipleCases 测试多个条件分支 +func (s *CondFlowTestSuite) TestMultipleCases() { + // Case 1: 小额自动审批 + case1Cond := condition.New(condition.Describe("小额自动审批")) + case1Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("1000", condition.STRING))) + + case1 := condflow.NewCondCase("auto-approve-small", case1Cond, "Approval/ApproveExecutor@AutoApprove"). + WithPriority(100) + + // Case 2: 经理审批 + case2Cond := condition.New(condition.Describe("经理审批")) + case2Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("1000", condition.STRING))). + SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("10000", condition.STRING))) + + case2 := condflow.NewCondCase("manager-approval", case2Cond, "Approval/ApproveExecutor@Manager"). + WithPriority(90) + + // Case 3: 总监审批 + case3Cond := condition.New(condition.Describe("总监审批")) + case3Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("10000", condition.STRING))) + + case3 := condflow.NewCondCase("director-approval", case3Cond, "Approval/ApproveExecutor@Director"). + WithPriority(80) + + // Case 4: 低信用拒绝(最高优先级) + case4Cond := condition.New(condition.Describe("信用不足拒绝")) + case4Cond.SetExpr(condition.NewExpr("@", "user->>'credit_score'", condition.Operator(condition.LT), condition.Token("60", condition.STRING))) + + case4Config, _ := fieldx.SchemaFromMap(map[string]any{ + "reason": map[string]any{ + "type": "string", + "value": "信用评分不足", + }, + }) + + case4 := condflow.NewCondCase("reject-low-credit", case4Cond, "Approval/ApproveExecutor@Reject"). + WithPriority(200). + WithActionConfig(case4Config) + + // 添加所有条件分支 + s.flow.AddCase(case4). + AddCase(case1). + AddCase(case2). + AddCase(case3) + + // 测试不同的输入数据 + testCases := []struct { + name string + input map[string]any + expect string + }{ + { + name: "小额正常用户", + input: map[string]any{ + "amount": 500.0, + "user": map[string]any{ + "id": 1, + "credit_score": 80, + }, + }, + expect: "auto-approve", + }, + { + name: "中额正常用户", + input: map[string]any{ + "amount": 5000.0, + "user": map[string]any{ + "id": 1, + "credit_score": 80, + }, + }, + expect: "manager-approve", + }, + { + name: "大额正常用户", + input: map[string]any{ + "amount": 15000.0, + "user": map[string]any{ + "id": 1, + "credit_score": 80, + }, + }, + expect: "director-approve", + }, + { + name: "低信用用户", + input: map[string]any{ + "amount": 500.0, + "user": map[string]any{ + "id": 1, + "credit_score": 50, + }, + }, + expect: "reject", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + input := reflux.New(tc.input) + err := s.flow.Run(s.app, input, base.GetSystemUser()) + s.NoError(err) + }) + } +} + +// TestSuite 运行测试套件 +func TestCondFlow(t *testing.T) { + suite.Run(t, new(CondFlowTestSuite)) +} diff --git a/tests/example_test.go b/tests/example_test.go new file mode 100644 index 0000000..50694a5 --- /dev/null +++ b/tests/example_test.go @@ -0,0 +1,591 @@ +package tests + +import ( + "fmt" + + "git.fsdpf.net/go/condflow" + "git.fsdpf.net/go/condflow/tests/fixtures" + "git.fsdpf.net/go/condition" + "git.fsdpf.net/go/contracts/base" + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/fieldx" + "git.fsdpf.net/go/req" + "git.fsdpf.net/go/req/resx" + "github.com/samber/do/v2" +) + +// setupExampleExecutor 创建并注册示例 executor +func setupExampleExecutor(app do.Injector) { + // 创建 resource + exampleRes := resx.New(app, "Example", "examples", + resx.WithUuid("660e8400-e29b-41d4-a716-446655440002"), + resx.WithName("示例"), + resx.WithFields( + resx.NewResField("id", "", resx.FieldWithName("ID"), resx.FieldWithDataType(req.ResInteger)), + resx.NewResField("created_user", "", resx.FieldWithName("创建者"), resx.FieldWithDefault("00000000-0000-0000-0000-000000000000"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("owned_user", "", resx.FieldWithName("拥有者"), resx.FieldWithDefault("00000000-0000-0000-0000-000000000000"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("created_at", "", resx.FieldWithName("创建时间"), resx.FieldWithDefault("sql:CURRENT_TIMESTAMP"), resx.FieldWithDataType(req.ResTimestamp)), + resx.NewResField("updated_at", "", resx.FieldWithName("更新时间"), resx.FieldWithDefault("sql:CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"), resx.FieldWithDataType(req.ResTimestamp)), + resx.NewResField("uuid", "", resx.FieldWithName("UUID"), resx.FieldWithDefault("sql:uuid()"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("status", "", resx.FieldWithName("状态"), resx.FieldWithDefault("pending"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("type", "", resx.FieldWithName("类型"), resx.FieldWithDataType(req.ResString)), + resx.NewResField("data", "", resx.FieldWithName("数据"), resx.FieldWithDefault("{}"), resx.FieldWithDataType(req.ResJson)), + ), + ) + + executor := condflow.NewActionInvoker(fixtures.NewExampleExecutor(exampleRes), nil) + + do.ProvideNamedValue(app, fmt.Sprintf("%s/ExampleExecutor", executor.Res().GetCode()), executor) +} + +// Example_userApprovalFlow 用户审批流示例 +func Example_userApprovalFlow() { + // 初始化 DI 容器并注册 executor + app := do.New() + setupExampleExecutor(app) + + // 创建条件流 + flow := condflow.New("user-approval-flow") + + // Case 1: 金额 <= 1000,自动通过 + case1Cond := condition.New(condition.Describe("小额自动审批")) + case1Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("1000", condition.STRING))) + + case1Config, _ := fieldx.SchemaFromMap(map[string]any{ + "notify": map[string]any{ + "type": "string", + "value": "true", + }, + "message": map[string]any{ + "type": "string", + "value": "申请已自动通过", + }, + }) + + case1 := condflow.NewCondCase( + "auto-approve-small", + case1Cond, + "Example/ExampleExecutor@AutoApprove", + ). + WithPriority(100). + WithActionConfig(case1Config) + + // Case 2: 1000 < 金额 <= 10000,需要经理审批 + case2Cond := condition.New(condition.Describe("经理审批")) + case2Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("1000", condition.STRING))). + SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.LE), condition.Token("10000", condition.STRING))) + + case2Config, _ := fieldx.SchemaFromMap(map[string]any{ + "approver_role": map[string]any{ + "type": "string", + "value": "manager", + }, + "timeout_hours": map[string]any{ + "type": "string", + "value": "24", + }, + "notify": map[string]any{ + "type": "string", + "value": "true", + }, + }) + + case2 := condflow.NewCondCase( + "manager-approval", + case2Cond, + "Example/ExampleExecutor@ManagerApproval", + ). + WithPriority(90). + WithActionConfig(case2Config) + + // Case 3: 金额 > 10000,需要总监审批 + case3Cond := condition.New(condition.Describe("总监审批")) + case3Cond.SetExpr(condition.NewExpr("@", "amount", condition.Operator(condition.GT), condition.Token("10000", condition.STRING))) + + case3Config, _ := fieldx.SchemaFromMap(map[string]any{ + "approver_role": map[string]any{ + "type": "string", + "value": "director", + }, + "timeout_hours": map[string]any{ + "type": "string", + "value": "48", + }, + "notify": map[string]any{ + "type": "string", + "value": "true", + }, + "cc_roles": map[string]any{ + "type": "string", + "value": "manager,ceo", + }, + }) + + case3 := condflow.NewCondCase( + "director-approval", + case3Cond, + "Example/ExampleExecutor@DirectorApproval", + ). + WithPriority(80). + WithActionConfig(case3Config) + + // Case 4: 用户信用评分 < 60,直接拒绝 + case4Cond := condition.New(condition.Describe("信用不足拒绝")) + case4Cond.SetExpr(condition.NewExpr("@", "user->>'credit_score'", condition.Operator(condition.LT), condition.Token("60", condition.STRING))) + + case4Config, _ := fieldx.SchemaFromMap(map[string]any{ + "reason": map[string]any{ + "type": "string", + "value": "信用评分不足", + }, + "notify": map[string]any{ + "type": "string", + "value": "true", + }, + "suggest_retry": map[string]any{ + "type": "string", + "value": "false", + }, + }) + + case4 := condflow.NewCondCase( + "reject-low-credit", + case4Cond, + "Example/ExampleExecutor@Reject", + ). + WithPriority(200). // 最高优先级,优先检查 + WithActionConfig(case4Config) + + // 添加所有条件分支到流程 + flow.AddCase(case4). // 优先级最高 + AddCase(case1). + AddCase(case2). + AddCase(case3) + + // 生成包含多种数据类型的测试 map + input := reflux.New(map[string]any{ + // 基础类型 + "amount": 5000.50, // float64 + "count": 100, // int + "active": true, // bool + "user_name": "张三", // string + "age": int32(25), // int32 + "total": int64(1000000), // int64 + "score": float32(98.5), // float32 + "is_vip": false, // bool + + // 数组/切片类型 + "tags": []string{"vip", "premium", "active"}, // slice + "scores": []int{85, 90, 95}, // slice of int + "prices": []float64{19.99, 29.99, 39.99}, // slice of float + + // Map 类型 + "preferences": map[string]any{ + "newsletter": true, + "theme": "dark", + "language": "zh-CN", + }, + + // 嵌套结构 + "user": map[string]any{ + "id": 12345, + "email": "user@example.com", + "level": 5, + "credit_score": 85, + "created_at": "2024-01-01", + }, + + // 空值测试 + "nullable_field": nil, + "empty_string": "", + }) + + flow.Run(app, input, base.GetSystemUser()) + + fmt.Printf("条件流名称: %s\n", flow.Name()) + fmt.Printf("条件分支数量: %d\n", flow.CaseCount()) + + // Output: + // 条件流名称: user-approval-flow + // 条件分支数量: 4 +} + +// Example_emailCampaignFlow 邮件营销活动流示例 +func Example_emailCampaignFlow() { + // 初始化 DI 容器并注册 executor + app := do.New() + setupExampleExecutor(app) + + // 创建邮件营销条件流 + flow := condflow.New("email-campaign-flow") + + // Case 1: VIP 用户 - 发送专属优惠 + vipCond := condition.New(condition.Describe("VIP用户筛选")) + vipCond.SetExpr(condition.NewExpr("User", "level", condition.Operator(condition.GE), condition.Token("5", condition.STRING))). + SetExpr(condition.NewExpr("User", "email", condition.Operator(condition.IS_NOT_NULL))). + SetExpr(condition.NewExpr("User", "preferences->'newsletter'", condition.Token("true", condition.STRING))). + SetExpr(condition.NewExpr("User", "tags", condition.FieldSqlFn("json_contains", ""), condition.Token("vip", condition.STRING))) + + vipConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "template": map[string]any{ + "type": "string", + "value": "vip-exclusive-offer", + }, + "subject": map[string]any{ + "type": "string", + "value": "尊贵的VIP会员,专属优惠来啦!", + }, + "discount": map[string]any{ + "type": "string", + "value": "30", + }, + "valid_days": map[string]any{ + "type": "string", + "value": "30", + }, + "priority": map[string]any{ + "type": "string", + "value": "high", + }, + "attachments": map[string]any{ + "type": "string", + "value": "vip-catalog.pdf", + }, + }) + + vipCase := condflow.NewCondCase("vip-campaign", vipCond, "Example/ExampleExecutor@VIPEmail"). + WithPriority(100). + WithActionConfig(vipConfig) + + // Case 2: 新用户 - 发送欢迎邮件 + newUserCond := condition.New(condition.Describe("新用户识别")) + newUserCond.SetExpr(condition.NewExpr("User", "created_at", condition.Operator(condition.GE), condition.Token("DATE_SUB(NOW(), INTERVAL 7 DAY)", condition.SQL))). + SetExpr(condition.NewExpr("User", "email_verified", condition.Token("true", condition.STRING))) + + newUserConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "template": map[string]any{ + "type": "string", + "value": "welcome-new-user", + }, + "subject": map[string]any{ + "type": "string", + "value": "欢迎加入我们!", + }, + "coupon_code": map[string]any{ + "type": "string", + "value": "WELCOME2024", + }, + "discount": map[string]any{ + "type": "string", + "value": "10", + }, + "trigger_delay": map[string]any{ + "type": "string", + "value": "1h", + }, + }) + + newUserCase := condflow.NewCondCase("new-user-welcome", newUserCond, "Example/ExampleExecutor@WelcomeEmail"). + WithPriority(90). + WithActionConfig(newUserConfig) + + // Case 3: 普通活跃用户 - 发送常规营销邮件 + regularCond := condition.New(condition.Describe("活跃普通用户")) + regularCond.SetExpr(condition.NewExpr("User", "last_login_at", condition.Operator(condition.GE), condition.Token("DATE_SUB(NOW(), INTERVAL 30 DAY)", condition.SQL))). + SetExpr(condition.NewExpr("User", "email", condition.Operator(condition.IS_NOT_NULL))). + SetExpr(condition.NewExpr("User", "preferences->'newsletter'", condition.Token("true", condition.STRING))) + + regularConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "template": map[string]any{ + "type": "string", + "value": "regular-promotion", + }, + "subject": map[string]any{ + "type": "string", + "value": "本月精选优惠", + }, + "discount": map[string]any{ + "type": "string", + "value": "15", + }, + "valid_days": map[string]any{ + "type": "string", + "value": "14", + }, + }) + + regularCase := condflow.NewCondCase("regular-campaign", regularCond, "Example/ExampleExecutor@RegularEmail"). + WithPriority(50). + WithActionConfig(regularConfig) + + // Case 4: 未订阅用户 - 跳过 + skipCond := condition.New(condition.Describe("未订阅用户")) + skipCond.SetExpr(condition.NewExpr("User", "preferences->'newsletter'", condition.Token("false", condition.STRING))) + + skipConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "reason": map[string]any{ + "type": "string", + "value": "用户未订阅营销邮件", + }, + "log": map[string]any{ + "type": "string", + "value": "true", + }, + }) + + skipCase := condflow.NewCondCase("skip-unsubscribed", skipCond, "Example/ExampleExecutor@Skip"). + WithPriority(200). // 最高优先级 + WithActionConfig(skipConfig) + + // 添加所有条件分支 + flow.AddCase(skipCase). + AddCase(vipCase). + AddCase(newUserCase). + AddCase(regularCase) + + fmt.Printf("条件流名称: %s\n", flow.Name()) + fmt.Printf("条件分支数量: %d\n", flow.CaseCount()) + + // Output: + // 条件流名称: email-campaign-flow + // 条件分支数量: 4 +} + +// Example_orderProcessingFlow 订单处理流示例 +func Example_orderProcessingFlow() { + // 初始化 DI 容器并注册 executor + app := do.New() + setupExampleExecutor(app) + + // 创建订单处理条件流 + flow := condflow.New("order-processing-flow") + + // Case 1: 高价值订单 + VIP客户 -> 优先处理 + 包邮 + premiumCond := condition.New(condition.Describe("高价值VIP订单")) + premiumCond.SetExpr(condition.NewExpr("Order", "total_amount", condition.Operator(condition.GE), condition.Token("5000", condition.STRING))). + SetExpr(condition.NewExpr("User", "level", condition.Operator(condition.GE), condition.Token("5", condition.STRING))) + + premiumConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "shipping_method": map[string]any{ + "type": "string", + "value": "express", + }, + "shipping_fee": map[string]any{ + "type": "string", + "value": "0", + }, + "priority": map[string]any{ + "type": "string", + "value": "high", + }, + "gift_wrapping": map[string]any{ + "type": "string", + "value": "true", + }, + "insurance": map[string]any{ + "type": "string", + "value": "true", + }, + }) + + premiumCase := condflow.NewCondCase("premium-order", premiumCond, "Example/ExampleExecutor@ExpressShipping"). + WithPriority(100). + WithActionConfig(premiumConfig). + WithNextCaseOnSuccess(true) // 继续执行下一个case(通知客户) + + // Case 2: 紧急订单 -> 加急配送 + urgentCond := condition.New(condition.Describe("紧急订单")) + urgentCond.SetExpr(condition.NewExpr("Order", "tags", condition.FieldSqlFn("json_contains", ""), condition.Token("urgent", condition.STRING))) + + urgentConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "shipping_method": map[string]any{ + "type": "string", + "value": "express", + }, + "max_hours": map[string]any{ + "type": "string", + "value": "24", + }, + "priority": map[string]any{ + "type": "string", + "value": "urgent", + }, + }) + + urgentCase := condflow.NewCondCase("urgent-order", urgentCond, "Example/ExampleExecutor@ExpressShipping"). + WithPriority(95). + WithActionConfig(urgentConfig) + + // Case 3: 跨区域订单 -> 仓库调配 + crossRegionCond := condition.New(condition.Describe("跨区域订单")) + crossRegionCond.SetExpr(condition.NewExpr("Order", "shipping_province", condition.Operator(condition.NE), condition.Token("warehouse_province", condition.PARAM))) + + crossRegionConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "check_inventory": map[string]any{ + "type": "string", + "value": "true", + }, + "transfer_if_needed": map[string]any{ + "type": "string", + "value": "true", + }, + "estimated_days": map[string]any{ + "type": "string", + "value": "5", + }, + }) + + crossRegionCase := condflow.NewCondCase("cross-region-order", crossRegionCond, "Example/ExampleExecutor@WarehouseTransfer"). + WithPriority(80). + WithActionConfig(crossRegionConfig) + + // Case 4: 普通订单 -> 标准配送 + standardCond := condition.New(condition.Describe("普通订单")) + standardCond.SetExpr(condition.NewExpr("Order", "total_amount", condition.Operator(condition.GT), condition.Token("0", condition.STRING))) + + standardConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "shipping_method": map[string]any{ + "type": "string", + "value": "standard", + }, + "estimated_days": map[string]any{ + "type": "string", + "value": "3", + }, + }) + + standardCase := condflow.NewCondCase("standard-order", standardCond, "Example/ExampleExecutor@StandardShipping"). + WithPriority(50). + WithActionConfig(standardConfig) + + // Case 5: 通知客户(配合 premium case 的 nextCaseOnSuccess) + notifyCond := condition.New(condition.Describe("需要通知的订单")) + notifyCond.SetExpr(condition.NewExpr("Order", "notify_customer", condition.Token("true", condition.STRING))) + + notifyConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "channels": map[string]any{ + "type": "string", + "value": "email,sms", + }, + "template": map[string]any{ + "type": "string", + "value": "order-confirmed", + }, + "immediate": map[string]any{ + "type": "string", + "value": "true", + }, + }) + + notifyCase := condflow.NewCondCase("notify-customer", notifyCond, "Example/ExampleExecutor@NotifyCustomer"). + WithPriority(40). + WithActionConfig(notifyConfig) + + // 添加所有条件分支 + flow.AddCase(premiumCase). + AddCase(urgentCase). + AddCase(crossRegionCase). + AddCase(standardCase). + AddCase(notifyCase) + + fmt.Printf("条件流名称: %s\n", flow.Name()) + fmt.Printf("条件分支数量: %d\n", flow.CaseCount()) + + // Output: + // 条件流名称: order-processing-flow + // 条件分支数量: 5 +} + +// Example_nestedFlow 嵌套流程示例 - 展示如何使用 NextFlowOnSuccess +func Example_nestedFlow() { + // 初始化 DI 容器并注册 executor + app := do.New() + setupExampleExecutor(app) + + // 创建主流程:用户注册流程 + mainFlow := condflow.New("user-registration-flow") + + // 创建子流程:邮件验证流程 + emailVerificationFlow := condflow.New("email-verification-flow") + + verifyCond := condition.New(condition.Describe("发送验证邮件")) + verifyConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "template": map[string]any{ + "type": "string", + "value": "email-verification", + }, + "expire_in": map[string]any{ + "type": "string", + "value": "24h", + }, + }) + verifyCase := condflow.NewCondCase("send-verify-email", verifyCond, "Example/ExampleExecutor@SendVerification"). + WithActionConfig(verifyConfig) + + emailVerificationFlow.AddCase(verifyCase) + + // 创建子流程:欢迎礼包流程 + welcomePackFlow := condflow.New("welcome-pack-flow") + + couponCond := condition.New(condition.Describe("创建新用户优惠券")) + couponConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "coupon_type": map[string]any{ + "type": "string", + "value": "new_user", + }, + "discount": map[string]any{ + "type": "string", + "value": "20", + }, + "valid_days": map[string]any{ + "type": "string", + "value": "30", + }, + }) + couponCase := condflow.NewCondCase("new-user-coupon", couponCond, "Example/ExampleExecutor@CreateCoupon"). + WithActionConfig(couponConfig) + + welcomePackFlow.AddCase(couponCase) + + // 主流程 Case: 邮箱注册用户 + emailRegCond := condition.New(condition.Describe("邮箱注册")) + emailRegCond.SetExpr(condition.NewExpr("User", "register_type", condition.Token("email", condition.STRING))) + + emailRegConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "send_welcome": map[string]any{ + "type": "string", + "value": "true", + }, + }) + + emailRegCase := condflow.NewCondCase("email-registration", emailRegCond, "Example/ExampleExecutor@CreateUser"). + WithPriority(100). + WithNextFlowOnSuccess(emailVerificationFlow, nil). // 成功后执行邮件验证流程 + WithActionConfig(emailRegConfig) + + // 主流程 Case: 手机号注册用户 + phoneRegCond := condition.New(condition.Describe("手机号注册")) + phoneRegCond.SetExpr(condition.NewExpr("User", "register_type", condition.Token("phone", condition.STRING))) + + phoneRegConfig, _ := fieldx.SchemaFromMap(map[string]any{ + "send_sms": map[string]any{ + "type": "string", + "value": "true", + }, + }) + + phoneRegCase := condflow.NewCondCase("phone-registration", phoneRegCond, "Example/ExampleExecutor@CreateUser"). + WithPriority(90). + WithNextFlowOnSuccess(welcomePackFlow, nil). // 成功后执行欢迎礼包流程 + WithActionConfig(phoneRegConfig) + + mainFlow.AddCase(emailRegCase). + AddCase(phoneRegCase) + + fmt.Printf("主流程名称: %s\n", mainFlow.Name()) + fmt.Printf("主流程分支数量: %d\n", mainFlow.CaseCount()) + + // Output: + // 主流程名称: user-registration-flow + // 主流程分支数量: 2 +} diff --git a/tests/fixtures/approve_executor.go b/tests/fixtures/approve_executor.go new file mode 100644 index 0000000..e9ff669 --- /dev/null +++ b/tests/fixtures/approve_executor.go @@ -0,0 +1,55 @@ +package fixtures + +import ( + "git.fsdpf.net/go/condflow" + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/valuex" + "git.fsdpf.net/go/req" +) + +// ApproveExecutor 审批执行器 +type ApproveExecutor struct { + res req.Resource +} + +// NewApproveExecutor 创建审批执行器 +func NewApproveExecutor(res req.Resource) *ApproveExecutor { + return &ApproveExecutor{res: res} +} + +func (ae *ApproveExecutor) Res() req.Resource { + return ae.res +} + +// AutoApprove 自动审批 +func (ae *ApproveExecutor) AutoApprove(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{ + "status": "approved", + "method": "auto", + }), nil +} + +// Manager 经理审批 +func (ae *ApproveExecutor) Manager(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{ + "status": "pending", + "approver": "manager", + }), nil +} + +// Director 总监审批 +func (ae *ApproveExecutor) Director(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{ + "status": "pending", + "approver": "director", + }), nil +} + +// Reject 拒绝 +func (ae *ApproveExecutor) Reject(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + reason, _ := cfg.Lookup("reason") + return reflux.New(map[string]any{ + "status": "rejected", + "reason": reason.String(), + }), nil +} diff --git a/tests/fixtures/example_executor.go b/tests/fixtures/example_executor.go new file mode 100644 index 0000000..bbff38b --- /dev/null +++ b/tests/fixtures/example_executor.go @@ -0,0 +1,114 @@ +package fixtures + +import ( + "git.fsdpf.net/go/condflow" + "git.fsdpf.net/go/reflux" + "git.fsdpf.net/go/reflux/valuex" + "git.fsdpf.net/go/req" +) + +// ExampleExecutor 示例执行器 +type ExampleExecutor struct { + res req.Resource +} + +// NewExampleExecutor 创建示例执行器 +func NewExampleExecutor(res req.Resource) *ExampleExecutor { + return &ExampleExecutor{res: res} +} + +func (ee *ExampleExecutor) Res() req.Resource { + return ee.res +} + +// AutoApprove 自动审批 +func (ee *ExampleExecutor) AutoApprove(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"status": "approved", "method": "auto"}), nil +} + +// ManagerApproval 经理审批 +func (ee *ExampleExecutor) ManagerApproval(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"status": "pending", "approver": "manager"}), nil +} + +// DirectorApproval 总监审批 +func (ee *ExampleExecutor) DirectorApproval(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"status": "pending", "approver": "director"}), nil +} + +// Reject 拒绝 +func (ee *ExampleExecutor) Reject(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + reason, _ := cfg.Lookup("reason") + return reflux.New(map[string]any{"status": "rejected", "reason": reason.String()}), nil +} + +// VIPEmail 处理VIP邮件 +func (ee *ExampleExecutor) VIPEmail(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + template, _ := cfg.Lookup("template") + return reflux.New(map[string]any{"sent": true, "template": template.String()}), nil +} + +// RegularEmail 处理普通邮件 +func (ee *ExampleExecutor) RegularEmail(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + template, _ := cfg.Lookup("template") + return reflux.New(map[string]any{"sent": true, "template": template.String()}), nil +} + +// WelcomeEmail 处理欢迎邮件 +func (ee *ExampleExecutor) WelcomeEmail(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + template, _ := cfg.Lookup("template") + return reflux.New(map[string]any{"sent": true, "template": template.String()}), nil +} + +// Skip 跳过处理 +func (ee *ExampleExecutor) Skip(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + reason, _ := cfg.Lookup("reason") + return reflux.New(map[string]any{"skipped": true, "reason": reason.String()}), nil +} + +// ExpressShipping 处理快递配送 +func (ee *ExampleExecutor) ExpressShipping(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + fee, _ := cfg.Lookup("shipping_fee") + return reflux.New(map[string]any{"shipping": "express", "fee": fee.Int()}), nil +} + +// StandardShipping 处理标准配送 +func (ee *ExampleExecutor) StandardShipping(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"shipping": "standard"}), nil +} + +// WarehouseTransfer 处理仓库调配 +func (ee *ExampleExecutor) WarehouseTransfer(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"transfer": true}), nil +} + +// NotifyCustomer 通知客户 +func (ee *ExampleExecutor) NotifyCustomer(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"notified": true}), nil +} + +// SendVerification 发送验证 +func (ee *ExampleExecutor) SendVerification(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"verification_sent": true}), nil +} + +// WaitVerification 等待验证 +func (ee *ExampleExecutor) WaitVerification(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"waiting": true}), nil +} + +// CreateCoupon 创建优惠券 +func (ee *ExampleExecutor) CreateCoupon(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + discount, _ := cfg.Lookup("discount") + return reflux.New(map[string]any{"coupon_created": true, "discount": discount.Int()}), nil +} + +// GrantPoints 授予积分 +func (ee *ExampleExecutor) GrantPoints(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"points_granted": true}), nil +} + +// CreateUser 创建用户 +func (ee *ExampleExecutor) CreateUser(ctx condflow.Context, cfg valuex.Accessor) (reflux.R, error) { + return reflux.New(map[string]any{"user_created": true}), nil +} diff --git a/tests/fixtures/init_condflow_cache.gobak b/tests/fixtures/init_condflow_cache.gobak new file mode 100644 index 0000000..1d55c6c --- /dev/null +++ b/tests/fixtures/init_condflow_cache.gobak @@ -0,0 +1,97 @@ +package fixtures + +import ( + // "fmt" + + // "git.fsdpf.net/go/contracts" + "git.fsdpf.net/go/contracts/base" + // "git.fsdpf.net/go/db" + "github.com/samber/do" + "github.com/samber/lo" +) + +func init() { + caches = append(caches, _ResCache{injector: func(app *do.Injector) { + do.Provide(app, GetCondflowServiceProvider) + + // 缓存 Condflow + do.Provide(app, func(i *do.Injector) ([]base.Condflow, error) { + defer useRefreshCache(i, "Condflow", func() error { + do.Override(i, GetCondflowServiceProvider) + do.Override(i, func(i *do.Injector) ([]base.Condflow, error) { + return getCondflowCaches(i) + }) + return nil + }) + return getCondflowCaches(i) + }) + }, priority: 0}) +} + +func GetCondflowServiceProvider(container *do.Injector) (base.GetCondflow, error) { + caches := do.MustInvoke[[]base.Condflow](container) + + return func(code string) (base.Condflow, bool) { + item, flag := lo.Find(caches, func(i base.Condflow) bool { + return i.Code == code || i.Uuid == code + }) + return item, flag + }, nil +} + +func getCondflowCaches(app *do.Injector) (items []base.Condflow, err error) { + // TODO: 临时测试数据,等待资源表创建后从数据库读取 + // if res, ok := do.MustInvoke[contracts.GetResource](app)("Condflow"); !ok { + // return items, fmt.Errorf("获取 Condflow 资源失败") + // } else { + // items = []base.Condflow{} + // if e := res.GetDBTable(base.GetSystemUser()). + // Select( + // db.T("Condflow").All(), + // ). + // Order(db.I("Condflow.id").Asc()). + // ScanStructs(&items); e != nil { + // err = fmt.Errorf("获取 Condflow 资源失败, %w", e) + // } + // } + // + items = []base.Condflow{ + { + Uuid: "550e8400-e29b-41d4-a716-446655440001", + Name: "用户审批流程", + Code: "user-approval-flow", + Comment: "处理用户申请的审批流程,根据金额和信用评分自动分配审批策略", + Params: []base.CondflowParam{ + {Name: "申请金额", Code: "amount", DataType: "decimal", IsRequired: true, Comment: "申请的金额"}, + {Name: "用户信息", Code: "user", DataType: "json", IsRequired: true, Comment: "用户详细信息"}, + }, + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "550e8400-e29b-41d4-a716-446655440002", + Name: "邮件营销活动流程", + Code: "email-campaign-flow", + Comment: "根据用户等级和活跃度自动发送不同类型的营销邮件", + Params: []base.CondflowParam{ + {Name: "用户信息", Code: "user", DataType: "json", IsRequired: true, Comment: "用户详细信息"}, + }, + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "550e8400-e29b-41d4-a716-446655440003", + Name: "订单处理流程", + Code: "order-processing-flow", + Comment: "订单自动化处理流程,包括配送方式选择和客户通知", + Params: []base.CondflowParam{ + {Name: "订单信息", Code: "order", DataType: "json", IsRequired: true, Comment: "订单详细信息"}, + {Name: "用户信息", Code: "user", DataType: "json", IsRequired: true, Comment: "下单用户信息"}, + }, + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + } + + return +} diff --git a/tests/fixtures/init_condflow_case_cache.gobak b/tests/fixtures/init_condflow_case_cache.gobak new file mode 100644 index 0000000..b5b232b --- /dev/null +++ b/tests/fixtures/init_condflow_case_cache.gobak @@ -0,0 +1,207 @@ +package fixtures + +import ( + // "fmt" + + // "git.fsdpf.net/go/contracts" + "git.fsdpf.net/go/contracts/base" + // "git.fsdpf.net/go/db" + "github.com/samber/do" + "github.com/samber/lo" +) + +func init() { + caches = append(caches, _ResCache{injector: func(app *do.Injector) { + do.Provide(app, GetCondflowCasesServiceProvider) + + // 缓存 CondflowCase + do.Provide(app, func(i *do.Injector) ([]base.CondflowCase, error) { + defer useRefreshCache(i, "CondflowCase", func() error { + do.Override(i, GetCondflowCasesServiceProvider) + do.Override(i, func(i *do.Injector) ([]base.CondflowCase, error) { + return getCondflowCaseCaches(i) + }) + return nil + }) + return getCondflowCaseCaches(i) + }) + }, priority: 0}) +} + +func GetCondflowCasesServiceProvider(container *do.Injector) (base.GetCondflowCases, error) { + caches := do.MustInvoke[[]base.CondflowCase](container) + + return func(uuid string) []base.CondflowCase { + items := lo.Filter(caches, func(item base.CondflowCase, _ int) bool { + return item.CondflowUuid == uuid + }) + + return items + }, nil +} + +func getCondflowCaseCaches(app *do.Injector) (items []base.CondflowCase, err error) { + // TODO: 临时测试数据,等待资源表创建后从数据库读取 + // if res, ok := do.MustInvoke[contracts.GetResource](app)("CondflowCase"); !ok { + // return items, fmt.Errorf("获取 CondflowCase 资源失败") + // } else { + // items = []base.CondflowCase{} + // if e := res.GetDBTable(base.GetSystemUser()). + // Select( + // db.T("CondflowCase").All(), + // ). + // Order(db.I("CondflowCase.priority").Desc(), db.I("CondflowCase.id").Asc()). + // ScanStructs(&items); e != nil { + // err = fmt.Errorf("获取 CondflowCase 资源失败, %w", e) + // } + // } + items = []base.CondflowCase{ + // 用户审批流程的 cases + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440001", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440001", + Name: "低信用拒绝", + Code: "reject-low-credit", + Priority: 200, + Action: "Approval/ApproveExecutor@Reject", + Config: map[string]any{"reason": "信用评分不足", "notify": true, "suggest_retry": false}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "信用评分低于60,直接拒绝", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440002", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440001", + Name: "小额自动审批", + Code: "auto-approve-small", + Priority: 100, + Action: "Approval/ApproveExecutor@AutoApprove", + Config: map[string]any{"notify": true, "message": "申请已自动通过"}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "金额 <= 1000,自动通过", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440003", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440001", + Name: "经理审批", + Code: "manager-approval", + Priority: 90, + Action: "Approval/ApproveExecutor@Manager", + Config: map[string]any{"approver_role": "manager", "timeout_hours": 24, "notify": true}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "1000 < 金额 <= 10000,需要经理审批", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440004", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440001", + Name: "总监审批", + Code: "director-approval", + Priority: 80, + Action: "Approval/ApproveExecutor@Director", + Config: map[string]any{"approver_role": "director", "timeout_hours": 48, "notify": true, "cc_roles": []string{"manager", "ceo"}}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "金额 > 10000,需要总监审批", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + + // 邮件营销活动流程的 cases + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440005", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440002", + Name: "跳过未订阅用户", + Code: "skip-unsubscribed", + Priority: 200, + Action: "Marketing/EmailExecutor@Skip", + Config: map[string]any{"reason": "用户未订阅营销邮件", "log": true}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "未订阅营销邮件的用户跳过", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440006", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440002", + Name: "VIP 用户营销", + Code: "vip-campaign", + Priority: 100, + Action: "Marketing/EmailExecutor@VIP", + Config: map[string]any{"template": "vip-exclusive-offer", "discount": 30, "valid_days": 30, "priority": "high"}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "VIP 用户发送专属优惠", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440007", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440002", + Name: "新用户欢迎", + Code: "new-user-welcome", + Priority: 90, + Action: "Marketing/EmailExecutor@Welcome", + Config: map[string]any{"template": "welcome-new-user", "coupon_code": "WELCOME2024", "discount": 10}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "新注册用户发送欢迎邮件", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + + // 订单处理流程的 cases + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440008", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440003", + Name: "VIP 订单优先处理", + Code: "premium-order", + Priority: 100, + Action: "Order/ShippingExecutor@Express", + Config: map[string]any{"shipping_method": "express", "shipping_fee": 0, "priority": "high", "gift_wrapping": true}, + NextCaseOnSuccess: true, // 继续执行通知客户 + NextFlowUuid: "", + Comment: "高价值 VIP 订单加急处理", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440009", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440003", + Name: "标准配送", + Code: "standard-order", + Priority: 50, + Action: "Order/ShippingExecutor@Standard", + Config: map[string]any{"shipping_method": "standard", "estimated_days": 3}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "普通订单标准配送", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "case-550e8400-e29b-41d4-a716-446655440010", + CondflowUuid: "550e8400-e29b-41d4-a716-446655440003", + Name: "通知客户", + Code: "notify-customer", + Priority: 40, + Action: "Order/NotificationExecutor@Notify", + Config: map[string]any{"channels": []string{"email", "sms"}, "template": "order-confirmed", "immediate": true}, + NextCaseOnSuccess: false, + NextFlowUuid: "", + Comment: "订单确认后通知客户", + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + } + + return items, nil +} diff --git a/tests/fixtures/init_condflow_decision_cache.gobak b/tests/fixtures/init_condflow_decision_cache.gobak new file mode 100644 index 0000000..852ba84 --- /dev/null +++ b/tests/fixtures/init_condflow_decision_cache.gobak @@ -0,0 +1,211 @@ +package fixtures + +import ( + // "fmt" + + // "git.fsdpf.net/go/contracts" + + "git.fsdpf.net/go/contracts/base" + // "git.fsdpf.net/go/db" + "github.com/samber/do" + "github.com/samber/lo" +) + +func init() { + caches = append(caches, _ResCache{injector: func(app *do.Injector) { + do.Provide(app, GetCondflowDecisionServiceProvider) + + // 缓存 CondflowDecision + do.Provide(app, func(i *do.Injector) ([]base.CondflowDecision, error) { + defer useRefreshCache(i, "CondflowDecision", func() error { + do.Override(i, GetCondflowDecisionServiceProvider) + do.Override(i, func(i *do.Injector) ([]base.CondflowDecision, error) { + return getCondflowDecisionCaches(i) + }) + return nil + }) + return getCondflowDecisionCaches(i) + }) + }, priority: 0}) +} + +func GetCondflowDecisionServiceProvider(container *do.Injector) (base.GetCondflowDecision, error) { + caches := do.MustInvoke[[]base.CondflowDecision](container) + + return func(code string) (base.CondflowDecision, bool) { + item, flag := lo.Find(caches, func(i base.CondflowDecision) bool { + return i.Code == code || i.Uuid == code + }) + return item, flag + }, nil +} + +func getCondflowDecisionCaches(app *do.Injector) (items []base.CondflowDecision, err error) { + // TODO: 临时测试数据,等待资源表创建后从数据库读取 + // if res, ok := do.MustInvoke[contracts.GetResource](app)("CondflowDecision"); !ok { + // return items, fmt.Errorf("获取 CondflowDecision 资源失败") + // } else { + // items = []base.CondflowDecision{} + // if e := res.GetDBTable(base.GetSystemUser()). + // Select( + // db.T("CondflowDecision").All(), + // ). + // Order(db.I("CondflowDecision.id").Asc()). + // ScanStructs(&items); e != nil { + // err = fmt.Errorf("获取 CondflowDecision 资源失败, %w", e) + // } + // } + items = []base.CondflowDecision{ + { + Uuid: "exec-550e8400-e29b-41d4-a716-446655440001", + ResourceUuid: "550e8400-e29b-41d4-a716-446655440100", // Approval 资源 UUID + Name: "审批执行器", + Code: "ApproveDecision", + Comment: "处理各类审批操作的执行器", + Actions: []base.CondflowDecisionAction{ + { + Name: "自动审批", + Code: "AutoApprove", + ConfigSchema: map[string]any{ + "notify": map[string]any{"type": "boolean", "default": true, "description": "是否发送通知"}, + "message": map[string]any{"type": "string", "description": "通知消息内容"}, + }, + Comment: "小额申请自动审批通过", + }, + { + Name: "经理审批", + Code: "Manager", + ConfigSchema: map[string]any{ + "approver_role": map[string]any{"type": "string", "default": "manager", "description": "审批人角色"}, + "timeout_hours": map[string]any{"type": "integer", "default": 24, "description": "审批超时时间(小时)"}, + "notify": map[string]any{"type": "boolean", "default": true, "description": "是否发送通知"}, + }, + Comment: "中额申请需要经理审批", + }, + { + Name: "总监审批", + Code: "Director", + ConfigSchema: map[string]any{ + "approver_role": map[string]any{"type": "string", "default": "director", "description": "审批人角色"}, + "timeout_hours": map[string]any{"type": "integer", "default": 48, "description": "审批超时时间(小时)"}, + "notify": map[string]any{"type": "boolean", "default": true, "description": "是否发送通知"}, + "cc_roles": map[string]any{"type": "array", "description": "抄送角色列表"}, + }, + Comment: "大额申请需要总监审批", + }, + { + Name: "拒绝", + Code: "Reject", + ConfigSchema: map[string]any{ + "reason": map[string]any{"type": "string", "required": true, "description": "拒绝原因"}, + "notify": map[string]any{"type": "boolean", "default": true, "description": "是否发送通知"}, + "suggest_retry": map[string]any{"type": "boolean", "default": false, "description": "是否建议重试"}, + }, + Comment: "拒绝申请", + }, + }, + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "exec-550e8400-e29b-41d4-a716-446655440002", + ResourceUuid: "550e8400-e29b-41d4-a716-446655440200", // Marketing 资源 UUID + Name: "邮件营销执行器", + Code: "EmailDecision", + Comment: "处理邮件营销活动的执行器", + Actions: []base.CondflowDecisionAction{ + { + Name: "VIP 邮件", + Code: "VIP", + ConfigSchema: map[string]any{ + "template": map[string]any{"type": "string", "required": true, "description": "邮件模板"}, + "subject": map[string]any{"type": "string", "description": "邮件主题"}, + "discount": map[string]any{"type": "integer", "description": "折扣百分比"}, + "valid_days": map[string]any{"type": "integer", "description": "有效天数"}, + "priority": map[string]any{"type": "string", "enum": []string{"low", "normal", "high"}, "description": "优先级"}, + "attachments": map[string]any{"type": "array", "description": "附件列表"}, + }, + Comment: "发送 VIP 专属营销邮件", + }, + { + Name: "欢迎邮件", + Code: "Welcome", + ConfigSchema: map[string]any{ + "template": map[string]any{"type": "string", "required": true, "description": "邮件模板"}, + "subject": map[string]any{"type": "string", "description": "邮件主题"}, + "coupon_code": map[string]any{"type": "string", "description": "优惠券代码"}, + "discount": map[string]any{"type": "integer", "description": "折扣百分比"}, + "trigger_delay": map[string]any{"type": "string", "description": "延迟发送时间"}, + }, + Comment: "发送新用户欢迎邮件", + }, + { + Name: "跳过", + Code: "Skip", + ConfigSchema: map[string]any{ + "reason": map[string]any{"type": "string", "description": "跳过原因"}, + "log": map[string]any{"type": "boolean", "default": true, "description": "是否记录日志"}, + }, + Comment: "跳过邮件发送", + }, + }, + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "exec-550e8400-e29b-41d4-a716-446655440003", + ResourceUuid: "550e8400-e29b-41d4-a716-446655440300", // Order 资源 UUID + Name: "配送执行器", + Code: "ShippingDecision", + Comment: "处理订单配送的执行器", + Actions: []base.CondflowDecisionAction{ + { + Name: "加急配送", + Code: "Express", + ConfigSchema: map[string]any{ + "shipping_method": map[string]any{"type": "string", "default": "express", "description": "配送方式"}, + "shipping_fee": map[string]any{"type": "number", "description": "配送费用"}, + "priority": map[string]any{"type": "string", "enum": []string{"normal", "high", "urgent"}, "description": "优先级"}, + "gift_wrapping": map[string]any{"type": "boolean", "default": false, "description": "是否礼品包装"}, + "insurance": map[string]any{"type": "boolean", "default": false, "description": "是否购买保险"}, + }, + Comment: "加急配送服务", + }, + { + Name: "标准配送", + Code: "Standard", + ConfigSchema: map[string]any{ + "shipping_method": map[string]any{"type": "string", "default": "standard", "description": "配送方式"}, + "estimated_days": map[string]any{"type": "integer", "default": 3, "description": "预计送达天数"}, + }, + Comment: "标准配送服务", + }, + }, + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + { + Uuid: "exec-550e8400-e29b-41d4-a716-446655440004", + ResourceUuid: "550e8400-e29b-41d4-a716-446655440300", // Order 资源 UUID + Name: "通知执行器", + Code: "NotificationDecision", + Comment: "处理客户通知的执行器", + Actions: []base.CondflowDecisionAction{ + { + Name: "发送通知", + Code: "Notify", + ConfigSchema: map[string]any{ + "channels": map[string]any{"type": "array", "required": true, "description": "通知渠道列表"}, + "template": map[string]any{"type": "string", "required": true, "description": "通知模板"}, + "immediate": map[string]any{"type": "boolean", "default": true, "description": "是否立即发送"}, + }, + Comment: "向客户发送通知", + }, + }, + CreatedAt: "2024-01-01 00:00:00", + UpdatedAt: "2024-01-01 00:00:00", + }, + } + + return +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..def0c6a --- /dev/null +++ b/utils.go @@ -0,0 +1,60 @@ +package condflow + +import ( + "reflect" + + "git.fsdpf.net/go/req" +) + +// getDuckDBType 将 Go reflect.Kind 转换为 DuckDB 类型 +func kindToDuckDBType(kind reflect.Kind) string { + switch kind { + case reflect.Bool: + return "BOOLEAN" + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: + return "INTEGER" + case reflect.Int64: + return "BIGINT" + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: + return "UINTEGER" + case reflect.Uint64: + return "UBIGINT" + case reflect.Float32: + return "FLOAT" + case reflect.Float64: + return "DOUBLE" + case reflect.String: + return "VARCHAR" + case reflect.Slice, reflect.Array: + return "JSON" // 数组类型使用 JSON + case reflect.Map, reflect.Struct: + return "JSON" // 复杂类型使用 JSON + case reflect.Ptr, reflect.Interface: + return "VARCHAR" // 指针和接口类型默认使用 VARCHAR + default: + return "VARCHAR" // 默认使用 VARCHAR + } +} + +// kindToSQLite3Type 将 Go reflect.Kind 转换为 SQLite3 类型 +func kindToSQLite3Type(kind reflect.Kind) string { + switch kind { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return "INTEGER" + case reflect.Float32, reflect.Float64: + return "REAL" + case reflect.Slice, reflect.Array, reflect.Map, reflect.Struct: + return "TEXT" // SQLite3 无 JSON 类型,复杂类型序列化后存为 TEXT + default: + return "TEXT" + } +} + +func toActionContext(c FlowContext, res req.Resource) Context { + return &context{ + FlowContext: c, + res: res, + } +}