convert missing context key refactoring

This commit is contained in:
Daisuke Maki 2026-02-17 21:40:04 +09:00
parent a2959c038a
commit e61e114ded
8 changed files with 45 additions and 13 deletions

View file

@ -66,7 +66,7 @@ func (ecf *ExternalCmd) Apply(ctx context.Context, buf []line.Line, out pipeline
defer g.End()
}
query := ctx.Value(queryKey{}).(string)
query := pipeline.QueryFromContext(ctx)
args := append([]string(nil), ecf.args...)
for i, v := range args {
if v == "$QUERY" {

View file

@ -91,7 +91,7 @@ func TestExternalCmd_CancelCleansUpGoroutine(t *testing.T) {
t.Errorf("goroutine leak: before=%d, after=%d", before, runtime.NumGoroutine())
}
func TestExternalCmd_ApplyPanicReturnsError(t *testing.T) {
func TestExternalCmd_ApplyWithoutQueryContext(t *testing.T) {
idgen := &testIDGen{}
lines := []line.Line{
@ -102,12 +102,9 @@ func TestExternalCmd_ApplyPanicReturnsError(t *testing.T) {
out := pipeline.ChanOutput(make(chan line.Line, 256))
// Call Apply with a context that does NOT have the query key set.
// This triggers a nil interface type assertion panic at the line:
// query := ctx.Value(queryKey).(string)
// The bug: this panic was silently swallowed, returning nil error.
// QueryFromContext returns "" safely (no panic).
err := ecf.Apply(context.Background(), lines, out)
require.Error(t, err, "Apply should return an error when an internal panic occurs, not swallow it silently")
require.Contains(t, err.Error(), "panic")
require.NoError(t, err, "Apply with missing query context should not panic or error")
}
func TestExternalCmdFilter_NullSep(t *testing.T) {

View file

@ -1,11 +1,15 @@
package filter
import "context"
import (
"context"
"github.com/peco/peco/pipeline"
)
// newContext initializes the context so that it is suitable
// to be passed to `Run()`
func newContext(ctx context.Context, query string) context.Context {
return context.WithValue(ctx, queryKey{}, query)
return pipeline.NewQueryContext(ctx, query)
}
// sort related stuff

View file

@ -12,6 +12,7 @@ import (
"github.com/peco/peco/internal/util"
"github.com/peco/peco/line"
"github.com/peco/peco/pipeline"
)
// NewFuzzy builds a fuzzy-finder type of filter.
@ -40,7 +41,7 @@ func (ff Fuzzy) String() string {
}
func (ff *Fuzzy) applyInternal(ctx context.Context, lines []line.Line, emit func(line.Line)) error {
originalQuery := ctx.Value(queryKey{}).(string)
originalQuery := pipeline.QueryFromContext(ctx)
// Parse negative terms and compile them as case-insensitive regexps
posTerms, negTerms := SplitQueryTerms(originalQuery)

View file

@ -15,7 +15,6 @@ var ErrFilterNotFound = errors.New("specified filter was not found")
var ignoreCaseFlags = regexpFlagList([]string{"i"})
var defaultFlags = regexpFlagList{}
type queryKey struct{}
// DefaultCustomFilterBufferThreshold is the default value
// for BufferThreshold setting on CustomFilters.

View file

@ -10,6 +10,7 @@ import (
"github.com/peco/peco/internal/util"
"github.com/peco/peco/line"
"github.com/peco/peco/pipeline"
)
func (r regexpFlagList) flags(_ string) []string {
@ -153,7 +154,7 @@ func (f *regexpQueryFactory) Compile(s string, flags regexpFlags, quotemeta bool
}
func (rf *Regexp) applyInternal(ctx context.Context, lines []line.Line, emit func(line.Line)) error {
query := ctx.Value(queryKey{}).(string)
query := pipeline.QueryFromContext(ctx)
posRegexps, negRegexps, err := rf.factory.Compile(query, rf.flags, rf.quotemeta)
if err != nil {
return fmt.Errorf("failed to compile queries as regular expression: %w", err)

View file

@ -9,6 +9,19 @@ import (
"github.com/peco/peco/line"
)
type queryContextKey struct{}
// NewQueryContext returns a context with the query string stored under a typed key.
func NewQueryContext(ctx context.Context, query string) context.Context {
return context.WithValue(ctx, queryContextKey{}, query)
}
// QueryFromContext retrieves the query string from the context.
func QueryFromContext(ctx context.Context) string {
v, _ := ctx.Value(queryContextKey{}).(string)
return v
}
func NilOutput(ctx context.Context) ChanOutput {
ch := make(chan line.Line)
go func() {
@ -86,7 +99,7 @@ func (p *Pipeline) SetDestination(d Destination) {
// called while `Run` is running.
func (p *Pipeline) Run(ctx context.Context) (err error) {
if pdebug.Enabled {
g := pdebug.Marker("Pipeline.Run (%s)", ctx.Value("query")).BindError(&err)
g := pdebug.Marker("Pipeline.Run (%s)", QueryFromContext(ctx)).BindError(&err)
defer g.End()
}
p.mutex.Lock()

View file

@ -108,6 +108,23 @@ func (r *Receiver) Accept(ctx context.Context, in <-chan line.Line, out ChanOutp
}
}
func TestQueryContext(t *testing.T) {
t.Run("round-trip", func(t *testing.T) {
ctx := NewQueryContext(context.Background(), "hello")
got := QueryFromContext(ctx)
if got != "hello" {
t.Fatalf("expected %q, got %q", "hello", got)
}
})
t.Run("missing key returns empty", func(t *testing.T) {
got := QueryFromContext(context.Background())
if got != "" {
t.Fatalf("expected empty string, got %q", got)
}
})
}
func TestPipeline(t *testing.T) {
src := NewLineFeeder(strings.NewReader(`foo
bar