Skip to content

Commit 9e1beec

Browse files
authored
Merge pull request #504 from pohly/naming-convention
tighten validation of inlining and metadata
2 parents f7e401e + 5b13d40 commit 9e1beec

File tree

2 files changed

+142
-48
lines changed

2 files changed

+142
-48
lines changed

pkg/generators/rules/names_match.go

+21-17
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ var (
3232
"-",
3333
)
3434

35-
// Blacklist of JSON names that should skip match evaluation
36-
jsonNameBlacklist = sets.NewString(
37-
// Empty name is used for inline struct field (e.g. metav1.TypeMeta)
38-
"",
39-
// Special case for object and list meta
40-
"metadata",
41-
)
42-
4335
// List of substrings that aren't allowed in Go name and JSON name
4436
disallowedNameSubstrings = sets.NewString(
4537
// Underscore is not allowed in either name
@@ -73,12 +65,11 @@ is also considered matched.
7365
7466
HTTPJSONSpec httpjsonSpec true
7567
76-
NOTE: JSON names in jsonNameBlacklist should skip evaluation
68+
NOTE: an empty JSON name is valid only for inlined structs or pointer to structs.
69+
It cannot be empty for anything else because capitalization must be set explicitly.
7770
78-
true
79-
podSpec true
80-
podSpec - true
81-
podSpec metadata true
71+
NOTE: metav1.ListMeta and metav1.ObjectMeta by convention must have "metadata" as name.
72+
Other fields may have that JSON name if the field name matches.
8273
*/
8374
type NamesMatch struct{}
8475

@@ -109,14 +100,30 @@ func (n *NamesMatch) Validate(t *types.Type) ([]string, error) {
109100
continue
110101
}
111102
jsonName := strings.Split(jsonTag, ",")[0]
112-
if !namesMatch(goName, jsonName) {
103+
if !nameIsOkay(m, jsonName) {
113104
fields = append(fields, goName)
114105
}
115106
}
116107
}
117108
return fields, nil
118109
}
119110

