Compare commits

...

1 Commits

Author SHA1 Message Date
srikanthccv
04ec681eda fix(alerts): surface individual validation errors in API response 2026-06-17 16:41:28 +05:30
2 changed files with 36 additions and 13 deletions

View File

@@ -338,6 +338,7 @@ func isValidLabelValue(v string) bool {
// validate runs during UnmarshalJSON (read + write path).
// Preserves the original pre-existing checks only so that stored rules
// continue to load without errors.
// TODO(srikanthccv): remove this once v1 is deprecated and removed
func (r *PostableRule) validate() error {
var errs []error
@@ -366,9 +367,13 @@ func (r *PostableRule) validate() error {
errs = append(errs, testTemplateParsing(r)...)
joined := errors.Join(errs...)
if joined != nil {
return errors.WrapInvalidInputf(joined, errors.CodeInvalidInput, "validation failed")
if len(errs) > 0 {
messages := make([]string, len(errs))
for i, e := range errs {
messages[i] = e.Error()
}
return errors.NewInvalidInputf(errors.CodeInvalidInput, "alert rule definition is not valid").
WithAdditional(messages...)
}
return nil
}
@@ -466,9 +471,13 @@ func (r *PostableRule) Validate() error {
errs = append(errs, testTemplateParsing(r)...)
joined := errors.Join(errs...)
if joined != nil {
return errors.WrapInvalidInputf(joined, errors.CodeInvalidInput, "validation failed")
if len(errs) > 0 {
messages := make([]string, len(errs))
for i, e := range errs {
messages[i] = e.Error()
}
return errors.NewInvalidInputf(errors.CodeInvalidInput, "alert rule is not valid").
WithAdditional(messages...)
}
return nil
}

View File

@@ -4,8 +4,23 @@ import (
"encoding/json"
"strings"
"testing"
"github.com/SigNoz/signoz/pkg/errors"
)
func errorContains(err error, substr string) bool {
j := errors.AsJSON(err)
if strings.Contains(j.Message, substr) {
return true
}
for _, e := range j.Errors {
if strings.Contains(e.Message, substr) {
return true
}
}
return false
}
// validV1Builder returns a minimal valid v1 builder rule JSON.
func validV1Builder() string {
return `{
@@ -494,7 +509,7 @@ func TestValidate_PostableRule_Common(t *testing.T) {
if tt.wantErr {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errSubstr)
} else if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
} else if tt.errSubstr != "" && !errorContains(err, tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, err)
}
} else {
@@ -687,7 +702,7 @@ func TestValidate_V1_ConditionFields(t *testing.T) {
if tt.wantErr {
if validateErr == nil {
t.Errorf("expected Validate() error containing %q, got nil", tt.errSubstr)
} else if tt.errSubstr != "" && !strings.Contains(validateErr.Error(), tt.errSubstr) {
} else if tt.errSubstr != "" && !errorContains(validateErr, tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, validateErr)
}
} else {
@@ -1029,7 +1044,7 @@ func TestValidate_V2Alpha1(t *testing.T) {
if tt.wantErr {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errSubstr)
} else if tt.errSubstr != "" && !strings.Contains(err.Error(), tt.errSubstr) {
} else if tt.errSubstr != "" && !errorContains(err, tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, err)
}
} else {
@@ -1337,7 +1352,7 @@ func TestValidate_MultipleErrors(t *testing.T) {
t.Fatal("expected unmarshal error for wrong version")
}
// The error should mention version
if !strings.Contains(err.Error(), "version") {
if !errorContains(err, "version") {
t.Errorf("expected error to mention version, got: %v", err)
}
})
@@ -1355,10 +1370,9 @@ func TestValidate_MultipleErrors(t *testing.T) {
if validateErr == nil {
t.Fatal("expected Validate() error")
}
errStr := validateErr.Error()
// Should contain errors for thresholds, evaluation, notificationSettings
for _, substr := range []string{"evaluation", "notificationSettings"} {
if !strings.Contains(errStr, substr) {
if !errorContains(validateErr, substr) {
t.Errorf("expected error to mention %q, got: %v", substr, validateErr)
}
}
@@ -1469,7 +1483,7 @@ func TestValidate_V2Alpha1_CumulativeEvaluation(t *testing.T) {
if tt.wantErr {
if err == nil {
t.Errorf("expected error containing %q, got nil", tt.errSubstr)
} else if !strings.Contains(err.Error(), tt.errSubstr) {
} else if !errorContains(err, tt.errSubstr) {
t.Errorf("expected error containing %q, got: %v", tt.errSubstr, err)
}
} else if err != nil {