diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..250b415 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +.PHONY: all + +all: + go build -v ./... + go test -race -v ./... diff --git a/cmd.go b/cmd.go new file mode 100644 index 0000000..4f2163c --- /dev/null +++ b/cmd.go @@ -0,0 +1 @@ +package cmd // import "hookt.dev/cmd" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5777b26 --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module hookt.dev/cmd + +go 1.22.3 + +require ( + github.com/Masterminds/sprig v2.22.0+incompatible + github.com/google/go-cmp v0.6.0 + github.com/itchyny/gojq v0.12.16 + github.com/lmittmann/tint v1.0.4 + github.com/spf13/cobra v1.8.0 + github.com/spf13/pflag v1.0.5 + golang.org/x/sync v0.7.0 + sigs.k8s.io/yaml v1.4.0 +) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver v1.5.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/itchyny/timefmt-go v0.1.6 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect + golang.org/x/crypto v0.24.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bd0ef79 --- /dev/null +++ b/go.sum @@ -0,0 +1,49 @@ +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= +github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +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/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= +github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= +github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= +github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= +github.com/lmittmann/tint v1.0.4 h1:LeYihpJ9hyGvE0w+K2okPTGUdVLfng1+nDNVR4vWISc= +github.com/lmittmann/tint v1.0.4/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +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/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/hkt/main.go b/hkt/main.go index 7be5012..20c3a60 100644 --- a/hkt/main.go +++ b/hkt/main.go @@ -7,6 +7,7 @@ import ( "os/signal" "hookt.dev/cmd/pkg/command" + "hookt.dev/cmd/pkg/trace" "github.com/spf13/cobra" ) @@ -60,7 +61,12 @@ func newRunCommand(ctx context.Context, app *command.App) *cobra.Command { cmd := &cobra.Command{ Use: "run", Args: cobra.ExactArgs(1), - RunE: func(_ *cobra.Command, files []string) error { + RunE: func(cmd *cobra.Command, files []string) error { + if cmd.Flags().Changed("debug") { + ctx = trace.WithJob(ctx, trace.LogJob()) + ctx = trace.WithPattern(ctx, trace.LogPattern()) + } + s, err := app.Engine.Run(ctx, files[0]) if err != nil { return err diff --git a/pkg/hookt/engine.go b/pkg/hookt/engine.go index aa6d5a4..3c8b369 100644 --- a/pkg/hookt/engine.go +++ b/pkg/hookt/engine.go @@ -2,15 +2,15 @@ package hookt import ( "context" - "io/ioutil" "log/slog" + "os" "github.com/lmittmann/tint" + "golang.org/x/sync/errgroup" "hookt.dev/cmd/pkg/check" "hookt.dev/cmd/pkg/errors" "hookt.dev/cmd/pkg/plugin/builtin" "hookt.dev/cmd/pkg/proto" - "golang.org/x/sync/errgroup" ) var plugins []proto.Interface @@ -38,7 +38,7 @@ func New(opts ...func(*Engine)) *Engine { } func (e *Engine) Run(ctx context.Context, file string) (*check.S, error) { - p, err := ioutil.ReadFile(file) + p, err := os.ReadFile(file) if err != nil { return nil, errors.New("failed to read file: %w", err) } diff --git a/pkg/proto/pattern.go b/pkg/proto/pattern.go index d925548..5080711 100644 --- a/pkg/proto/pattern.go +++ b/pkg/proto/pattern.go @@ -2,10 +2,13 @@ package proto import ( "context" + "fmt" "log/slog" + "text/template" "hookt.dev/cmd/pkg/errors" "hookt.dev/cmd/pkg/proto/wire" + "hookt.dev/cmd/pkg/trace" "github.com/google/go-cmp/cmp" "github.com/itchyny/gojq" @@ -41,34 +44,62 @@ func (p Pattern) Match(ctx context.Context, obj any) (bool, error) { } func (p *P) Pattern(ctx context.Context, obj wire.Object) (Pattern, error) { - pt := make(Pattern) + var ( + pt = make(Pattern) + tr = trace.ContextPattern(ctx) + err error + ) for k, raw := range obj { - q, err := gojq.Parse(k) - if err != nil { - return nil, errors.New("failed to parse jq %q: %w", k, err) + q, e := gojq.Parse(k) + tr.ParseKey(k, q, e) + if e != nil { + err = errors.Join( + err, + errors.New("failed to parse jq %q: %w", k, e), + ) + continue } - var v any + var want any - if err := yaml.Unmarshal(raw, &v); err != nil { - return nil, errors.New("failed to parse value for jq %q: %w", k, err) + e = yaml.Unmarshal(raw, &want) + tr.UnmarshalValue(k, raw, want, e) + if e != nil { + err = errors.Join( + err, + errors.New("failed to parse value for jq %q: %w", k, e), + ) + continue } slog.Debug("building pattern", "key", k, - "pattern", v, + "pattern", want, ) - switch v := v.(type) { + switch want := want.(type) { case bool: - pt[q] = func(_ context.Context, x any) (bool, error) { return v || x == nil, nil } + pt[q] = func(_ context.Context, got any) (bool, error) { return want || got == nil, nil } case string: - pt[q] = p.t.Match(ctx, v) + tv := tr.TemplateValue + tr.TemplateValue = func(_, v string, t *template.Template, e error) { tv(k, v, t, e) } + pt[q] = p.t.Match(trace.WithPattern(ctx, tr), want) default: - pt[q] = func(_ context.Context, x any) (bool, error) { return cmp.Equal(x, v), nil } + pt[q] = func(_ context.Context, got any) (bool, error) { + ok := cmpEqual(want, got) + tr.EqualMatch(k, want, got, ok) + return ok, nil + } } } - return pt, nil + return pt, err +} + +func cmpEqual(want, got any) bool { + if fmt.Sprint(want) == fmt.Sprint(got) { + return true + } + return cmp.Equal(want, got) } diff --git a/pkg/proto/pattern_test.go b/pkg/proto/pattern_test.go index e62c7ea..82f59e1 100644 --- a/pkg/proto/pattern_test.go +++ b/pkg/proto/pattern_test.go @@ -2,9 +2,11 @@ package proto_test import ( "context" + "strconv" "testing" "hookt.dev/cmd/pkg/proto/wire" + "hookt.dev/cmd/pkg/trace" ) func TestPattern(t *testing.T) { @@ -74,7 +76,7 @@ func TestPattern(t *testing.T) { }, 5: { wire.Object{ - ".foo.bar": []byte(`"${{ setvar "bar" . }}"`), + ".foo.bar": []byte(strconv.Quote(`${{ setvar "bar" . }}`)), }, map[string]any{ "foo": map[string]any{ @@ -85,7 +87,7 @@ func TestPattern(t *testing.T) { }, 6: { wire.Object{ - ".foo.bar": []byte(`"${{ var "bar" }}"`), + ".foo.bar": []byte(strconv.Quote(`${{ var "bar" }}`)), }, map[string]any{ "foo": map[string]any{ @@ -136,6 +138,7 @@ func TestPattern(t *testing.T) { p := newP() ctx := context.Background() + ctx = trace.WithPattern(ctx, trace.LogPattern()) for _, cas := range cases { t.Run("", func(t *testing.T) { @@ -146,7 +149,7 @@ func TestPattern(t *testing.T) { ok, err := pt.Match(ctx, cas.obj) if err != nil { - t.Fatal(err) + t.Fatalf("match: Match()=%+v", err) } if ok != cas.ok { diff --git a/pkg/proto/proto.go b/pkg/proto/proto.go index b05d1ea..54cd65b 100644 --- a/pkg/proto/proto.go +++ b/pkg/proto/proto.go @@ -61,7 +61,7 @@ func (p *P) Parse(ctx context.Context, q []byte) (*Workflow, error) { var ( w Workflow - tr = trace.ContextJobTrace(ctx) + tr = trace.ContextJob(ctx) ) w.Jobs = make([]Job, len(raw.Jobs)) diff --git a/pkg/proto/proto_test.go b/pkg/proto/proto_test.go index 3c24a0d..b772007 100644 --- a/pkg/proto/proto_test.go +++ b/pkg/proto/proto_test.go @@ -2,15 +2,29 @@ package proto_test import ( "context" - "io/ioutil" + "log/slog" + "os" "testing" + "time" "hookt.dev/cmd/pkg/plugin/builtin" "hookt.dev/cmd/pkg/proto" - "github.com/davecgh/go-spew/spew" + "github.com/lmittmann/tint" ) +func init() { + slog.SetDefault( + slog.New( + tint.NewHandler(os.Stderr, &tint.Options{ + AddSource: true, + Level: slog.LevelDebug, + TimeFormat: time.Kitchen, + }), + ), + ) +} + func newP() *proto.P { p := builtin.Plugins() q := make([]proto.Interface, len(p)) @@ -32,13 +46,15 @@ func TestParse(t *testing.T) { t.Fatal(err) } - spew.Dump(w) + _ = w + + // spew.Dump(w) } func file(t *testing.T, path string) []byte { t.Helper() - p, err := ioutil.ReadFile(path) + p, err := os.ReadFile(path) if err != nil { t.Fatal(err) } diff --git a/pkg/proto/template.go b/pkg/proto/template.go index d49bddc..02b727e 100644 --- a/pkg/proto/template.go +++ b/pkg/proto/template.go @@ -10,9 +10,9 @@ import ( "hookt.dev/cmd/pkg/async" "hookt.dev/cmd/pkg/errors" + "hookt.dev/cmd/pkg/trace" "github.com/Masterminds/sprig" - "github.com/google/go-cmp/cmp" "sigs.k8s.io/yaml" ) @@ -64,28 +64,39 @@ func (t *T) funcs() template.FuncMap { } func (t *T) Match(ctx context.Context, data string) func(context.Context, any) (bool, error) { + tr := trace.ContextPattern(ctx) tmpl, err := t.Parse("", data) + tr.TemplateValue("", data, tmpl, err) if err != nil { return func(context.Context, any) (bool, error) { return false, err } } - return func(_ context.Context, x any) (bool, error) { - var buf bytes.Buffer - - if err := tmpl.Execute(&buf, x); err != nil { + return func(ctx context.Context, got any) (bool, error) { + var ( + buf bytes.Buffer + tr = trace.ContextPattern(ctx) + ) + + err := tmpl.Execute(&buf, got) + tr.ExecuteMatch("", buf.Bytes(), err) + if err != nil { return false, errors.New("failed to evaluate %q: %w", data, err) } - var v any + var want any - if err := yaml.Unmarshal(buf.Bytes(), &v); err != nil { + err = yaml.Unmarshal(buf.Bytes(), &want) + tr.UnmarshalMatch("", buf.Bytes(), want, err) + if err != nil { return false, errors.New("failed to parse result: %w", err) } - switch v := v.(type) { + switch want := want.(type) { case bool: - return v, nil + return want, nil default: - return cmp.Equal(x, v), nil + ok := cmpEqual(want, got) + tr.EqualMatch("", want, got, ok) + return ok, nil } } } diff --git a/pkg/proto/wire/wire_test.go b/pkg/proto/wire/wire_test.go index 02c3e84..82a7875 100644 --- a/pkg/proto/wire/wire_test.go +++ b/pkg/proto/wire/wire_test.go @@ -1,7 +1,7 @@ package wire_test import ( - "io/ioutil" + "os" "path/filepath" "testing" @@ -36,7 +36,7 @@ func TestParse(t *testing.T) { func file(t *testing.T, path string) []byte { t.Helper() - p, err := ioutil.ReadFile(path) + p, err := os.ReadFile(path) if err != nil { t.Fatal(err) } diff --git a/pkg/trace/trace.go b/pkg/trace/trace.go index 8653849..f900eea 100644 --- a/pkg/trace/trace.go +++ b/pkg/trace/trace.go @@ -2,28 +2,158 @@ package trace import ( "context" + "fmt" + "log/slog" + "text/template" "hookt.dev/cmd/pkg/proto/wire" + + "github.com/itchyny/gojq" + "github.com/lmittmann/tint" +) + +var ( + nopJob = JobTrace{ + WireJob: func(int, *wire.Job) {}, + WirePlugin: func(int, *wire.Plugin, any) {}, + WireStep: func(int, *wire.Step, any) {}, + RunStep: func() {}, + MatchStep: func() {}, + TapMessage: func() {}, + } + nopPattern = PatternTrace{ + ParseKey: func(string, *gojq.Query, error) {}, + UnmarshalValue: func(string, []byte, any, error) {}, + TemplateValue: func(string, string, *template.Template, error) {}, + ExecuteMatch: func(string, []byte, error) {}, + UnmarshalMatch: func(string, []byte, any, error) {}, + EqualMatch: func(string, any, any, bool) {}, + } ) -var nop = &JobTrace{ - WireJob: func(int, *wire.Job) {}, - WirePlugin: func(int, *wire.Plugin, any) {}, - WireStep: func(int, *wire.Step, any) {}, - RunStep: func() {}, - MatchStep: func() {}, - TapMessage: func() {}, +func LogJob() JobTrace { + return JobTrace{ + WireJob: func(int, *wire.Job) {}, + WirePlugin: func(int, *wire.Plugin, any) {}, + WireStep: func(int, *wire.Step, any) {}, + RunStep: func() {}, + MatchStep: func() {}, + TapMessage: func() {}, + } +} + +func LogPattern() PatternTrace { + return PatternTrace{ + ParseKey: func(key string, q *gojq.Query, err error) { + if err != nil { + slog.Error("trace: ParseKey", + "key", key, + tint.Err(err), + ) + } else { + slog.Info("trace: ParseKey", + "key", key, + ) + } + }, + UnmarshalValue: func(key string, p []byte, v any, err error) { + if err != nil { + slog.Error("trace: UnmarshalValue", + "key", key, + "raw", string(p), + "value", v, + tint.Err(err), + ) + } else { + slog.Info("trace: UnmarshalValue", + "key", key, + "raw", string(p), + "value", v, + ) + } + }, + TemplateValue: func(key string, value string, t *template.Template, err error) { + if err != nil { + slog.Error("trace: TemplateValue", + "key", key, + "value", value, + tint.Err(err), + ) + } else { + slog.Info("trace: TemplateValue", + "key", key, + "value", value, + ) + } + }, + ExecuteMatch: func(key string, p []byte, err error) { + if err != nil { + slog.Error("trace: ExecuteMatch", + "key", key, + "raw", string(p), + tint.Err(err), + ) + } else { + slog.Info("trace: ExecuteMatch", + "key", key, + "raw", string(p), + ) + } + }, + UnmarshalMatch: func(key string, p []byte, v any, err error) { + if err != nil { + slog.Error("trace: UnmarshalMatch", + "key", key, + "raw", string(p), + "value", v, + tint.Err(err), + ) + } else { + slog.Info("trace: UnmarshalMatch", + "key", key, + "raw", string(p), + "value", v, + ) + } + }, + EqualMatch: func(key string, want any, got any, ok bool) { + if !ok { + slog.Error("trace: EqualMatch", + "key", key, + "want", fmt.Sprintf("%+[1]v (%[1]T)", want), + "got", fmt.Sprintf("%+[1]v (%[1]T)", got), + ) + } else { + slog.Info("trace: EqualMatch", + "key", key, + "want", fmt.Sprintf("%+[1]v (%[1]T)", want), + "got", fmt.Sprintf("%+[1]v (%[1]T)", got), + ) + } + }, + } } -func WithJobTrace(ctx context.Context, trace *JobTrace) context.Context { - return with(ctx, trace) +func WithJob(ctx context.Context, trace JobTrace) context.Context { + return with(ctx, &trace) } -func ContextJobTrace(ctx context.Context) *JobTrace { +func ContextJob(ctx context.Context) JobTrace { if trace := from[JobTrace](ctx); trace != nil { - return trace + return *trace + } + return nopJob +} + +func WithPattern(ctx context.Context, trace PatternTrace) context.Context { + return with(ctx, &trace) +} + +func ContextPattern(ctx context.Context) PatternTrace { + if trace := from[PatternTrace](ctx); trace != nil { + return *trace } - return nop + return nopPattern } type JobTrace struct { @@ -36,4 +166,13 @@ type JobTrace struct { TapMessage func() } +type PatternTrace struct { + ParseKey func(string, *gojq.Query, error) + UnmarshalValue func(string, []byte, any, error) + TemplateValue func(string, string, *template.Template, error) + ExecuteMatch func(string, []byte, error) + UnmarshalMatch func(string, []byte, any, error) + EqualMatch func(string, any, any, bool) +} + type EventInfo struct{}