Skip to content

Commit 8c8db36

Browse files
committed
chore: third time's the charm
1 parent 7c33229 commit 8c8db36

2 files changed

Lines changed: 41 additions & 22 deletions

File tree

‎grafana-alertcheck/internal/gate/resolve.go‎

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -95,35 +95,39 @@ func resolveOne(defs []Definition, name, folder string) (Definition, error) {
9595
return Definition{}, fmt.Errorf("no rule matched %q: no rule has this uid (run 'grafana-alertcheck list' to see uids)", name)
9696
}
9797

98-
// A datasource rule's name can contain "/", so the full input may be a title,
99-
// not a segmented form: try an exact title match before splitting.
100-
if def, found, err := pickCandidate(defs, name, func(d Definition) bool {
101-
return titleMatches(d, name, folder)
102-
}); found {
103-
return def, err
104-
}
105-
106-
parts, err := parseNameForm(name)
107-
if err != nil {
108-
return Definition{}, err
98+
// Two interpretations are possible: the whole input as one rule's exact
99+
// title (a datasource rule's name can itself contain "/"), and the
100+
// /-separated forms. Collect candidates from BOTH — a selector that is
101+
// ambiguous between them must be reported, never silently resolved to one.
102+
parts, formErr := parseNameForm(name)
103+
var candidates []Definition
104+
seen := make(map[string]bool, len(defs))
105+
for _, d := range defs {
106+
exact := titleMatches(d, name, folder)
107+
segmented := formErr == nil && matchesName(d, parts, folder)
108+
if !exact && !segmented {
109+
continue
110+
}
111+
if key := defKey(d); !seen[key] {
112+
seen[key] = true
113+
candidates = append(candidates, d)
114+
}
109115
}
110-
if def, found, err := pickCandidate(defs, name, func(d Definition) bool {
111-
return matchesName(d, parts, folder)
112-
}); found {
116+
if def, found, err := pickCandidate(candidates, name); found {
113117
return def, err
114118
}
119+
if formErr != nil {
120+
return Definition{}, formErr
121+
}
115122
return Definition{}, noMatchError(supportedDefs(defs), name, parts[len(parts)-1])
116123
}
117124

118-
// pickCandidate applies the shared one-match/ambiguous/unsupported/no-match
119-
// policy to a candidate predicate. found is false when nothing matched, so the
120-
// caller can try the next interpretation.
121-
func pickCandidate(defs []Definition, name string, match func(Definition) bool) (Definition, bool, error) {
125+
// pickCandidate applies the shared one-match/ambiguous/unsupported policy to the
126+
// collected candidates. found is false when nothing matched, so the caller can
127+
// fall through to the no-match surface.
128+
func pickCandidate(candidates []Definition, name string) (Definition, bool, error) {
122129
var supported, unsupported []Definition
123-
for _, d := range defs {
124-
if !match(d) {
125-
continue
126-
}
130+
for _, d := range candidates {
127131
if isSupported(d) {
128132
supported = append(supported, d)
129133
} else {

‎grafana-alertcheck/internal/gate/resolve_test.go‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,21 @@ func TestResolve_DatasourceNameWithSlashes(t *testing.T) {
243243
require.Equal(t, name, resolved[0].Title)
244244
}
245245

246+
// The same string can be one datasource rule's exact title AND a Grafana
247+
// Folder/Title selector. That must be reported as ambiguous, not silently
248+
// resolved to whichever interpretation is tried first.
249+
func TestResolve_ExactTitleVsSegmentedIsAmbiguous(t *testing.T) {
250+
ds := Definition{
251+
Key: ruleKey("vm", "G", "Platform/HighErrorRate", "f", ""),
252+
Title: "Platform/HighErrorRate", Group: "G",
253+
Kind: KindDatasourceManaged, DatasourceUID: "vm", DatasourceName: "VM",
254+
}
255+
grafana := Definition{Key: "u1", UID: "u1", Title: "HighErrorRate", Folder: "Platform", Kind: KindGrafanaManaged}
256+
_, _, err := Resolve([]Definition{grafana, ds}, []string{"Platform/HighErrorRate"}, "")
257+
require.Error(t, err)
258+
require.Contains(t, err.Error(), "matches 2 rules")
259+
}
260+
246261
// A key shared by two distinct rules cannot select one of them.
247262
func TestResolve_KeySharedByDistinctRulesIsAmbiguous(t *testing.T) {
248263
a := Definition{

0 commit comments

Comments
 (0)