package filtering import ( "testing" syntaxv1 "go.einride.tech/aip/proto/gen/einride/example/syntax/v1" "gotest.tools/v3/assert" ) func TestChecker(t *testing.T) { t.Parallel() for _, tt := range []struct { filter string declarations []DeclarationOption errorContains string }{ { filter: "New York Giants", declarations: []DeclarationOption{ DeclareIdent("New", TypeBool), DeclareIdent("York", TypeBool), DeclareIdent("Giants", TypeBool), }, errorContains: "undeclared function 'FUZZY'", }, { filter: "New York Giants OR Yankees", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("New", TypeBool), DeclareIdent("York", TypeBool), DeclareIdent("Giants", TypeBool), DeclareIdent("Yankees", TypeBool), }, errorContains: "undeclared function 'FUZZY'", }, { filter: "New York (Giants OR Yankees)", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("New", TypeBool), DeclareIdent("York", TypeBool), DeclareIdent("Giants", TypeBool), DeclareIdent("Yankees", TypeBool), }, errorContains: "undeclared function 'FUZZY'", }, { filter: "a b AND c AND d", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("a", TypeBool), DeclareIdent("b", TypeBool), DeclareIdent("c", TypeBool), DeclareIdent("d", TypeBool), }, errorContains: "undeclared function 'FUZZY'", }, { filter: "a", declarations: []DeclarationOption{ DeclareIdent("a", TypeBool), }, }, { filter: "(a b) AND c AND d", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("a", TypeBool), DeclareIdent("b", TypeBool), DeclareIdent("c", TypeBool), DeclareIdent("d", TypeBool), }, errorContains: "undeclared function 'FUZZY'", }, { filter: `author = "Karin Boye" AND NOT read`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("author", TypeString), DeclareIdent("read", TypeBool), }, }, { filter: "a < 10 OR a >= 100", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("a", TypeInt), }, }, { filter: "NOT (a OR b)", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("a", TypeBool), DeclareIdent("b", TypeBool), }, }, { filter: `-file:".java"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("file", TypeString), }, }, { filter: "-30", errorContains: "non-bool result type", }, { filter: "package=com.google", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("package", TypeString), DeclareIdent("com", TypeMap(TypeString, TypeString)), }, }, { filter: `msg != 'hello'`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("msg", TypeString), }, }, { filter: `1 > 0`, declarations: []DeclarationOption{ DeclareStandardFunctions(), }, }, { filter: `2.5 >= 2.4`, declarations: []DeclarationOption{ DeclareStandardFunctions(), }, }, { filter: `foo >= -2.4`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("foo", TypeFloat), }, }, { filter: `foo >= (-2.4)`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("foo", TypeFloat), }, }, { filter: `-2.5 >= -2.4`, declarations: []DeclarationOption{ DeclareStandardFunctions(), }, }, { filter: `yesterday < request.time`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("yesterday", TypeTimestamp), }, // TODO: Add support for structs. errorContains: "undeclared identifier 'request'", }, { filter: `experiment.rollout <= cohort(request.user)`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareFunction("cohort", NewFunctionOverload("cohort_string", TypeFloat, TypeString)), }, // TODO: Add support for structs. errorContains: "undeclared identifier 'experiment'", }, { filter: `prod`, declarations: []DeclarationOption{ DeclareIdent("prod", TypeBool), }, }, { filter: `expr.type_map.1.type`, // TODO: Add support for structs. errorContains: "undeclared identifier 'expr'", }, { filter: `regex(m.key, '^.*prod.*$')`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("m", TypeMap(TypeString, TypeString)), DeclareFunction("regex", NewFunctionOverload("regex_string", TypeBool, TypeString, TypeString)), }, }, { filter: `math.mem('30mb')`, declarations: []DeclarationOption{ DeclareFunction("math.mem", NewFunctionOverload("math.mem_string", TypeBool, TypeString)), }, }, { filter: `(msg.endsWith('world') AND retries < 10)`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("retries", TypeInt), }, errorContains: "undeclared function 'msg.endsWith'", }, { filter: `(endsWith(msg, 'world') AND retries < 10)`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareFunction("endsWith", NewFunctionOverload("endsWith_string", TypeBool, TypeString, TypeString)), DeclareIdent("retries", TypeInt), DeclareIdent("msg", TypeString), }, }, { filter: "expire_time > time.now()", declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)), DeclareIdent("expire_time", TypeTimestamp), }, }, { filter: `time.now() > timestamp("2012-04-21T11:30:00-04:00")`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)), }, }, { filter: `time.now() > timestamp("INVALID")`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareFunction("time.now", NewFunctionOverload("time.now", TypeTimestamp)), }, errorContains: "invalid timestamp", }, { filter: `ttl > duration("30s")`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("ttl", TypeDuration), }, }, { filter: `ttl > duration("INVALID")`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("ttl", TypeDuration), }, errorContains: "invalid duration", }, { filter: `ttl > duration(input_field)`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("ttl", TypeDuration), DeclareIdent("input_field", TypeString), }, }, { filter: `create_time > timestamp("2006-01-02T15:04:05+07:00")`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: `create_time > timestamp("2006-01-02T15:04:05+07:00")`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: ` start_time > timestamp("2006-01-02T15:04:05+07:00") AND (driver = "driver1" OR start_driver = "driver1" OR end_driver = "driver1") `, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("start_time", TypeTimestamp), DeclareIdent("driver", TypeString), DeclareIdent("start_driver", TypeString), DeclareIdent("end_driver", TypeString), }, }, { filter: `annotations:schedule`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("annotations", TypeMap(TypeString, TypeString)), }, }, { filter: `annotations.schedule = "test"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("annotations", TypeMap(TypeString, TypeString)), }, }, { filter: `enum = ENUM_ONE`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareEnumIdent("enum", syntaxv1.Enum(0).Type()), }, }, { filter: `enum = ENUM_ONE AND NOT enum2 = ENUM_TWO`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareEnumIdent("enum", syntaxv1.Enum(0).Type()), DeclareEnumIdent("enum2", syntaxv1.Enum(0).Type()), }, }, { filter: `create_time = "2022-08-12 22:22:22"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, errorContains: "invalid timestamp. Should be in RFC3339 format", }, { filter: `create_time = "2022-08-12T22:22:22+01:00"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: `create_time != "2022-08-12T22:22:22+01:00"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: `create_time < "2022-08-12T22:22:22+01:00"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: `create_time > "2022-08-12T22:22:22+01:00"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: `create_time <= "2022-08-12T22:22:22+01:00"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: `create_time >= "2022-08-12T22:22:22+01:00"`, declarations: []DeclarationOption{ DeclareStandardFunctions(), DeclareIdent("create_time", TypeTimestamp), }, }, { filter: "<", errorContains: "unexpected token <", }, { filter: `(-2.5) >= -2.4`, errorContains: "unexpected token >=", }, { filter: `a = "foo`, errorContains: "unterminated string", }, { filter: "invalid = foo\xa0\x01bar", errorContains: "invalid UTF-8", }, } { tt := tt t.Run(tt.filter, func(t *testing.T) { t.Parallel() var parser Parser parser.Init(tt.filter) parsedExpr, err := parser.Parse() if err != nil && tt.errorContains != "" { assert.ErrorContains(t, err, tt.errorContains) return } assert.NilError(t, err) declarations, err := NewDeclarations(tt.declarations...) if err != nil && tt.errorContains != "" { assert.ErrorContains(t, err, tt.errorContains) return } assert.NilError(t, err) var checker Checker checker.Init(parsedExpr.GetExpr(), parsedExpr.GetSourceInfo(), declarations) checkedExpr, err := checker.Check() if tt.errorContains != "" { assert.ErrorContains(t, err, tt.errorContains) return } assert.NilError(t, err) assert.Assert(t, checkedExpr != nil) }) } }