Alerting: Improve validation of query and expressions on rule submit (#53258)
* Improve error messages of server-side expression * move validation of alert queries and a condition to eval package
This commit is contained in:
@@ -203,19 +203,19 @@ func UnmarshalConditionsCmd(rawQuery map[string]interface{}, refID string) (*Con
|
||||
cond := condition{}
|
||||
|
||||
if i > 0 && cj.Operator.Type != "and" && cj.Operator.Type != "or" {
|
||||
return nil, fmt.Errorf("classic condition %v operator must be `and` or `or`", i+1)
|
||||
return nil, fmt.Errorf("condition %v operator must be `and` or `or`", i+1)
|
||||
}
|
||||
cond.Operator = cj.Operator.Type
|
||||
|
||||
if len(cj.Query.Params) == 0 || cj.Query.Params[0] == "" {
|
||||
return nil, fmt.Errorf("classic condition %v is missing the query refID argument", i+1)
|
||||
return nil, fmt.Errorf("condition %v is missing the query refID argument", i+1)
|
||||
}
|
||||
|
||||
cond.QueryRefID = cj.Query.Params[0]
|
||||
|
||||
cond.Reducer = classicReducer(cj.Reducer.Type)
|
||||
if !cond.Reducer.ValidReduceFunc() {
|
||||
return nil, fmt.Errorf("reducer '%v' in condition %v is not a valid reducer", cond.Reducer, i+1)
|
||||
return nil, fmt.Errorf("invalid reducer '%v' in condition %v", cond.Reducer, i+1)
|
||||
}
|
||||
|
||||
cond.Evaluator, err = newAlertEvaluator(cj.Evaluator)
|
||||
|
||||
+20
-19
@@ -2,6 +2,7 @@ package expr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -43,16 +44,16 @@ func NewMathCommand(refID, expr string) (*MathCommand, error) {
|
||||
func UnmarshalMathCommand(rn *rawNode) (*MathCommand, error) {
|
||||
rawExpr, ok := rn.Query["expression"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("math command for refId %v is missing an expression", rn.RefID)
|
||||
return nil, errors.New("command is missing an expression")
|
||||
}
|
||||
exprString, ok := rawExpr.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected math command for refId %v expression to be a string, got %T", rn.RefID, rawExpr)
|
||||
return nil, fmt.Errorf("math expression is expected to be a string, got %T", rawExpr)
|
||||
}
|
||||
|
||||
gm, err := NewMathCommand(rn.RefID, exprString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid math command type in '%v': %v", rn.RefID, err)
|
||||
return nil, fmt.Errorf("invalid math command type: %w", err)
|
||||
}
|
||||
return gm, nil
|
||||
}
|
||||
@@ -96,21 +97,21 @@ func NewReduceCommand(refID, reducer, varToReduce string, mapper mathexp.ReduceM
|
||||
func UnmarshalReduceCommand(rn *rawNode) (*ReduceCommand, error) {
|
||||
rawVar, ok := rn.Query["expression"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no variable specified to reduce for refId %v", rn.RefID)
|
||||
return nil, errors.New("no expression ID is specified to reduce. Must be a reference to an existing query or expression")
|
||||
}
|
||||
varToReduce, ok := rawVar.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected reduce variable to be a string, got %T for refId %v", rawVar, rn.RefID)
|
||||
return nil, fmt.Errorf("expression ID is expected to be a string, got %T", rawVar)
|
||||
}
|
||||
varToReduce = strings.TrimPrefix(varToReduce, "$")
|
||||
|
||||
rawReducer, ok := rn.Query["reducer"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no reducer specified for refId %v", rn.RefID)
|
||||
return nil, errors.New("no reducer specified")
|
||||
}
|
||||
redFunc, ok := rawReducer.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected reducer to be a string, got %T for refId %v", rawReducer, rn.RefID)
|
||||
return nil, fmt.Errorf("expected reducer to be a string, got %T", rawReducer)
|
||||
}
|
||||
|
||||
var mapper mathexp.ReduceMapper = nil
|
||||
@@ -126,20 +127,20 @@ func UnmarshalReduceCommand(rn *rawNode) (*ReduceCommand, error) {
|
||||
case "replaceNN":
|
||||
valueStr, ok := s["replaceWithValue"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected settings.replaceWithValue to be specified when mode is 'replaceNN' for refId %v", rn.RefID)
|
||||
return nil, errors.New("setting replaceWithValue must be specified when mode is 'replaceNN'")
|
||||
}
|
||||
switch value := valueStr.(type) {
|
||||
case float64:
|
||||
mapper = mathexp.ReplaceNonNumberWithValue{Value: value}
|
||||
default:
|
||||
return nil, fmt.Errorf("expected settings.replaceWithValue to be a number, got %T for refId %v", value, rn.RefID)
|
||||
return nil, fmt.Errorf("setting replaceWithValue must be a number, got %T", value)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("reducer mode %s is not supported for refId %v. Supported only: [dropNN,replaceNN]", mode, rn.RefID)
|
||||
return nil, fmt.Errorf("reducer mode '%s' is not supported. Supported only: [dropNN,replaceNN]", mode)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("expected settings to be an object, got %T for refId %v", s, rn.RefID)
|
||||
return nil, fmt.Errorf("field settings must be an object, got %T for refId %v", s, rn.RefID)
|
||||
}
|
||||
}
|
||||
return NewReduceCommand(rn.RefID, redFunc, varToReduce, mapper)
|
||||
@@ -211,40 +212,40 @@ func NewResampleCommand(refID, rawWindow, varToResample string, downsampler stri
|
||||
func UnmarshalResampleCommand(rn *rawNode) (*ResampleCommand, error) {
|
||||
rawVar, ok := rn.Query["expression"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no variable to resample for refId %v", rn.RefID)
|
||||
return nil, errors.New("no expression ID to resample. must be a reference to an existing query or expression")
|
||||
}
|
||||
varToReduce, ok := rawVar.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected resample input variable to be type string, but got type %T for refId %v", rawVar, rn.RefID)
|
||||
return nil, fmt.Errorf("expected resample input variable to be type string, but got type %T", rawVar)
|
||||
}
|
||||
varToReduce = strings.TrimPrefix(varToReduce, "$")
|
||||
varToResample := varToReduce
|
||||
|
||||
rawWindow, ok := rn.Query["window"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no time duration specified for the window in resample command for refId %v", rn.RefID)
|
||||
return nil, errors.New("no time duration specified for the window in resample command")
|
||||
}
|
||||
window, ok := rawWindow.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected resample window to be a string, got %T for refId %v", rawWindow, rn.RefID)
|
||||
return nil, fmt.Errorf("resample window is expected to be a string, got %T", rawWindow)
|
||||
}
|
||||
|
||||
rawDownsampler, ok := rn.Query["downsampler"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no downsampler function specified in resample command for refId %v", rn.RefID)
|
||||
return nil, errors.New("no downsampler function specified in resample command")
|
||||
}
|
||||
downsampler, ok := rawDownsampler.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", downsampler, rn.RefID)
|
||||
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T", downsampler)
|
||||
}
|
||||
|
||||
rawUpsampler, ok := rn.Query["upsampler"]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no downsampler specified in resample command for refId %v", rn.RefID)
|
||||
return nil, errors.New("no upsampler specified in resample command")
|
||||
}
|
||||
upsampler, ok := rawUpsampler.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T for refId %v", upsampler, rn.RefID)
|
||||
return nil, fmt.Errorf("expected resample downsampler to be a string, got type %T", upsampler)
|
||||
}
|
||||
|
||||
return NewResampleCommand(rn.RefID, window, varToResample, downsampler, upsampler, rn.TimeRange)
|
||||
|
||||
+1
-1
@@ -189,7 +189,7 @@ func buildGraphEdges(dp *simple.DirectedGraph, registry map[string]Node) error {
|
||||
}
|
||||
|
||||
if neededNode.ID() == cmdNode.ID() {
|
||||
return fmt.Errorf("can not add self referencing node for var '%v' ", neededVar)
|
||||
return fmt.Errorf("expression '%v' cannot reference itself. Must be query or another expression", neededVar)
|
||||
}
|
||||
|
||||
if cmdNode.CMDType == TypeClassicConditions {
|
||||
|
||||
@@ -77,7 +77,7 @@ func TestServicebuildPipeLine(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expectErrContains: "self referencing node",
|
||||
expectErrContains: "expression 'A' cannot reference itself. Must be query or another expression",
|
||||
},
|
||||
{
|
||||
name: "missing dependency will error",
|
||||
|
||||
@@ -123,7 +123,7 @@ func (s Series) Reduce(refID, rFunc string, mapper ReduceMapper) (Number, error)
|
||||
floatField := Float64Field(*fVec)
|
||||
reduceFunc, err := GetReduceFunc(rFunc)
|
||||
if err != nil {
|
||||
return number, err
|
||||
return number, fmt.Errorf("invalid expression '%s': %w", refID, err)
|
||||
}
|
||||
f = reduceFunc(&floatField)
|
||||
if f != nil && mapper != nil {
|
||||
|
||||
+3
-3
@@ -99,7 +99,7 @@ func (gn *CMDNode) Execute(ctx context.Context, vars mathexp.Vars, s *Service) (
|
||||
func buildCMDNode(dp *simple.DirectedGraph, rn *rawNode) (*CMDNode, error) {
|
||||
commandType, err := rn.GetCommandType()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid expression command type in '%v'", rn.RefID)
|
||||
return nil, fmt.Errorf("invalid command type in expression '%v': %w", rn.RefID, err)
|
||||
}
|
||||
|
||||
node := &CMDNode{
|
||||
@@ -120,10 +120,10 @@ func buildCMDNode(dp *simple.DirectedGraph, rn *rawNode) (*CMDNode, error) {
|
||||
case TypeClassicConditions:
|
||||
node.Command, err = classic.UnmarshalConditionsCmd(rn.Query, rn.RefID)
|
||||
default:
|
||||
return nil, fmt.Errorf("expression command type '%v' in '%v' not implemented", commandType, rn.RefID)
|
||||
return nil, fmt.Errorf("expression command type '%v' in expression '%v' not implemented", commandType, rn.RefID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to parse expression '%v': %w", rn.RefID, err)
|
||||
}
|
||||
|
||||
return node, nil
|
||||
|
||||
Reference in New Issue
Block a user