// 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>",
}