Skip to content

Commit 8e86c46

Browse files
authored
Merge pull request #181 from priyansh3006/ArrayOfStruct-Linter
feat: support ExactlyOneOf marker in arrayofstruct analyzer
2 parents cd3fb27 + eb4f5fd commit 8e86c46

File tree

3 files changed

+40
-0
lines changed

3 files changed

+40
-0
lines changed

pkg/analysis/arrayofstruct/analyzer.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import (
2020
"go/ast"
2121

2222
"golang.org/x/tools/go/analysis"
23+
2324
kalerrors "sigs.k8s.io/kube-api-linter/pkg/analysis/errors"
2425
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/extractjsontags"
2526
"sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/inspector"
2627
markershelper "sigs.k8s.io/kube-api-linter/pkg/analysis/helpers/markers"
2728
"sigs.k8s.io/kube-api-linter/pkg/analysis/utils"
29+
"sigs.k8s.io/kube-api-linter/pkg/markers"
2830
)
2931

3032
const name = "arrayofstruct"
@@ -38,6 +40,10 @@ var Analyzer = &analysis.Analyzer{
3840
Requires: []*analysis.Analyzer{inspector.Analyzer},
3941
}
4042

43+
func init() {
44+
markershelper.DefaultRegistry().Register(markers.KubebuilderExactlyOneOf)
45+
}
46+
4147
func run(pass *analysis.Pass) (any, error) {
4248
inspect, ok := pass.ResultOf[inspector.Analyzer].(inspector.Inspector)
4349
if !ok {
@@ -74,6 +80,13 @@ func checkField(pass *analysis.Pass, field *ast.Field, markersAccess markershelp
7480
return
7581
}
7682

83+
// Check if the struct has union markers that satisfy the required constraint
84+
if hasExactlyOneOfMarker(structType, markersAccess) {
85+
// ExactlyOneOf marker enforces that exactly one field is set,
86+
// so we don't need to report an error
87+
return
88+
}
89+
7790
// Check if at least one field in the struct has a required marker
7891
if hasRequiredField(structType, markersAccess) {
7992
return
@@ -197,3 +210,16 @@ func hasRequiredField(structType *ast.StructType, markersAccess markershelper.Ma
197210

198211
return false
199212
}
213+
214+
// hasExactlyOneOfMarker checks if the struct has an ExactlyOneOf marker,
215+
// which satisfies the required field constraint by ensuring exactly one field is set.
216+
func hasExactlyOneOfMarker(structType *ast.StructType, markersAccess markershelper.Markers) bool {
217+
if structType == nil {
218+
return false
219+
}
220+
221+
// Use StructMarkers to get the set of markers on the struct
222+
markerSet := markersAccess.StructMarkers(structType)
223+
224+
return markerSet.Has(markers.KubebuilderExactlyOneOf)
225+
}

pkg/analysis/arrayofstruct/testdata/src/a/a.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,14 @@ type ValidStructWithCustomBasicType struct {
150150
// This should not trigger the linter because CustomString is based on string, a basic type
151151
Items []CustomString
152152
}
153+
154+
// Valid case - struct with ExactlyOneOf marker
155+
type ValidWithExactlyOneOf struct {
156+
Items []ValidExactlyOneOfItem
157+
}
158+
159+
// +kubebuilder:validation:ExactlyOneOf=FieldA;FieldB
160+
type ValidExactlyOneOfItem struct {
161+
FieldA *string `json:"fieldA,omitempty"`
162+
FieldB *string `json:"fieldB,omitempty"`
163+
}

pkg/markers/markers.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ const (
9393
// KubebuilderRequiredMarker is the marker that indicates that a field is required in kubebuilder.
9494
KubebuilderRequiredMarker = "kubebuilder:validation:Required"
9595

96+
// KubebuilderExactlyOneOf is the marker that indicates that a field has an exactly one of in kubebuilder.
97+
KubebuilderExactlyOneOf = "kubebuilder:validation:ExactlyOneOf"
98+
9699
// KubebuilderItemsMaxLengthMarker is the marker that indicates that a field has a maximum length in kubebuilder.
97100
KubebuilderItemsMaxLengthMarker = "kubebuilder:validation:items:MaxLength"
98101

0 commit comments

Comments
 (0)