pkg/lib/comparison/comparison.go (152 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. package comparison // Package comparison implements support for parsing comparison expressions of the following type: // * Greater than, ">" // * Greater or equal, ">=" // * Less than, "<" // * Less or equal, "<=" // * Equal, "=" // Furthermore, the right hand side operator can be a percentage (relative comparison) // or an absolute number (absolute comparison) import ( "fmt" "strconv" "strings" ) // Type represents the type of comparison. It could be an absolute (e.g. >50) or relative comparison (e.g. >50%) type Type uint16 // TypeNames maps a comparison.Type to its string form. var TypeNames = map[Type]string{ TypeValue: "comparison_value", TypePercentage: "comparison_percentage", } // List of possible comparison types const ( TypeValue Type = iota TypePercentage ) // String translates a Type object into its corresponding string representation func (t Type) String() string { if name, ok := TypeNames[t]; ok { return name } return fmt.Sprintf("comparison_type_unknown(%d)", t) } // Operator is the string representation of any of the supported comparison operators type Operator string // comparison operators. const ( ExprGt Operator = ">" ExprLt Operator = "<" ExprGe Operator = ">=" ExprLe Operator = "<=" ExprEq Operator = "=" ) // Comparator is an interface that all comparators must implement type Comparator interface { Compare(lhs, rhs float64) bool Operator() Operator } // Ge implements a "greater or equal" comparator type Ge struct { } // Compare implement the "greater or equal" comparison func (c Ge) Compare(lhs, rhs float64) bool { return lhs >= rhs } // Operator returns the operator applied during the comparison func (c Ge) Operator() Operator { return ExprGe } // Gt implements a "greater than" comparator type Gt struct { } // Compare implement the "greater than" comparison func (c Gt) Compare(lhs, rhs float64) bool { return lhs > rhs } // Operator returns the operator applied during the comparison func (c Gt) Operator() Operator { return ExprGt } // Lt implements a "less than" comparator type Lt struct { } // Compare implement the "less than" comparison func (c Lt) Compare(lhs, rhs float64) bool { return lhs < rhs } // Operator returns the operator applied during the comparison func (c Lt) Operator() Operator { return ExprLt } // Le implements a "less or equal" comparator type Le struct { } // Compare implement the "less or equal" comparison func (c Le) Compare(lhs, rhs float64) bool { return lhs <= rhs } // Operator returns the operator applied during the comparison func (c Le) Operator() Operator { return ExprLe } // Eq implements an "equal" comparator type Eq struct { } // Compare implement the "equal" comparison func (c Eq) Compare(lhs, rhs float64) bool { return lhs == rhs } // Operator returns the operator applied during the comparison func (c Eq) Operator() Operator { return ExprEq } // Result wraps the result of a comparison, including the full expression that was evaluated type Result struct { Pass bool Expr string LHS string RHS string Type Type Op Operator } // Expression is a struct that represents the deserialization of a string expression. // The deserialization includes the following: // * The comparison type (absolute, relative) // * The comparison function and operator // * The right hand side operator type Expression struct { expr string Type Type Cmp Comparator RHS float64 } // EvaluateSuccess invokes the Compare function of the internal Comparator object func (e Expression) EvaluateSuccess(success, total uint64) (*Result, error) { var lhs float64 if e.Type == TypePercentage { if total == 0 { return nil, fmt.Errorf("cannot evaluate success rate if total is 0") } lhs = float64(success) / float64(total) * 100 } else if e.Type == TypeValue { lhs = float64(success) } else { return nil, fmt.Errorf("unknown comparison type: %s", e.Type.String()) } pass := e.Cmp.Compare(lhs, e.RHS) cmpRes := &Result{Type: e.Type, Op: e.Cmp.Operator()} if e.Type == TypePercentage { cmpRes.LHS = fmt.Sprintf("%.2f%%", lhs) cmpRes.RHS = fmt.Sprintf("%.2f%%", e.RHS) } else { cmpRes.LHS = fmt.Sprintf("%.2f", lhs) cmpRes.RHS = fmt.Sprintf("%.2f", e.RHS) } var expr string if pass { cmpRes.Pass = true if e.Type == TypePercentage { expr = fmt.Sprintf("%.2f%% %s %.2f%%", lhs, e.Cmp.Operator(), e.RHS) } else { expr = fmt.Sprintf("%.2f %s %.2f", lhs, e.Cmp.Operator(), e.RHS) } } else { cmpRes.Pass = false if e.Type == TypePercentage { expr = fmt.Sprintf("%.2f%% is not %s %.2f%%", lhs, string(e.Cmp.Operator()), e.RHS) } else { expr = fmt.Sprintf("%.2f is not %s %.2f", lhs, string(e.Cmp.Operator()), e.RHS) } } cmpRes.Expr = expr return cmpRes, nil } func (e Expression) String() string { return e.expr } // ParseExpression deserializes a string comparison expression (e.g. "">=50") into // a Expression object. Returns an error if the expression is not supported. func ParseExpression(expression string) (*Expression, error) { comparators := []Comparator{Ge{}, Le{}, Lt{}, Gt{}, Eq{}} for _, cmp := range comparators { if !strings.HasPrefix(expression, string(cmp.Operator())) { continue } trimmed := strings.Trim(expression, string(cmp.Operator())) if strings.HasSuffix(trimmed, "%") { _rhs, err := strconv.ParseFloat(strings.TrimRight(trimmed, "%"), 64) if err != nil { return nil, fmt.Errorf("could not extract percentage from expression: %v", err) } return &Expression{Type: TypePercentage, Cmp: cmp, RHS: _rhs, expr: expression}, nil } _rhs, err := strconv.ParseFloat(trimmed, 64) if err != nil { return nil, fmt.Errorf("could not extract right hand side of the expression: %v", err) } return &Expression{Type: TypeValue, Cmp: cmp, RHS: _rhs, expr: expression}, nil } return nil, fmt.Errorf("expression %s not supported", expression) }