diff --git a/config/common/notifierconfig.go b/config/common/notifierconfig.go index 6689e6d9e2..ade56dedef 100644 --- a/config/common/notifierconfig.go +++ b/config/common/notifierconfig.go @@ -13,6 +13,13 @@ package common +import ( + "errors" + "strings" + + "gopkg.in/yaml.v2" +) + // NotifierConfig contains base options common across all notifier configurations. type NotifierConfig struct { VSendResolved bool `yaml:"send_resolved" json:"send_resolved"` @@ -28,3 +35,43 @@ func (nc *NotifierConfig) SendResolved() bool { type Validator interface { Validate() error } + +// AsValidationError returns err as a *yaml.TypeError so that the YAML decoder +// records it and continues decoding instead of aborting at the first invalid +// notifier config. The decoder collects every such error across the document, +// which is what allows a configuration to report all of its invalid notifier +// configs at once. Errors that already are a *yaml.TypeError, including the +// decoder's own type and unknown field errors, are returned unchanged, and so +// is nil. +func AsValidationError(err error) error { + if err == nil { + return nil + } + if te, ok := errors.AsType[*yaml.TypeError](err); ok { + return te + } + return &yaml.TypeError{Errors: []string{err.Error()}} +} + +// FlattenValidationErrors turns the errors collected through AsValidationError +// back into plain errors at the top level of the configuration, so that a single +// validation error reads exactly as it did before and several read one per +// line. Errors reported by the decoder itself, which carry a "line N:" prefix, +// keep their usual *yaml.TypeError form. +func FlattenValidationErrors(err error) error { + te, ok := errors.AsType[*yaml.TypeError](err) + if !ok { + return err + } + errs := make([]error, 0, len(te.Errors)) + for _, msg := range te.Errors { + if strings.HasPrefix(msg, "line ") { + return te + } + errs = append(errs, errors.New(msg)) + } + if len(errs) == 1 { + return errs[0] + } + return errors.Join(errs...) +} diff --git a/config/config.go b/config/config.go index 487ec83e4a..ca112e88e4 100644 --- a/config/config.go +++ b/config/config.go @@ -317,7 +317,7 @@ func (c *Config) UnmarshalYAML(unmarshal func(any) error) error { // again, we have to hide it using a type indirection. type plain Config if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.FlattenValidationErrors(err) } // If a global block was open but empty the default global config is overwritten. diff --git a/config/config_test.go b/config/config_test.go index c70fdaf3d2..bb90ff1fb4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2017,3 +2017,103 @@ receivers: t.Errorf("expected local proxy_url %q, got %q", "http://local-proxy.example.com:8080", got) } } + +func TestReceiverValidationErrorsAccumulate(t *testing.T) { + tests := []struct { + name string + in string + expected string + }{ + { + // https://github.com/prometheus/alertmanager/issues/4990 + name: "multiple invalid notifier configs in a single receiver", + in: ` +route: + receiver: team-X + +receivers: +- name: 'team-X' + webhook_configs: + - send_resolved: true + - url: 'http://example.com/' + url_file: '/etc/secrets/webhook-url' +`, + expected: "one of url or url_file must be configured\n" + + "at most one of url & url_file must be configured", + }, + { + // The example from https://github.com/prometheus/alertmanager/issues/4990: + // the decoder's own type error and the validation error are reported + // together instead of the latter hiding the former. + name: "type errors and validation errors are reported together", + in: ` +receivers: + - name: 'webhook-critical' + webhook_configs: + - url: 'http://example.com/api' + send_resolved: "yes_please" + - url: '' + http_config: + basic_auth: + username: 'admin' +route: + group_by: ['alertname'] + receiver: 'webhook-critical' +`, + expected: "yaml: unmarshal errors:\n" + + " line 6: cannot unmarshal !!str `yes_please` into bool\n" + + " one of url or url_file must be configured", + }, + { + name: "invalid notifier configs of different types in a single receiver", + in: ` +route: + receiver: team-X + +receivers: +- name: 'team-X' + webhook_configs: + - send_resolved: true + pagerduty_configs: + - url: 'https://example.com/' +`, + expected: "one of url or url_file must be configured\n" + + "missing service or routing key in PagerDuty config", + }, + { + // https://github.com/prometheus/alertmanager/issues/4991 + name: "invalid notifier configs across multiple receivers", + in: ` +route: + receiver: team-A + +receivers: +- name: 'team-A' + webhook_configs: + - send_resolved: true +- name: 'team-B' + email_configs: + - smarthost: 'smtp.example.com:587' + from: 'alertmanager@example.com' +- name: 'team-C' + pagerduty_configs: + - url: 'https://example.com/' +`, + expected: "one of url or url_file must be configured\n" + + "missing to address in email config\n" + + "missing service or routing key in PagerDuty config", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := Load(tc.in) + if err == nil { + t.Fatalf("no error returned, expected:\n%v", tc.expected) + } + if err.Error() != tc.expected { + t.Errorf("\nexpected:\n%v\ngot:\n%v", tc.expected, err.Error()) + } + }) + } +} diff --git a/config/notifiers.go b/config/notifiers.go index 8ae478eca7..ec04cf245c 100644 --- a/config/notifiers.go +++ b/config/notifiers.go @@ -105,9 +105,9 @@ func (c *WebexConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultWebexConfig type plain WebexConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *WebexConfig) Validate() error { @@ -161,7 +161,7 @@ func (c *EmailConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultEmailConfig type plain EmailConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } // Header names are case insensitive. The normalization loop below // detects duplicates and builds a canonical header map in one pass. @@ -171,13 +171,13 @@ func (c *EmailConfig) UnmarshalYAML(unmarshal func(any) error) error { for h, v := range c.Headers { normalized := textproto.CanonicalMIMEHeaderKey(h) if _, ok := normalizedHeaders[normalized]; ok { - return fmt.Errorf("duplicate header %q in email config", normalized) + return amcommoncfg.AsValidationError(fmt.Errorf("duplicate header %q in email config", normalized)) } normalizedHeaders[normalized] = v } c.Headers = normalizedHeaders - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *EmailConfig) Validate() error { @@ -217,7 +217,7 @@ type SlackAction struct { func (c *SlackAction) UnmarshalYAML(unmarshal func(any) error) error { type plain SlackAction if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } if c.URL != "" { // Clear all message action fields. @@ -227,9 +227,9 @@ func (c *SlackAction) UnmarshalYAML(unmarshal func(any) error) error { } else if c.Name != "" { c.URL = "" } else { - return errors.New("missing name or url in Slack action configuration") + return amcommoncfg.AsValidationError(errors.New("missing name or url in Slack action configuration")) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *SlackAction) Validate() error { @@ -256,9 +256,9 @@ type SlackConfirmationField struct { func (c *SlackConfirmationField) UnmarshalYAML(unmarshal func(any) error) error { type plain SlackConfirmationField if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *SlackConfirmationField) Validate() error { @@ -282,9 +282,9 @@ type SlackField struct { func (c *SlackField) UnmarshalYAML(unmarshal func(any) error) error { type plain SlackField if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *SlackField) Validate() error { @@ -346,9 +346,9 @@ func (c *SlackConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultSlackConfig type plain SlackConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *SlackConfig) Validate() error { @@ -396,14 +396,14 @@ func (c *WechatConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultWechatConfig type plain WechatConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } if c.MessageType == "" { c.MessageType = "text" } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *WechatConfig) Validate() error { @@ -440,9 +440,9 @@ func (c *VictorOpsConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultVictorOpsConfig type plain VictorOpsConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } func (c *VictorOpsConfig) Validate() error { diff --git a/config/notifiers_test.go b/config/notifiers_test.go index 0ab362dc00..86be7a7421 100644 --- a/config/notifiers_test.go +++ b/config/notifiers_test.go @@ -35,7 +35,7 @@ to: '' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -55,7 +55,7 @@ headers: if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -126,7 +126,7 @@ routing_key: '' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } }) @@ -145,7 +145,7 @@ api_key_file: /global_file if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } }) @@ -165,7 +165,7 @@ custom_fields: if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } @@ -304,7 +304,7 @@ api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXX t.Fatalf("\nno error returned, expected:\n%v", rt.expectedErr) } // Check that the error that occurred was what was expected. - if err != nil && err.Error() != rt.expectedErr { + if err != nil && err.Error() != "yaml: unmarshal errors:\n "+rt.expectedErr { t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expectedErr, err.Error()) } } @@ -361,7 +361,7 @@ fields: t.Fatalf("\nno error returned, expected:\n%v", rt.expected) } // Check that the error that occurred was what was expected. - if err != nil && err.Error() != rt.expected { + if err != nil && err.Error() != "yaml: unmarshal errors:\n "+rt.expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", rt.expected, err.Error()) } } @@ -551,7 +551,11 @@ http_config: var cfg WebexConfig err := yaml.UnmarshalStrict([]byte(tt.in), &cfg) - require.Equal(t, tt.expected, err) + if tt.expected != nil { + require.EqualError(t, err, "yaml: unmarshal errors:\n "+tt.expected.Error()) + } else { + require.NoError(t, err) + } }) } } @@ -609,7 +613,11 @@ headers: {X-Custom-Header: CustomValue, X-CUSTOM-HEADER: AnotherValue} var cfg EmailConfig err := yaml.UnmarshalStrict([]byte(tt.in), &cfg) - require.Equal(t, tt.expected, err) + if tt.expected != nil { + require.EqualError(t, err, "yaml: unmarshal errors:\n "+tt.expected.Error()) + } else { + require.NoError(t, err) + } }) } } diff --git a/notify/discord/config.go b/notify/discord/config.go index 63caaa7976..39237497a7 100644 --- a/notify/discord/config.go +++ b/notify/discord/config.go @@ -50,10 +50,10 @@ func (c *DiscordConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = defaultDiscordConfig type plain DiscordConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the DiscordConfig for correctness. diff --git a/notify/incidentio/config.go b/notify/incidentio/config.go index 12ee5e64d0..a6ad01142c 100644 --- a/notify/incidentio/config.go +++ b/notify/incidentio/config.go @@ -64,9 +64,9 @@ func (c *IncidentioConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = defaultIncidentioConfig type plain IncidentioConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the IncidentioConfig for correctness. diff --git a/notify/jira/config.go b/notify/jira/config.go index c7cccf2c73..07e235a29e 100644 --- a/notify/jira/config.go +++ b/notify/jira/config.go @@ -91,10 +91,10 @@ func (c *JiraConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultJiraConfig type plain JiraConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the JiraConfig for correctness. diff --git a/notify/mattermost/config.go b/notify/mattermost/config.go index 52c9119928..ac66396a76 100644 --- a/notify/mattermost/config.go +++ b/notify/mattermost/config.go @@ -58,9 +58,9 @@ type MattermostField struct { func (c *MattermostField) UnmarshalYAML(unmarshal func(any) error) error { type plain MattermostField if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the MattermostField for correctness. @@ -132,10 +132,10 @@ func (c *MattermostConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultMattermostConfig type plain MattermostConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the MattermostConfig for correctness. diff --git a/notify/mattermost/config_test.go b/notify/mattermost/config_test.go index 15660d6011..1aac96f694 100644 --- a/notify/mattermost/config_test.go +++ b/notify/mattermost/config_test.go @@ -63,7 +63,11 @@ value: some value var cfg MattermostField err := yaml.UnmarshalStrict([]byte(tt.in), &cfg) - require.Equal(t, tt.expected, err) + if tt.expected != nil { + require.EqualError(t, err, "yaml: unmarshal errors:\n "+tt.expected.Error()) + } else { + require.NoError(t, err) + } }) } } @@ -125,7 +129,11 @@ attachments: var cfg MattermostConfig err := yaml.UnmarshalStrict([]byte(tt.in), &cfg) - require.Equal(t, tt.expected, err) + if tt.expected != nil { + require.EqualError(t, err, "yaml: unmarshal errors:\n "+tt.expected.Error()) + } else { + require.NoError(t, err) + } }) } } diff --git a/notify/msteams/config.go b/notify/msteams/config.go index 782479567c..69d83216bb 100644 --- a/notify/msteams/config.go +++ b/notify/msteams/config.go @@ -45,10 +45,10 @@ func (c *MSTeamsConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultMSTeamsConfig type plain MSTeamsConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the MSTeamsConfig for correctness. diff --git a/notify/msteamsv2/config.go b/notify/msteamsv2/config.go index 55f0dd6a42..6af473bf94 100644 --- a/notify/msteamsv2/config.go +++ b/notify/msteamsv2/config.go @@ -43,10 +43,10 @@ func (c *MSTeamsV2Config) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultMSTeamsV2Config type plain MSTeamsV2Config if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the MSTeamsV2Config for correctness. diff --git a/notify/opsgenie/config.go b/notify/opsgenie/config.go index 2c37cebf7c..5ac2d05a0a 100644 --- a/notify/opsgenie/config.go +++ b/notify/opsgenie/config.go @@ -66,9 +66,9 @@ func (c *OpsGenieConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultOpsGenieConfig type plain OpsGenieConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the OpsGenieConfig for correctness. diff --git a/notify/pagerduty/config.go b/notify/pagerduty/config.go index 694a3ff541..e7622cd46d 100644 --- a/notify/pagerduty/config.go +++ b/notify/pagerduty/config.go @@ -87,7 +87,7 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultPagerdutyConfig type plain PagerdutyConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } if c.Details == nil { c.Details = make(map[string]any) @@ -100,7 +100,7 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(any) error) error { c.Details[k] = v } } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the PagerdutyConfig for correctness. diff --git a/notify/pagerduty/config_test.go b/notify/pagerduty/config_test.go index 3d31bd0017..696e0e6467 100644 --- a/notify/pagerduty/config_test.go +++ b/notify/pagerduty/config_test.go @@ -33,7 +33,7 @@ routing_key: '' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } }) @@ -51,7 +51,7 @@ routing_key_file: 'xyz' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } }) @@ -70,7 +70,7 @@ service_key: '' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } }) @@ -88,7 +88,7 @@ service_key_file: 'xyz' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } }) diff --git a/notify/pushover/config.go b/notify/pushover/config.go index 08074a12f2..f4f31fde2d 100644 --- a/notify/pushover/config.go +++ b/notify/pushover/config.go @@ -78,9 +78,9 @@ func (c *PushoverConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultPushoverConfig type plain PushoverConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the PushoverConfig for correctness. diff --git a/notify/pushover/config_test.go b/notify/pushover/config_test.go index 36185d71d4..d38e8053af 100644 --- a/notify/pushover/config_test.go +++ b/notify/pushover/config_test.go @@ -31,7 +31,7 @@ user_key: '' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -49,7 +49,7 @@ user_key_file: /pushover/user_key if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -67,7 +67,7 @@ token: '' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -86,7 +86,7 @@ user_key: 'user key' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -106,7 +106,7 @@ monospace: true if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } diff --git a/notify/rocketchat/config.go b/notify/rocketchat/config.go index c0dd7580c8..a178ef64fa 100644 --- a/notify/rocketchat/config.go +++ b/notify/rocketchat/config.go @@ -91,9 +91,9 @@ func (c *RocketchatConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultRocketchatConfig type plain RocketchatConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the RocketchatConfig for correctness. diff --git a/notify/sns/config.go b/notify/sns/config.go index f7a0e25be6..4924439c36 100644 --- a/notify/sns/config.go +++ b/notify/sns/config.go @@ -56,9 +56,9 @@ func (c *SNSConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultSNSConfig type plain SNSConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the SNSConfig for correctness. diff --git a/notify/telegram/config.go b/notify/telegram/config.go index 5f02739cd4..2f027a3055 100644 --- a/notify/telegram/config.go +++ b/notify/telegram/config.go @@ -57,9 +57,9 @@ func (c *TelegramConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = DefaultTelegramConfig type plain TelegramConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the TelegramConfig for correctness. diff --git a/notify/telegram/config_test.go b/notify/telegram/config_test.go index adaa1576df..7daef24fde 100644 --- a/notify/telegram/config_test.go +++ b/notify/telegram/config_test.go @@ -96,7 +96,11 @@ parse_mode: invalid var cfg TelegramConfig err := yaml.UnmarshalStrict([]byte(tt.in), &cfg) - require.Equal(t, tt.expected, err) + if tt.expected != nil { + require.EqualError(t, err, "yaml: unmarshal errors:\n "+tt.expected.Error()) + } else { + require.NoError(t, err) + } }) } } diff --git a/notify/webhook/config.go b/notify/webhook/config.go index 5ddabb86d0..2fea05914c 100644 --- a/notify/webhook/config.go +++ b/notify/webhook/config.go @@ -55,9 +55,9 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(any) error) error { *c = defaultWebhookConfig type plain WebhookConfig if err := unmarshal((*plain)(c)); err != nil { - return err + return amcommoncfg.AsValidationError(err) } - return c.Validate() + return amcommoncfg.AsValidationError(c.Validate()) } // Validate checks the WebhookConfig for correctness. diff --git a/notify/webhook/config_test.go b/notify/webhook/config_test.go index a9db06e704..ea435e7442 100644 --- a/notify/webhook/config_test.go +++ b/notify/webhook/config_test.go @@ -30,7 +30,7 @@ func TestWebhookURLIsPresent(t *testing.T) { if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -48,7 +48,7 @@ url_file: 'http://example.com' if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } } @@ -68,7 +68,7 @@ http_config: if err == nil { t.Fatalf("no error returned, expected:\n%v", expected) } - if err.Error() != expected { + if err.Error() != "yaml: unmarshal errors:\n "+expected { t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error()) } }