Rename search package to filter package

This commit is contained in:
David Stotijn 2022-03-31 15:12:54 +02:00
parent 2ce4218a30
commit aa9822854d
No known key found for this signature in database
GPG key ID: B23243A9C47CEE2D
21 changed files with 364 additions and 362 deletions

View file

@ -35,7 +35,7 @@ linters-settings:
godot: godot:
capital: true capital: true
ireturn: ireturn:
allow: "error,empty,anon,stdlib,.*(or|er)$,github.com/99designs/gqlgen/graphql.Marshaler,github.com/dstotijn/hetty/pkg/api.QueryResolver,github.com/dstotijn/hetty/pkg/search.Expression" allow: "error,empty,anon,stdlib,.*(or|er)$,github.com/99designs/gqlgen/graphql.Marshaler,github.com/dstotijn/hetty/pkg/api.QueryResolver,github.com/dstotijn/hetty/pkg/filter.Expression"
issues: issues:
exclude-rules: exclude-rules:

View file

@ -18,12 +18,12 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/vektah/gqlparser/v2/gqlerror" "github.com/vektah/gqlparser/v2/gqlerror"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/proj" "github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/proxy" "github.com/dstotijn/hetty/pkg/proxy"
"github.com/dstotijn/hetty/pkg/proxy/intercept" "github.com/dstotijn/hetty/pkg/proxy/intercept"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
"github.com/dstotijn/hetty/pkg/sender" "github.com/dstotijn/hetty/pkg/sender"
) )
@ -639,7 +639,7 @@ func (r *mutationResolver) UpdateInterceptSettings(
} }
if input.RequestFilter != nil && *input.RequestFilter != "" { if input.RequestFilter != nil && *input.RequestFilter != "" {
expr, err := search.ParseQuery(*input.RequestFilter) expr, err := filter.ParseQuery(*input.RequestFilter)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse request filter: %w", err) return nil, fmt.Errorf("could not parse request filter: %w", err)
} }
@ -648,7 +648,7 @@ func (r *mutationResolver) UpdateInterceptSettings(
} }
if input.ResponseFilter != nil && *input.ResponseFilter != "" { if input.ResponseFilter != nil && *input.ResponseFilter != "" {
expr, err := search.ParseQuery(*input.ResponseFilter) expr, err := filter.ParseQuery(*input.ResponseFilter)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not parse response filter: %w", err) return nil, fmt.Errorf("could not parse response filter: %w", err)
} }
@ -916,43 +916,43 @@ func scopeToScopeRules(rules []scope.Rule) []ScopeRule {
return scopeRules return scopeRules
} }
func findRequestsFilterFromInput(input *HTTPRequestLogFilterInput) (filter reqlog.FindRequestsFilter, err error) { func findRequestsFilterFromInput(input *HTTPRequestLogFilterInput) (findFilter reqlog.FindRequestsFilter, err error) {
if input == nil { if input == nil {
return return
} }
if input.OnlyInScope != nil { if input.OnlyInScope != nil {
filter.OnlyInScope = *input.OnlyInScope findFilter.OnlyInScope = *input.OnlyInScope
} }
if input.SearchExpression != nil && *input.SearchExpression != "" { if input.SearchExpression != nil && *input.SearchExpression != "" {
expr, err := search.ParseQuery(*input.SearchExpression) expr, err := filter.ParseQuery(*input.SearchExpression)
if err != nil { if err != nil {
return reqlog.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err) return reqlog.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
} }
filter.SearchExpr = expr findFilter.SearchExpr = expr
} }
return return
} }
func findSenderRequestsFilterFromInput(input *SenderRequestFilterInput) (filter sender.FindRequestsFilter, err error) { func findSenderRequestsFilterFromInput(input *SenderRequestFilterInput) (findFilter sender.FindRequestsFilter, err error) {
if input == nil { if input == nil {
return return
} }
if input.OnlyInScope != nil { if input.OnlyInScope != nil {
filter.OnlyInScope = *input.OnlyInScope findFilter.OnlyInScope = *input.OnlyInScope
} }
if input.SearchExpression != nil && *input.SearchExpression != "" { if input.SearchExpression != nil && *input.SearchExpression != "" {
expr, err := search.ParseQuery(*input.SearchExpression) expr, err := filter.ParseQuery(*input.SearchExpression)
if err != nil { if err != nil {
return sender.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err) return sender.FindRequestsFilter{}, fmt.Errorf("could not parse search query: %w", err)
} }
filter.SearchExpr = expr findFilter.SearchExpr = expr
} }
return return

View file

@ -15,9 +15,9 @@ import (
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/proj" "github.com/dstotijn/hetty/pkg/proj"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
) )
//nolint:gosec //nolint:gosec
@ -45,7 +45,7 @@ func TestUpsertProject(t *testing.T) {
database := DatabaseFromBadgerDB(badgerDB) database := DatabaseFromBadgerDB(badgerDB)
defer database.Close() defer database.Close()
searchExpr, err := search.ParseQuery("foo AND bar OR NOT baz") searchExpr, err := filter.ParseQuery("foo AND bar OR NOT baz")
if err != nil { if err != nil {
t.Fatalf("unexpected error (expected: nil, got: %v)", err) t.Fatalf("unexpected error (expected: nil, got: %v)", err)
} }

View file

@ -1,4 +1,4 @@
package search package filter
import ( import (
"encoding/gob" "encoding/gob"
@ -78,8 +78,10 @@ func (rl *RegexpLiteral) UnmarshalBinary(data []byte) error {
} }
func init() { func init() {
gob.Register(PrefixExpression{}) // The `filter` package was previously named `search`.
gob.Register(InfixExpression{}) // We use the legacy names for backwards compatibility with existing database data.
gob.Register(StringLiteral{}) gob.RegisterName("github.com/dstotijn/hetty/pkg/search.PrefixExpression", PrefixExpression{})
gob.Register(RegexpLiteral{}) gob.RegisterName("github.com/dstotijn/hetty/pkg/search.InfixExpression", InfixExpression{})
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.StringLiteral", StringLiteral{})
gob.RegisterName("github.com/dstotijn/hetty/pkg/search.RegexpLiteral", RegexpLiteral{})
} }

212
pkg/filter/ast_test.go Normal file
View file

@ -0,0 +1,212 @@
package filter_test
import (
"regexp"
"testing"
"github.com/dstotijn/hetty/pkg/filter"
)
func TestExpressionString(t *testing.T) {
t.Parallel()
tests := []struct {
name string
expression filter.Expression
expected string
}{
{
name: "string literal expression",
expression: filter.StringLiteral{Value: "foobar"},
expected: `"foobar"`,
},
{
name: "boolean expression with equal operator",
expression: filter.InfixExpression{
Operator: filter.TokOpEq,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
expected: `("foo" = "bar")`,
},
{
name: "boolean expression with not equal operator",
expression: filter.InfixExpression{
Operator: filter.TokOpNotEq,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
expected: `("foo" != "bar")`,
},
{
name: "boolean expression with greater than operator",
expression: filter.InfixExpression{
Operator: filter.TokOpGt,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
expected: `("foo" > "bar")`,
},
{
name: "boolean expression with less than operator",
expression: filter.InfixExpression{
Operator: filter.TokOpLt,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
expected: `("foo" < "bar")`,
},
{
name: "boolean expression with greater than or equal operator",
expression: filter.InfixExpression{
Operator: filter.TokOpGtEq,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
expected: `("foo" >= "bar")`,
},
{
name: "boolean expression with less than or equal operator",
expression: filter.InfixExpression{
Operator: filter.TokOpLtEq,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
expected: `("foo" <= "bar")`,
},
{
name: "boolean expression with regular expression operator",
expression: filter.InfixExpression{
Operator: filter.TokOpRe,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.RegexpLiteral{regexp.MustCompile("bar")},
},
expected: `("foo" =~ "bar")`,
},
{
name: "boolean expression with not regular expression operator",
expression: filter.InfixExpression{
Operator: filter.TokOpNotRe,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.RegexpLiteral{regexp.MustCompile("bar")},
},
expected: `("foo" !~ "bar")`,
},
{
name: "boolean expression with AND, OR and NOT operators",
expression: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.InfixExpression{
Operator: filter.TokOpOr,
Left: filter.StringLiteral{Value: "bar"},
Right: filter.PrefixExpression{
Operator: filter.TokOpNot,
Right: filter.StringLiteral{Value: "baz"},
},
},
},
expected: `("foo" AND ("bar" OR (NOT "baz")))`,
},
{
name: "boolean expression with nested group",
expression: filter.InfixExpression{
Operator: filter.TokOpOr,
Left: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
Right: filter.PrefixExpression{
Operator: filter.TokOpNot,
Right: filter.StringLiteral{Value: "baz"},
},
},
expected: `(("foo" AND "bar") OR (NOT "baz"))`,
},
{
name: "implicit boolean expression with string literal operands",
expression: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
Right: filter.StringLiteral{Value: "baz"},
},
expected: `(("foo" AND "bar") AND "baz")`,
},
{
name: "implicit boolean expression nested in group",
expression: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
expected: `("foo" AND "bar")`,
},
{
name: "implicit and explicit boolean expression with string literal operands",
expression: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.InfixExpression{
Operator: filter.TokOpOr,
Left: filter.StringLiteral{Value: "bar"},
Right: filter.StringLiteral{Value: "baz"},
},
},
Right: filter.StringLiteral{Value: "yolo"},
},
expected: `(("foo" AND ("bar" OR "baz")) AND "yolo")`,
},
{
name: "implicit boolean expression with comparison operands",
expression: filter.InfixExpression{
Operator: filter.TokOpAnd,
Left: filter.InfixExpression{
Operator: filter.TokOpEq,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
Right: filter.InfixExpression{
Operator: filter.TokOpRe,
Left: filter.StringLiteral{Value: "baz"},
Right: filter.RegexpLiteral{regexp.MustCompile("yolo")},
},
},
expected: `(("foo" = "bar") AND ("baz" =~ "yolo"))`,
},
{
name: "eq operator takes precedence over boolean ops",
expression: filter.InfixExpression{
Operator: filter.TokOpOr,
Left: filter.InfixExpression{
Operator: filter.TokOpEq,
Left: filter.StringLiteral{Value: "foo"},
Right: filter.StringLiteral{Value: "bar"},
},
Right: filter.InfixExpression{
Operator: filter.TokOpEq,
Left: filter.StringLiteral{Value: "baz"},
Right: filter.StringLiteral{Value: "yolo"},
},
},
expected: `(("foo" = "bar") OR ("baz" = "yolo"))`,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := tt.expression.String()
if tt.expected != got {
t.Errorf("expected: %v, got: %v", tt.expected, got)
}
})
}
}

View file

@ -1,4 +1,4 @@
package search package filter
import ( import (
"errors" "errors"
@ -15,7 +15,7 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
case TokOpEq: case TokOpEq:
strLiteral, ok := expr.(StringLiteral) strLiteral, ok := expr.(StringLiteral)
if !ok { if !ok {
return false, errors.New("search: expression must be a string literal") return false, errors.New("filter: expression must be a string literal")
} }
// Return `true` if at least one header (<key>: <value>) is equal to the string literal. // Return `true` if at least one header (<key>: <value>) is equal to the string literal.
@ -31,7 +31,7 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
case TokOpNotEq: case TokOpNotEq:
strLiteral, ok := expr.(StringLiteral) strLiteral, ok := expr.(StringLiteral)
if !ok { if !ok {
return false, errors.New("search: expression must be a string literal") return false, errors.New("filter: expression must be a string literal")
} }
// Return `true` if none of the headers (<key>: <value>) are equal to the string literal. // Return `true` if none of the headers (<key>: <value>) are equal to the string literal.
@ -47,7 +47,7 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
case TokOpRe: case TokOpRe:
re, ok := expr.(RegexpLiteral) re, ok := expr.(RegexpLiteral)
if !ok { if !ok {
return false, errors.New("search: expression must be a regular expression") return false, errors.New("filter: expression must be a regular expression")
} }
// Return `true` if at least one header (<key>: <value>) matches the regular expression. // Return `true` if at least one header (<key>: <value>) matches the regular expression.
@ -63,7 +63,7 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
case TokOpNotRe: case TokOpNotRe:
re, ok := expr.(RegexpLiteral) re, ok := expr.(RegexpLiteral)
if !ok { if !ok {
return false, errors.New("search: expression must be a regular expression") return false, errors.New("filter: expression must be a regular expression")
} }
// Return `true` if none of the headers (<key>: <value>) match the regular expression. // Return `true` if none of the headers (<key>: <value>) match the regular expression.
@ -77,6 +77,6 @@ func MatchHTTPHeaders(op TokenType, expr Expression, headers http.Header) (bool,
return true, nil return true, nil
default: default:
return false, fmt.Errorf("search: unsupported operator %q", op.String()) return false, fmt.Errorf("filter: unsupported operator %q", op.String())
} }
} }

View file

@ -1,4 +1,4 @@
package search package filter
import ( import (
"fmt" "fmt"

View file

@ -1,4 +1,4 @@
package search package filter
import "testing" import "testing"

View file

@ -1,4 +1,4 @@
package search package filter
import ( import (
"fmt" "fmt"
@ -88,7 +88,7 @@ func ParseQuery(input string) (expr Expression, err error) {
p.nextToken() p.nextToken()
if p.curTokenIs(TokEOF) { if p.curTokenIs(TokEOF) {
return nil, fmt.Errorf("search: unexpected EOF") return nil, fmt.Errorf("filter: unexpected EOF")
} }
for !p.curTokenIs(TokEOF) { for !p.curTokenIs(TokEOF) {
@ -96,7 +96,7 @@ func ParseQuery(input string) (expr Expression, err error) {
switch { switch {
case err != nil: case err != nil:
return nil, fmt.Errorf("search: could not parse expression: %w", err) return nil, fmt.Errorf("filter: could not parse expression: %w", err)
case expr == nil: case expr == nil:
expr = right expr = right
default: default:

View file

@ -1,4 +1,4 @@
package search package filter
import ( import (
"errors" "errors"
@ -20,7 +20,7 @@ func TestParseQuery(t *testing.T) {
name: "empty query", name: "empty query",
input: "", input: "",
expectedExpression: nil, expectedExpression: nil,
expectedError: errors.New("search: unexpected EOF"), expectedError: errors.New("filter: unexpected EOF"),
}, },
{ {
name: "string literal expression", name: "string literal expression",

View file

@ -11,10 +11,10 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/proxy/intercept" "github.com/dstotijn/hetty/pkg/proxy/intercept"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
"github.com/dstotijn/hetty/pkg/sender" "github.com/dstotijn/hetty/pkg/sender"
) )
@ -59,17 +59,17 @@ type Settings struct {
// Request log settings // Request log settings
ReqLogBypassOutOfScope bool ReqLogBypassOutOfScope bool
ReqLogOnlyFindInScope bool ReqLogOnlyFindInScope bool
ReqLogSearchExpr search.Expression ReqLogSearchExpr filter.Expression
// Intercept settings // Intercept settings
InterceptRequests bool InterceptRequests bool
InterceptResponses bool InterceptResponses bool
InterceptRequestFilter search.Expression InterceptRequestFilter filter.Expression
InterceptResponseFilter search.Expression InterceptResponseFilter filter.Expression
// Sender settings // Sender settings
SenderOnlyFindInScope bool SenderOnlyFindInScope bool
SenderSearchExpr search.Expression SenderSearchExpr filter.Expression
// Scope settings // Scope settings
ScopeRules []scope.Rule ScopeRules []scope.Rule

View file

@ -10,8 +10,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
) )
//nolint:unparam //nolint:unparam
@ -68,22 +68,22 @@ var resFilterKeyFns = map[string]func(res *http.Response) (string, error){
} }
// MatchRequestFilter returns true if an HTTP request matches the request filter expression. // MatchRequestFilter returns true if an HTTP request matches the request filter expression.
func MatchRequestFilter(req *http.Request, expr search.Expression) (bool, error) { func MatchRequestFilter(req *http.Request, expr filter.Expression) (bool, error) {
switch e := expr.(type) { switch e := expr.(type) {
case search.PrefixExpression: case filter.PrefixExpression:
return matchReqPrefixExpr(req, e) return matchReqPrefixExpr(req, e)
case search.InfixExpression: case filter.InfixExpression:
return matchReqInfixExpr(req, e) return matchReqInfixExpr(req, e)
case search.StringLiteral: case filter.StringLiteral:
return matchReqStringLiteral(req, e) return matchReqStringLiteral(req, e)
default: default:
return false, fmt.Errorf("expression type (%T) not supported", expr) return false, fmt.Errorf("expression type (%T) not supported", expr)
} }
} }
func matchReqPrefixExpr(req *http.Request, expr search.PrefixExpression) (bool, error) { func matchReqPrefixExpr(req *http.Request, expr filter.PrefixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpNot: case filter.TokOpNot:
match, err := MatchRequestFilter(req, expr.Right) match, err := MatchRequestFilter(req, expr.Right)
if err != nil { if err != nil {
return false, err return false, err
@ -95,9 +95,9 @@ func matchReqPrefixExpr(req *http.Request, expr search.PrefixExpression) (bool,
} }
} }
func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, error) { func matchReqInfixExpr(req *http.Request, expr filter.InfixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpAnd: case filter.TokOpAnd:
left, err := MatchRequestFilter(req, expr.Left) left, err := MatchRequestFilter(req, expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -109,7 +109,7 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
} }
return left && right, nil return left && right, nil
case search.TokOpOr: case filter.TokOpOr:
left, err := MatchRequestFilter(req, expr.Left) left, err := MatchRequestFilter(req, expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -123,7 +123,7 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
return left || right, nil return left || right, nil
} }
left, ok := expr.Left.(search.StringLiteral) left, ok := expr.Left.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("left operand must be a string literal") return false, errors.New("left operand must be a string literal")
} }
@ -134,7 +134,7 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
} }
if leftVal == "headers" { if leftVal == "headers" {
match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header) match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to match request HTTP headers: %w", err) return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
} }
@ -142,21 +142,21 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
return match, nil return match, nil
} }
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
right, ok := expr.Right.(search.RegexpLiteral) right, ok := expr.Right.(filter.RegexpLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a regular expression") return false, errors.New("right operand must be a regular expression")
} }
switch expr.Operator { switch expr.Operator {
case search.TokOpRe: case filter.TokOpRe:
return right.MatchString(leftVal), nil return right.MatchString(leftVal), nil
case search.TokOpNotRe: case filter.TokOpNotRe:
return !right.MatchString(leftVal), nil return !right.MatchString(leftVal), nil
} }
} }
right, ok := expr.Right.(search.StringLiteral) right, ok := expr.Right.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a string literal") return false, errors.New("right operand must be a string literal")
} }
@ -167,20 +167,20 @@ func matchReqInfixExpr(req *http.Request, expr search.InfixExpression) (bool, er
} }
switch expr.Operator { switch expr.Operator {
case search.TokOpEq: case filter.TokOpEq:
return leftVal == rightVal, nil return leftVal == rightVal, nil
case search.TokOpNotEq: case filter.TokOpNotEq:
return leftVal != rightVal, nil return leftVal != rightVal, nil
case search.TokOpGt: case filter.TokOpGt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal > rightVal, nil return leftVal > rightVal, nil
case search.TokOpLt: case filter.TokOpLt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal < rightVal, nil return leftVal < rightVal, nil
case search.TokOpGtEq: case filter.TokOpGtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal >= rightVal, nil return leftVal >= rightVal, nil
case search.TokOpLtEq: case filter.TokOpLtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal <= rightVal, nil return leftVal <= rightVal, nil
default: default:
@ -197,7 +197,7 @@ func getMappedStringLiteralFromReq(req *http.Request, s string) (string, error)
return s, nil return s, nil
} }
func matchReqStringLiteral(req *http.Request, strLiteral search.StringLiteral) (bool, error) { func matchReqStringLiteral(req *http.Request, strLiteral filter.StringLiteral) (bool, error) {
for _, fn := range reqFilterKeyFns { for _, fn := range reqFilterKeyFns {
value, err := fn(req) value, err := fn(req)
if err != nil { if err != nil {
@ -268,22 +268,22 @@ func MatchRequestScope(req *http.Request, s *scope.Scope) (bool, error) {
} }
// MatchResponseFilter returns true if an HTTP response matches the response filter expression. // MatchResponseFilter returns true if an HTTP response matches the response filter expression.
func MatchResponseFilter(res *http.Response, expr search.Expression) (bool, error) { func MatchResponseFilter(res *http.Response, expr filter.Expression) (bool, error) {
switch e := expr.(type) { switch e := expr.(type) {
case search.PrefixExpression: case filter.PrefixExpression:
return matchResPrefixExpr(res, e) return matchResPrefixExpr(res, e)
case search.InfixExpression: case filter.InfixExpression:
return matchResInfixExpr(res, e) return matchResInfixExpr(res, e)
case search.StringLiteral: case filter.StringLiteral:
return matchResStringLiteral(res, e) return matchResStringLiteral(res, e)
default: default:
return false, fmt.Errorf("expression type (%T) not supported", expr) return false, fmt.Errorf("expression type (%T) not supported", expr)
} }
} }
func matchResPrefixExpr(res *http.Response, expr search.PrefixExpression) (bool, error) { func matchResPrefixExpr(res *http.Response, expr filter.PrefixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpNot: case filter.TokOpNot:
match, err := MatchResponseFilter(res, expr.Right) match, err := MatchResponseFilter(res, expr.Right)
if err != nil { if err != nil {
return false, err return false, err
@ -295,9 +295,9 @@ func matchResPrefixExpr(res *http.Response, expr search.PrefixExpression) (bool,
} }
} }
func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, error) { func matchResInfixExpr(res *http.Response, expr filter.InfixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpAnd: case filter.TokOpAnd:
left, err := MatchResponseFilter(res, expr.Left) left, err := MatchResponseFilter(res, expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -309,7 +309,7 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
} }
return left && right, nil return left && right, nil
case search.TokOpOr: case filter.TokOpOr:
left, err := MatchResponseFilter(res, expr.Left) left, err := MatchResponseFilter(res, expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -323,7 +323,7 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
return left || right, nil return left || right, nil
} }
left, ok := expr.Left.(search.StringLiteral) left, ok := expr.Left.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("left operand must be a string literal") return false, errors.New("left operand must be a string literal")
} }
@ -334,7 +334,7 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
} }
if leftVal == "headers" { if leftVal == "headers" {
match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, res.Header) match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, res.Header)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to match request HTTP headers: %w", err) return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
} }
@ -342,21 +342,21 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
return match, nil return match, nil
} }
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
right, ok := expr.Right.(search.RegexpLiteral) right, ok := expr.Right.(filter.RegexpLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a regular expression") return false, errors.New("right operand must be a regular expression")
} }
switch expr.Operator { switch expr.Operator {
case search.TokOpRe: case filter.TokOpRe:
return right.MatchString(leftVal), nil return right.MatchString(leftVal), nil
case search.TokOpNotRe: case filter.TokOpNotRe:
return !right.MatchString(leftVal), nil return !right.MatchString(leftVal), nil
} }
} }
right, ok := expr.Right.(search.StringLiteral) right, ok := expr.Right.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a string literal") return false, errors.New("right operand must be a string literal")
} }
@ -367,20 +367,20 @@ func matchResInfixExpr(res *http.Response, expr search.InfixExpression) (bool, e
} }
switch expr.Operator { switch expr.Operator {
case search.TokOpEq: case filter.TokOpEq:
return leftVal == rightVal, nil return leftVal == rightVal, nil
case search.TokOpNotEq: case filter.TokOpNotEq:
return leftVal != rightVal, nil return leftVal != rightVal, nil
case search.TokOpGt: case filter.TokOpGt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal > rightVal, nil return leftVal > rightVal, nil
case search.TokOpLt: case filter.TokOpLt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal < rightVal, nil return leftVal < rightVal, nil
case search.TokOpGtEq: case filter.TokOpGtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal >= rightVal, nil return leftVal >= rightVal, nil
case search.TokOpLtEq: case filter.TokOpLtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal <= rightVal, nil return leftVal <= rightVal, nil
default: default:
@ -397,7 +397,7 @@ func getMappedStringLiteralFromRes(res *http.Response, s string) (string, error)
return s, nil return s, nil
} }
func matchResStringLiteral(res *http.Response, strLiteral search.StringLiteral) (bool, error) { func matchResStringLiteral(res *http.Response, strLiteral filter.StringLiteral) (bool, error) {
for _, fn := range resFilterKeyFns { for _, fn := range resFilterKeyFns {
value, err := fn(res) value, err := fn(res)
if err != nil { if err != nil {

View file

@ -10,9 +10,9 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/log" "github.com/dstotijn/hetty/pkg/log"
"github.com/dstotijn/hetty/pkg/proxy" "github.com/dstotijn/hetty/pkg/proxy"
"github.com/dstotijn/hetty/pkg/search"
) )
var ( var (
@ -56,16 +56,16 @@ type Service struct {
requestsEnabled bool requestsEnabled bool
responsesEnabled bool responsesEnabled bool
reqFilter search.Expression reqFilter filter.Expression
resFilter search.Expression resFilter filter.Expression
} }
type Config struct { type Config struct {
Logger log.Logger Logger log.Logger
RequestsEnabled bool RequestsEnabled bool
ResponsesEnabled bool ResponsesEnabled bool
RequestFilter search.Expression RequestFilter filter.Expression
ResponseFilter search.Expression ResponseFilter filter.Expression
} }
// RequestIDs implements sort.Interface. // RequestIDs implements sort.Interface.

View file

@ -1,10 +1,10 @@
package intercept package intercept
import "github.com/dstotijn/hetty/pkg/search" import "github.com/dstotijn/hetty/pkg/filter"
type Settings struct { type Settings struct {
RequestsEnabled bool RequestsEnabled bool
ResponsesEnabled bool ResponsesEnabled bool
RequestFilter search.Expression RequestFilter filter.Expression
ResponseFilter search.Expression ResponseFilter filter.Expression
} }

View file

@ -12,10 +12,10 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/log" "github.com/dstotijn/hetty/pkg/log"
"github.com/dstotijn/hetty/pkg/proxy" "github.com/dstotijn/hetty/pkg/proxy"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
) )
type contextKey int type contextKey int
@ -77,7 +77,7 @@ type service struct {
type FindRequestsFilter struct { type FindRequestsFilter struct {
ProjectID ulid.ULID ProjectID ulid.ULID
OnlyInScope bool OnlyInScope bool
SearchExpr search.Expression SearchExpr filter.Expression
} }
type Config struct { type Config struct {

View file

@ -8,8 +8,8 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
) )
var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{ var reqLogSearchKeyFns = map[string]func(rl RequestLog) string{
@ -36,22 +36,22 @@ var ResLogSearchKeyFns = map[string]func(rl ResponseLog) string{
// TODO: Request and response headers search key functions. // TODO: Request and response headers search key functions.
// Matches returns true if the supplied search expression evaluates to true. // Matches returns true if the supplied search expression evaluates to true.
func (reqLog RequestLog) Matches(expr search.Expression) (bool, error) { func (reqLog RequestLog) Matches(expr filter.Expression) (bool, error) {
switch e := expr.(type) { switch e := expr.(type) {
case search.PrefixExpression: case filter.PrefixExpression:
return reqLog.matchPrefixExpr(e) return reqLog.matchPrefixExpr(e)
case search.InfixExpression: case filter.InfixExpression:
return reqLog.matchInfixExpr(e) return reqLog.matchInfixExpr(e)
case search.StringLiteral: case filter.StringLiteral:
return reqLog.matchStringLiteral(e) return reqLog.matchStringLiteral(e)
default: default:
return false, fmt.Errorf("expression type (%T) not supported", expr) return false, fmt.Errorf("expression type (%T) not supported", expr)
} }
} }
func (reqLog RequestLog) matchPrefixExpr(expr search.PrefixExpression) (bool, error) { func (reqLog RequestLog) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpNot: case filter.TokOpNot:
match, err := reqLog.Matches(expr.Right) match, err := reqLog.Matches(expr.Right)
if err != nil { if err != nil {
return false, err return false, err
@ -63,9 +63,9 @@ func (reqLog RequestLog) matchPrefixExpr(expr search.PrefixExpression) (bool, er
} }
} }
func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, error) { func (reqLog RequestLog) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpAnd: case filter.TokOpAnd:
left, err := reqLog.Matches(expr.Left) left, err := reqLog.Matches(expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -77,7 +77,7 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
} }
return left && right, nil return left && right, nil
case search.TokOpOr: case filter.TokOpOr:
left, err := reqLog.Matches(expr.Left) left, err := reqLog.Matches(expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -91,7 +91,7 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
return left || right, nil return left || right, nil
} }
left, ok := expr.Left.(search.StringLiteral) left, ok := expr.Left.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("left operand must be a string literal") return false, errors.New("left operand must be a string literal")
} }
@ -99,7 +99,7 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
leftVal := reqLog.getMappedStringLiteral(left.Value) leftVal := reqLog.getMappedStringLiteral(left.Value)
if leftVal == "req.headers" { if leftVal == "req.headers" {
match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Header) match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Header)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to match request HTTP headers: %w", err) return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
} }
@ -108,7 +108,7 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
} }
if leftVal == "res.headers" && reqLog.Response != nil { if leftVal == "res.headers" && reqLog.Response != nil {
match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Header) match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, reqLog.Response.Header)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to match response HTTP headers: %w", err) return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
} }
@ -116,21 +116,21 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
return match, nil return match, nil
} }
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
right, ok := expr.Right.(search.RegexpLiteral) right, ok := expr.Right.(filter.RegexpLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a regular expression") return false, errors.New("right operand must be a regular expression")
} }
switch expr.Operator { switch expr.Operator {
case search.TokOpRe: case filter.TokOpRe:
return right.MatchString(leftVal), nil return right.MatchString(leftVal), nil
case search.TokOpNotRe: case filter.TokOpNotRe:
return !right.MatchString(leftVal), nil return !right.MatchString(leftVal), nil
} }
} }
right, ok := expr.Right.(search.StringLiteral) right, ok := expr.Right.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a string literal") return false, errors.New("right operand must be a string literal")
} }
@ -138,20 +138,20 @@ func (reqLog RequestLog) matchInfixExpr(expr search.InfixExpression) (bool, erro
rightVal := reqLog.getMappedStringLiteral(right.Value) rightVal := reqLog.getMappedStringLiteral(right.Value)
switch expr.Operator { switch expr.Operator {
case search.TokOpEq: case filter.TokOpEq:
return leftVal == rightVal, nil return leftVal == rightVal, nil
case search.TokOpNotEq: case filter.TokOpNotEq:
return leftVal != rightVal, nil return leftVal != rightVal, nil
case search.TokOpGt: case filter.TokOpGt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal > rightVal, nil return leftVal > rightVal, nil
case search.TokOpLt: case filter.TokOpLt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal < rightVal, nil return leftVal < rightVal, nil
case search.TokOpGtEq: case filter.TokOpGtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal >= rightVal, nil return leftVal >= rightVal, nil
case search.TokOpLtEq: case filter.TokOpLtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal <= rightVal, nil return leftVal <= rightVal, nil
default: default:
@ -180,7 +180,7 @@ func (reqLog RequestLog) getMappedStringLiteral(s string) string {
return s return s
} }
func (reqLog RequestLog) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) { func (reqLog RequestLog) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
for _, fn := range reqLogSearchKeyFns { for _, fn := range reqLogSearchKeyFns {
if strings.Contains( if strings.Contains(
strings.ToLower(fn(reqLog)), strings.ToLower(fn(reqLog)),

View file

@ -3,8 +3,8 @@ package reqlog_test
import ( import (
"testing" "testing"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/search"
) )
func TestRequestLogMatch(t *testing.T) { func TestRequestLogMatch(t *testing.T) {
@ -176,7 +176,7 @@ func TestRequestLogMatch(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
searchExpr, err := search.ParseQuery(tt.query) searchExpr, err := filter.ParseQuery(tt.query)
assertError(t, nil, err) assertError(t, nil, err)
got, err := tt.requestLog.Matches(searchExpr) got, err := tt.requestLog.Matches(searchExpr)

View file

@ -1,212 +0,0 @@
package search_test
import (
"regexp"
"testing"
"github.com/dstotijn/hetty/pkg/search"
)
func TestExpressionString(t *testing.T) {
t.Parallel()
tests := []struct {
name string
expression search.Expression
expected string
}{
{
name: "string literal expression",
expression: search.StringLiteral{Value: "foobar"},
expected: `"foobar"`,
},
{
name: "boolean expression with equal operator",
expression: search.InfixExpression{
Operator: search.TokOpEq,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
expected: `("foo" = "bar")`,
},
{
name: "boolean expression with not equal operator",
expression: search.InfixExpression{
Operator: search.TokOpNotEq,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
expected: `("foo" != "bar")`,
},
{
name: "boolean expression with greater than operator",
expression: search.InfixExpression{
Operator: search.TokOpGt,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
expected: `("foo" > "bar")`,
},
{
name: "boolean expression with less than operator",
expression: search.InfixExpression{
Operator: search.TokOpLt,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
expected: `("foo" < "bar")`,
},
{
name: "boolean expression with greater than or equal operator",
expression: search.InfixExpression{
Operator: search.TokOpGtEq,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
expected: `("foo" >= "bar")`,
},
{
name: "boolean expression with less than or equal operator",
expression: search.InfixExpression{
Operator: search.TokOpLtEq,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
expected: `("foo" <= "bar")`,
},
{
name: "boolean expression with regular expression operator",
expression: search.InfixExpression{
Operator: search.TokOpRe,
Left: search.StringLiteral{Value: "foo"},
Right: search.RegexpLiteral{regexp.MustCompile("bar")},
},
expected: `("foo" =~ "bar")`,
},
{
name: "boolean expression with not regular expression operator",
expression: search.InfixExpression{
Operator: search.TokOpNotRe,
Left: search.StringLiteral{Value: "foo"},
Right: search.RegexpLiteral{regexp.MustCompile("bar")},
},
expected: `("foo" !~ "bar")`,
},
{
name: "boolean expression with AND, OR and NOT operators",
expression: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.StringLiteral{Value: "foo"},
Right: search.InfixExpression{
Operator: search.TokOpOr,
Left: search.StringLiteral{Value: "bar"},
Right: search.PrefixExpression{
Operator: search.TokOpNot,
Right: search.StringLiteral{Value: "baz"},
},
},
},
expected: `("foo" AND ("bar" OR (NOT "baz")))`,
},
{
name: "boolean expression with nested group",
expression: search.InfixExpression{
Operator: search.TokOpOr,
Left: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
Right: search.PrefixExpression{
Operator: search.TokOpNot,
Right: search.StringLiteral{Value: "baz"},
},
},
expected: `(("foo" AND "bar") OR (NOT "baz"))`,
},
{
name: "implicit boolean expression with string literal operands",
expression: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
Right: search.StringLiteral{Value: "baz"},
},
expected: `(("foo" AND "bar") AND "baz")`,
},
{
name: "implicit boolean expression nested in group",
expression: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
expected: `("foo" AND "bar")`,
},
{
name: "implicit and explicit boolean expression with string literal operands",
expression: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.StringLiteral{Value: "foo"},
Right: search.InfixExpression{
Operator: search.TokOpOr,
Left: search.StringLiteral{Value: "bar"},
Right: search.StringLiteral{Value: "baz"},
},
},
Right: search.StringLiteral{Value: "yolo"},
},
expected: `(("foo" AND ("bar" OR "baz")) AND "yolo")`,
},
{
name: "implicit boolean expression with comparison operands",
expression: search.InfixExpression{
Operator: search.TokOpAnd,
Left: search.InfixExpression{
Operator: search.TokOpEq,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
Right: search.InfixExpression{
Operator: search.TokOpRe,
Left: search.StringLiteral{Value: "baz"},
Right: search.RegexpLiteral{regexp.MustCompile("yolo")},
},
},
expected: `(("foo" = "bar") AND ("baz" =~ "yolo"))`,
},
{
name: "eq operator takes precedence over boolean ops",
expression: search.InfixExpression{
Operator: search.TokOpOr,
Left: search.InfixExpression{
Operator: search.TokOpEq,
Left: search.StringLiteral{Value: "foo"},
Right: search.StringLiteral{Value: "bar"},
},
Right: search.InfixExpression{
Operator: search.TokOpEq,
Left: search.StringLiteral{Value: "baz"},
Right: search.StringLiteral{Value: "yolo"},
},
},
expected: `(("foo" = "bar") OR ("baz" = "yolo"))`,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got := tt.expression.String()
if tt.expected != got {
t.Errorf("expected: %v, got: %v", tt.expected, got)
}
})
}
}

View file

@ -7,9 +7,9 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
) )
var senderReqSearchKeyFns = map[string]func(req Request) string{ var senderReqSearchKeyFns = map[string]func(req Request) string{
@ -29,22 +29,22 @@ var senderReqSearchKeyFns = map[string]func(req Request) string{
// TODO: Request and response headers search key functions. // TODO: Request and response headers search key functions.
// Matches returns true if the supplied search expression evaluates to true. // Matches returns true if the supplied search expression evaluates to true.
func (req Request) Matches(expr search.Expression) (bool, error) { func (req Request) Matches(expr filter.Expression) (bool, error) {
switch e := expr.(type) { switch e := expr.(type) {
case search.PrefixExpression: case filter.PrefixExpression:
return req.matchPrefixExpr(e) return req.matchPrefixExpr(e)
case search.InfixExpression: case filter.InfixExpression:
return req.matchInfixExpr(e) return req.matchInfixExpr(e)
case search.StringLiteral: case filter.StringLiteral:
return req.matchStringLiteral(e) return req.matchStringLiteral(e)
default: default:
return false, fmt.Errorf("expression type (%T) not supported", expr) return false, fmt.Errorf("expression type (%T) not supported", expr)
} }
} }
func (req Request) matchPrefixExpr(expr search.PrefixExpression) (bool, error) { func (req Request) matchPrefixExpr(expr filter.PrefixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpNot: case filter.TokOpNot:
match, err := req.Matches(expr.Right) match, err := req.Matches(expr.Right)
if err != nil { if err != nil {
return false, err return false, err
@ -56,9 +56,9 @@ func (req Request) matchPrefixExpr(expr search.PrefixExpression) (bool, error) {
} }
} }
func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) { func (req Request) matchInfixExpr(expr filter.InfixExpression) (bool, error) {
switch expr.Operator { switch expr.Operator {
case search.TokOpAnd: case filter.TokOpAnd:
left, err := req.Matches(expr.Left) left, err := req.Matches(expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -70,7 +70,7 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
} }
return left && right, nil return left && right, nil
case search.TokOpOr: case filter.TokOpOr:
left, err := req.Matches(expr.Left) left, err := req.Matches(expr.Left)
if err != nil { if err != nil {
return false, err return false, err
@ -84,7 +84,7 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
return left || right, nil return left || right, nil
} }
left, ok := expr.Left.(search.StringLiteral) left, ok := expr.Left.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("left operand must be a string literal") return false, errors.New("left operand must be a string literal")
} }
@ -92,7 +92,7 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
leftVal := req.getMappedStringLiteral(left.Value) leftVal := req.getMappedStringLiteral(left.Value)
if leftVal == "req.headers" { if leftVal == "req.headers" {
match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header) match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Header)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to match request HTTP headers: %w", err) return false, fmt.Errorf("failed to match request HTTP headers: %w", err)
} }
@ -101,7 +101,7 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
} }
if leftVal == "res.headers" && req.Response != nil { if leftVal == "res.headers" && req.Response != nil {
match, err := search.MatchHTTPHeaders(expr.Operator, expr.Right, req.Response.Header) match, err := filter.MatchHTTPHeaders(expr.Operator, expr.Right, req.Response.Header)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to match response HTTP headers: %w", err) return false, fmt.Errorf("failed to match response HTTP headers: %w", err)
} }
@ -109,21 +109,21 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
return match, nil return match, nil
} }
if expr.Operator == search.TokOpRe || expr.Operator == search.TokOpNotRe { if expr.Operator == filter.TokOpRe || expr.Operator == filter.TokOpNotRe {
right, ok := expr.Right.(search.RegexpLiteral) right, ok := expr.Right.(filter.RegexpLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a regular expression") return false, errors.New("right operand must be a regular expression")
} }
switch expr.Operator { switch expr.Operator {
case search.TokOpRe: case filter.TokOpRe:
return right.MatchString(leftVal), nil return right.MatchString(leftVal), nil
case search.TokOpNotRe: case filter.TokOpNotRe:
return !right.MatchString(leftVal), nil return !right.MatchString(leftVal), nil
} }
} }
right, ok := expr.Right.(search.StringLiteral) right, ok := expr.Right.(filter.StringLiteral)
if !ok { if !ok {
return false, errors.New("right operand must be a string literal") return false, errors.New("right operand must be a string literal")
} }
@ -131,20 +131,20 @@ func (req Request) matchInfixExpr(expr search.InfixExpression) (bool, error) {
rightVal := req.getMappedStringLiteral(right.Value) rightVal := req.getMappedStringLiteral(right.Value)
switch expr.Operator { switch expr.Operator {
case search.TokOpEq: case filter.TokOpEq:
return leftVal == rightVal, nil return leftVal == rightVal, nil
case search.TokOpNotEq: case filter.TokOpNotEq:
return leftVal != rightVal, nil return leftVal != rightVal, nil
case search.TokOpGt: case filter.TokOpGt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal > rightVal, nil return leftVal > rightVal, nil
case search.TokOpLt: case filter.TokOpLt:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal < rightVal, nil return leftVal < rightVal, nil
case search.TokOpGtEq: case filter.TokOpGtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal >= rightVal, nil return leftVal >= rightVal, nil
case search.TokOpLtEq: case filter.TokOpLtEq:
// TODO(?) attempt to parse as int. // TODO(?) attempt to parse as int.
return leftVal <= rightVal, nil return leftVal <= rightVal, nil
default: default:
@ -173,7 +173,7 @@ func (req Request) getMappedStringLiteral(s string) string {
return s return s
} }
func (req Request) matchStringLiteral(strLiteral search.StringLiteral) (bool, error) { func (req Request) matchStringLiteral(strLiteral filter.StringLiteral) (bool, error) {
for _, fn := range senderReqSearchKeyFns { for _, fn := range senderReqSearchKeyFns {
if strings.Contains( if strings.Contains(
strings.ToLower(fn(req)), strings.ToLower(fn(req)),

View file

@ -3,8 +3,8 @@ package sender_test
import ( import (
"testing" "testing"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/search"
"github.com/dstotijn/hetty/pkg/sender" "github.com/dstotijn/hetty/pkg/sender"
) )
@ -177,7 +177,7 @@ func TestRequestLogMatch(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
t.Parallel() t.Parallel()
searchExpr, err := search.ParseQuery(tt.query) searchExpr, err := filter.ParseQuery(tt.query)
assertError(t, nil, err) assertError(t, nil, err)
got, err := tt.senderReq.Matches(searchExpr) got, err := tt.senderReq.Matches(searchExpr)

View file

@ -12,9 +12,9 @@ import (
"github.com/oklog/ulid" "github.com/oklog/ulid"
"github.com/dstotijn/hetty/pkg/filter"
"github.com/dstotijn/hetty/pkg/reqlog" "github.com/dstotijn/hetty/pkg/reqlog"
"github.com/dstotijn/hetty/pkg/scope" "github.com/dstotijn/hetty/pkg/scope"
"github.com/dstotijn/hetty/pkg/search"
) )
//nolint:gosec //nolint:gosec
@ -54,7 +54,7 @@ type service struct {
type FindRequestsFilter struct { type FindRequestsFilter struct {
ProjectID ulid.ULID ProjectID ulid.ULID
OnlyInScope bool OnlyInScope bool
SearchExpr search.Expression SearchExpr filter.Expression
} }
type Config struct { type Config struct {