111+
func nameIsOkay(member types.Member, jsonName string) bool {
112+
if jsonName == "" {
113+
return member.Type.Kind == types.Struct ||
114+
member.Type.Kind == types.Pointer && member.Type.Elem.Kind == types.Struct
115+
}
116+
117+
typeName := member.Type.String()
118+
switch typeName {
119+
case "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta",
120+
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta":
121+
return jsonName == "metadata"
122+
}
123+
124+
return namesMatch(member.Name, jsonName)
125+
}
126+
120127
// namesMatch evaluates if goName and jsonName match the API rule
121128
// TODO: Use an off-the-shelf CamelCase solution instead of implementing this logic. The following existing
122129
//
@@ -129,9 +136,6 @@ func (n *NamesMatch) Validate(t *types.Type) ([]string, error) {
129136
// about why they don't satisfy our need. What we need can be a function that detects an acronym at the
130137
// beginning of a string.
131138
func namesMatch(goName, jsonName string) bool {
132-
if jsonNameBlacklist.Has(jsonName) {
133-
return true
134-
}
135139
if !isAllowedName(goName) || !isAllowedName(jsonName) {
136140
return false
137141
}

pkg/generators/rules/names_match_test.go

+121-31
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,39 @@ import (
2424
)
2525

2626
func TestNamesMatch(t *testing.T) {
27+
someStruct := &types.Type{
28+
Name: types.Name{Name: "SomeStruct"},
29+
Kind: types.Struct,
30+
}
31+
someStructPtr := &types.Type{
32+
Name: types.Name{Name: "SomeStructPtr"},
33+
Kind: types.Pointer,
34+
Elem: someStruct,
35+
}
36+
intPtr := &types.Type{
37+
Name: types.Name{Name: "IntPtr"},
38+
Kind: types.Pointer,
39+
Elem: types.Int,
40+
}
41+
listMeta := &types.Type{
42+
Name: types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListMeta"},
43+
Kind: types.Struct,
44+
}
45+
listMetaPtr := &types.Type{
46+
Name: types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ListMetaPtr"},
47+
Kind: types.Pointer,
48+
Elem: listMeta,
49+
}
50+
objectMeta := &types.Type{
51+
Name: types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ObjectMeta"},
52+
Kind: types.Struct,
53+
}
54+
objectMetaPtr := &types.Type{
55+
Name: types.Name{Package: "k8s.io/apimachinery/pkg/apis/meta/v1", Name: "ObjectMetaPtr"},
56+
Kind: types.Pointer,
57+
Elem: objectMeta,
58+
}
59+
2760
tcs := []struct {
2861
// name of test case
2962
name string
@@ -231,36 +264,7 @@ func TestNamesMatch(t *testing.T) {
231264
},
232265
expected: []string{"PodSpec"},
233266
},
234-
// NOTE: JSON names in jsonNameBlacklist should skip evaluation
235-
// {"", "", true},
236-
{
237-
name: "unspecified",
238-
t: &types.Type{
239-
Kind: types.Struct,
240-
Members: []types.Member{
241-
{
242-
Name: "",
243-
Tags: `json:""`,
244-
},
245-
},
246-
},
247-
expected: []string{},
248-
},
249-
// {"podSpec", "", true},
250-
{
251-
name: "blacklist_empty",
252-
t: &types.Type{
253-
Kind: types.Struct,
254-
Members: []types.Member{
255-
{
256-
Name: "podSpec",
257-
Tags: `json:""`,
258-
},
259-
},
260-
},
261-
expected: []string{},
262-
},
263-
// {"podSpec", "metadata", true},
267+
// {"podSpec", "metadata", false},
264268
{
265269
name: "blacklist_metadata",
266270
t: &types.Type{
@@ -272,7 +276,7 @@ func TestNamesMatch(t *testing.T) {
272276
},
273277
},
274278
},
275-
expected: []string{},
279+
expected: []string{"podSpec"},
276280
},
277281
{
278282
name: "non_struct",
@@ -338,6 +342,92 @@ func TestNamesMatch(t *testing.T) {
338342
},
339343
expected: []string{"Pod_Spec"},
340344
},
345+
{
346+
name: "empty_JSON_name",
347+
t: &types.Type{
348+
Kind: types.Struct,
349+
Members: []types.Member{
350+
{
351+
Name: "Int",
352+
Tags: `json:""`, // Not okay!
353+
Type: types.Int,
354+
},
355+
{
356+
Name: "Struct",
357+
Tags: `json:""`, // Okay, inlined.
358+
Type: someStruct,
359+
},
360+
{
361+
Name: "IntPtr",
362+
Tags: `json:""`, // Not okay!
363+
Type: intPtr,
364+
},
365+
{
366+
Name: "StructPtr",
367+
Tags: `json:""`, // Okay, inlined.
368+
Type: someStructPtr,
369+
},
370+
},
371+
},
372+
expected: []string{
373+
"Int",
374+
"IntPtr",
375+
},
376+
},
377+
{
378+
name: "metadata_no_pointers",
379+
t: &types.Type{
380+
Kind: types.Struct,
381+
Members: []types.Member{
382+
{
383+
Name: "ListMeta",
384+
Tags: `json:"listMeta"`, // Not okay, should be "metadata"!
385+
Type: listMeta,
386+
},
387+
{
388+
Name: "ObjectMeta",
389+
Tags: `json:"objectMeta"`, // Not okay, should be metadata"!
390+
Type: objectMeta,
391+
},
392+
{
393+
Name: "SomeStruct",
394+
Tags: `json:"metadata"`, // Not okay, name mismatch!
395+
Type: someStruct,
396+
},
397+
},
398+
},
399+
expected: []string{
400+
"ListMeta",
401+
"ObjectMeta",
402+
"SomeStruct",
403+
},
404+
},
405+
{
406+
name: "metadata_pointers",
407+
t: &types.Type{
408+
Kind: types.Struct,
409+
Members: []types.Member{
410+
{
411+
Name: "ListMeta",
412+
Tags: `json:"listMeta"`, // Okay, convention only applies to struct.
413+
Type: listMetaPtr,
414+
},
415+
{
416+
Name: "ObjectMeta",
417+
Tags: `json:"objectMeta"`, // Okay, convention only applies to struct.
418+
Type: objectMetaPtr,
419+
},
420+
{
421+
Name: "SomeStruct",
422+
Tags: `json:"metadata"`, // Not okay, name mismatch!
423+
Type: someStructPtr,
424+
},
425+
},
426+
},
427+
expected: []string{
428+
"SomeStruct",
429+
},
430+
},
341431
}
342432

343433
n := &NamesMatch{}

0 commit comments

Comments
 (0)