Skip to content

Commit a09770e

Browse files
authored
fix(AIP-123): prevent panic in getParentIDVariable with single-variable patterns (#1565)
1 parent f10744f commit a09770e

File tree

3 files changed

+65
-0
lines changed

3 files changed

+65
-0
lines changed

rules/aip0123/aip0123.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ func getParentIDVariable(pattern string) string {
115115
return variables[len(variables)-1]
116116
}
117117

118+
// If there are fewer than 2 variables for a non-singleton resource, there is no parent ID variable.
119+
// This can happen with invalid patterns like "prefix/collection/{id}",
120+
// which will be caught by the resource-name-components-alternate rule.
121+
if len(variables) < 2 {
122+
return ""
123+
}
124+
118125
return variables[len(variables)-2]
119126
}
120127

rules/aip0123/aip0123_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"testing"
1919

2020
"github.com/googleapis/api-linter/v2/lint"
21+
"github.com/googleapis/api-linter/v2/rules/internal/testutils"
2122
apb "google.golang.org/genproto/googleapis/api/annotations"
2223
)
2324

@@ -250,3 +251,57 @@ func TestGetParentIDVariable(t *testing.T) {
250251
})
251252
}
252253
}
254+
255+
func TestAIP0123_InvalidPatternsDontPanic(t *testing.T) {
256+
registry := lint.NewRuleRegistry()
257+
if err := AddRules(registry); err != nil {
258+
t.Fatalf("Failed to add AIP-0123 rules: %v", err)
259+
}
260+
linter := lint.New(registry, nil)
261+
262+
for _, test := range []struct {
263+
name string
264+
Pattern string
265+
}{
266+
{
267+
name: "prefixed top level",
268+
Pattern: "nonCollectionPrefix/projects/{project}",
269+
},
270+
{
271+
name: "prefixed child collection",
272+
Pattern: "nonCollectionPrefix/projects/{project}/locations/{location}",
273+
},
274+
{
275+
name: "prefixed singleton",
276+
Pattern: "nonCollectionPrefix/projects/{project}/config",
277+
},
278+
} {
279+
t.Run(test.name, func(t *testing.T) {
280+
f := testutils.ParseProto3Tmpl(t, `
281+
import "google/api/resource.proto";
282+
message TestResource {
283+
option (google.api.resource) = {
284+
type: "library.googleapis.com/TestResource"
285+
pattern: "{{ .Pattern }}"
286+
singular: "testResource"
287+
plural: "testResources"
288+
};
289+
string name = 1;
290+
}
291+
`, test)
292+
293+
responses, err := linter.LintProtos(f)
294+
if err != nil {
295+
t.Fatalf("Linter returned error (possible panic): %v", err)
296+
}
297+
298+
problemCount := 0
299+
for _, resp := range responses {
300+
problemCount += len(resp.Problems)
301+
}
302+
if problemCount == 0 {
303+
t.Errorf("Expected at least one problem for invalid pattern %q, got none", test.Pattern)
304+
}
305+
})
306+
}
307+
}

rules/aip0123/resource_name_components_alternate_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ func TestResourceNameComponentsAlternate(t *testing.T) {
3030
{"ValidSingleton", "user/{user}/config", testutils.Problems{}},
3131
{"InvalidDoubleCollection", "author/books/{book}", testutils.Problems{{Message: "must alternate"}}},
3232
{"InvalidDoubleIdentifier", "books/{author}/{book}", testutils.Problems{{Message: "must alternate"}}},
33+
{"InvalidPrefixedSingleVariable", "nonCollectionPrefix/projects/{project}", testutils.Problems{{Message: "must alternate"}}},
34+
{"InvalidPrefixedChildCollection", "nonCollectionPrefix/projects/{project}/locations/{location}", testutils.Problems{{Message: "must alternate"}}},
35+
{"InvalidPrefixedSingleton", "nonCollectionPrefix/projects/{project}/config", testutils.Problems{{Message: "must alternate"}}},
3336
} {
3437
t.Run(test.name, func(t *testing.T) {
3538
f := testutils.ParseProto3Tmpl(t, `

0 commit comments

Comments
 (0)