internal/filter/filter.go

// Package filter is the public entry point for request filtering. The
// expression language itself lives in internal/filter/expr.
//
// mercemay.top/src/httptap/
package filter

import (
	"strings"

	"mercemay.top/httptap/internal/filter/expr"
	"mercemay.top/httptap/internal/parser"
)

// Predicate is a compiled filter expression.
type Predicate func(parser.Message) bool

// Compile parses a string expression into a Predicate. An empty string
// produces a predicate that matches everything.
func Compile(source string) (Predicate, error) {
	if strings.TrimSpace(source) == "" {
		return func(parser.Message) bool { return true }, nil
	}
	node, err := expr.Parse(source)
	if err != nil {
		return nil, err
	}
	return func(m parser.Message) bool {
		return expr.Eval(node, msgEnv{m})
	}, nil
}

// MustCompile panics on error. Used by tests.
func MustCompile(source string) Predicate {
	p, err := Compile(source)
	if err != nil {
		panic(err)
	}
	return p
}

// msgEnv is the expr.Env implementation over parser.Message.
type msgEnv struct{ m parser.Message }

// Lookup returns the value of a field such as "method", "host", "path",
// "status", "header:content-type".
func (e msgEnv) Lookup(name string) (expr.Value, bool) {
	switch name {
	case "method":
		return expr.S(parser.Method(e.m)), true
	case "host":
		return expr.S(parser.Header(e.m, "Host")), true
	case "path":
		return expr.S(parser.Path(e.m)), true
	case "scheme":
		return expr.S("https"), true
	case "status":
		return expr.N(float64(parser.StatusCode(e.m))), true
	case "body_size":
		return expr.N(float64(len(e.m.Body))), true
	}
	if strings.HasPrefix(name, "header:") {
		return expr.S(parser.Header(e.m, name[len("header:"):])), true
	}
	return expr.Value{}, false
}

// KnownFields lists the identifiers the filter language accepts. It is
// surfaced by cmd/httptap/cmd/doctor and by the completion subcommand.
var KnownFields = []string{
	"method", "host", "path", "scheme",
	"status", "body_size", "header:<name>",
}