Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v17] lib/utils/typical: support == and != operators for integers #52991

Open
wants to merge 3 commits into
base: branch/v17
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/services/label_expressions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ func TestLabelExpressions(t *testing.T) {
desc: "wrong type",
expr: `user.spec.traits["allow-env"] == "staging"`,
expectParseError: []string{
"parsing lhs of (==) operator",
"expected type string, got expression returning type ([]string)",
"operator (==) not supported for type: []string",
},
},
{
Expand Down
63 changes: 57 additions & 6 deletions lib/utils/typical/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -916,9 +916,30 @@ func or[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
}

func eq[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
return booleanOperator[TEnv, string]{
return func(lhs any, rhs any) (Expression[TEnv, bool], error) {
// If the LHS operand type isn't known at parse time (e.g. an `ifelse`
// function call) try the RHS instead.
operand := lhs
if _, isAny := operand.(Expression[TEnv, any]); isAny {
operand = rhs
}
switch operand.(type) {
case string, Expression[TEnv, string]:
return eqExpression[TEnv, string](lhs, rhs)
case int, Expression[TEnv, int]:
return eqExpression[TEnv, int](lhs, rhs)
case Expression[TEnv, any]:
return nil, trace.Errorf("operator (==) can only be used when at least one operand type is known at parse time")
default:
return nil, trace.Errorf("operator (==) not supported for type: %s", typeName(operand))
}
}
}

func eqExpression[TEnv any, TArgs comparable](lhs any, rhs any) (Expression[TEnv, bool], error) {
return booleanOperator[TEnv, TArgs]{
name: "==",
f: func(env TEnv, lhsExpr, rhsExpr Expression[TEnv, string]) (bool, error) {
f: func(env TEnv, lhsExpr, rhsExpr Expression[TEnv, TArgs]) (bool, error) {
lhs, err := lhsExpr.Evaluate(env)
if err != nil {
return false, trace.Wrap(err, "evaluating lhs of (==) operator")
Expand All @@ -929,13 +950,34 @@ func eq[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
}
return lhs == rhs, nil
},
}.buildExpression
}.buildExpression(lhs, rhs)
}

func neq[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
return booleanOperator[TEnv, string]{
return func(lhs any, rhs any) (Expression[TEnv, bool], error) {
// If the LHS operand type isn't known at parse time (e.g. an `ifelse`
// function call) try the RHS instead.
operand := lhs
if _, isAny := operand.(Expression[TEnv, any]); isAny {
operand = rhs
}
switch operand.(type) {
case string, Expression[TEnv, string]:
return neqExpression[TEnv, string](lhs, rhs)
case int, Expression[TEnv, int]:
return neqExpression[TEnv, int](lhs, rhs)
case Expression[TEnv, any]:
return nil, trace.Errorf("operator (!=) can only be used when at least one operand type is known at parse time")
default:
return nil, trace.Errorf("operator (!=) not supported for type: %s", typeName(operand))
}
}
}

func neqExpression[TEnv any, TArgs comparable](lhs any, rhs any) (Expression[TEnv, bool], error) {
return booleanOperator[TEnv, TArgs]{
name: "!=",
f: func(env TEnv, lhsExpr, rhsExpr Expression[TEnv, string]) (bool, error) {
f: func(env TEnv, lhsExpr, rhsExpr Expression[TEnv, TArgs]) (bool, error) {
lhs, err := lhsExpr.Evaluate(env)
if err != nil {
return false, trace.Wrap(err, "evaluating lhs of (!=) operator")
Expand All @@ -946,7 +988,7 @@ func neq[TEnv any]() func(lhs, rhs any) (Expression[TEnv, bool], error) {
}
return lhs != rhs, nil
},
}.buildExpression
}.buildExpression(lhs, rhs)
}

type notExpr[TEnv any] struct {
Expand Down Expand Up @@ -1113,3 +1155,12 @@ func unexpectedTypeError[TExpected any](v any) error {
resultType := evaluateMethod.Type.Out(0)
return trace.BadParameter(prefix+"got expression returning type (%s)", resultType)
}

func typeName(v any) string {
evaluateMethod, ok := reflect.TypeOf(v).MethodByName("Evaluate")
if !ok {
// This isn't an expr
return fmt.Sprintf("%T", v)
}
return evaluateMethod.Type.Out(0).String()
}
68 changes: 68 additions & 0 deletions lib/utils/typical/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,74 @@ func TestParser(t *testing.T) {
"haha",
},
},
{
desc: "integer equality (true)",
expr: `1 == 1`,
expectMatch: true,
},
{
desc: "integer equality (false)",
expr: `1 == 2`,
expectMatch: false,
},
{
desc: "equality lhs dynamic",
expr: `ifelse(1 == 1, 1, "one") == 1`,
expectMatch: true,
},
{
desc: "equality rhs dynamic",
expr: `1 == ifelse(1 == 1, 1, "one")`,
expectMatch: true,
},
{
desc: "equality both operands dynamic",
expr: `ifelse(1 == 1, 1, "one") == ifelse(1 == 1, 1, "one")`,
expectParseError: []string{
"operator (==) can only be used when at least one operand type is known at parse time",
},
},
{
desc: "equality unsupported operand type",
expr: `traits == traits`,
expectParseError: []string{
"operator (==) not supported for type: map[string][]string",
},
},
{
desc: "integer inequality (true)",
expr: `1 != 2`,
expectMatch: true,
},
{
desc: "integer inequality (false)",
expr: `1 != 1`,
expectMatch: false,
},
{
desc: "inequality lhs dynamic",
expr: `ifelse(1 == 1, 1, "one") != 2`,
expectMatch: true,
},
{
desc: "inequality rhs dynamic",
expr: `2 != ifelse(1 == 1, 1, "one")`,
expectMatch: true,
},
{
desc: "iequality both operands dynamic",
expr: `ifelse(1 == 1, 1, "one") != ifelse(1 == 1, 1, "one")`,
expectParseError: []string{
"operator (!=) can only be used when at least one operand type is known at parse time",
},
},
{
desc: "inequality unsupported operand type",
expr: `traits != traits`,
expectParseError: []string{
"operator (!=) not supported for type: map[string][]string",
},
},
} {
tc := tc
t.Run(tc.desc, func(t *testing.T) {
Expand Down
Loading