Add discriminated unions structure linter - #230
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: MatteoFari The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
|
||
| "golang.org/x/tools/go/analysis" | ||
| "golang.org/x/tools/go/analysis/passes/inspect" | ||
| "golang.org/x/tools/go/ast/inspector" |
There was a problem hiding this comment.
We have own inspector, so please use it instead of this.
| legacyUnionMarker = "union" | ||
| legacyUnionDiscriminatorMarker = "unionDiscriminator" | ||
| legacyUnionDiscriminatorLowerMarker = "uniondiscriminator" | ||
| legacyUnionMemberMarker = "unionMember" | ||
| k8sUnionDiscriminatorMarker = "k8s:unionDiscriminator" | ||
| k8sUnionMemberMarker = "k8s:unionMember" |
There was a problem hiding this comment.
Please move these markers to https://github.com/kubernetes-sigs/kube-api-linter/blob/main/pkg/markers/markers.go
| Name: name, | ||
| Doc: "Validates discriminated-union marker structure.", | ||
| Run: a.run, | ||
| Requires: []*analysis.Analyzer{inspect.Analyzer, markershelper.Analyzer, extractjsontags.Analyzer}, |
There was a problem hiding this comment.
| Requires: []*analysis.Analyzer{inspect.Analyzer, markershelper.Analyzer, extractjsontags.Analyzer}, | |
| []*analysis.Analyzer{inspector.Analyzer} |
| inspectPass, ok := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) | ||
| if !ok { | ||
| return nil, kalerrors.ErrCouldNotGetInspector | ||
| } | ||
|
|
||
| markersAccess, ok := pass.ResultOf[markershelper.Analyzer].(markershelper.Markers) | ||
| if !ok { | ||
| return nil, kalerrors.ErrCouldNotGetMarkers | ||
| } | ||
|
|
||
| jsonTags, ok := pass.ResultOf[extractjsontags.Analyzer].(extractjsontags.StructFieldTags) | ||
| if !ok { | ||
| return nil, kalerrors.ErrCouldNotGetJSONTags | ||
| } | ||
|
|
||
| inspectPass.Preorder([]ast.Node{(*ast.TypeSpec)(nil)}, func(n ast.Node) { | ||
| typeSpec, ok := n.(*ast.TypeSpec) | ||
| if !ok || typeSpec.Name == nil { | ||
| return | ||
| } | ||
|
|
||
| structType, ok := typeSpec.Type.(*ast.StructType) | ||
| if !ok { | ||
| return | ||
| } | ||
|
|
||
| union := buildUnionType(typeSpec, structType, markersAccess, jsonTags) | ||
| if union == nil { | ||
| return | ||
| } | ||
|
|
||
| a.reportStructureViolations(pass, union) | ||
| }) |
| } | ||
|
|
||
| func markerSpecifiesOptionalMember(marker markershelper.Marker) bool { | ||
| if strings.Contains(strings.ToLower(marker.String()), ",optional") { |
There was a problem hiding this comment.
I don't prefer string matching pattern. Is marker.Arguments insufficient ?
| return false | ||
| } | ||
|
|
||
| func isTruthyMarkerValue(value string) bool { |
There was a problem hiding this comment.
Why do we need it ? I could not come up with pattern.
There was a problem hiding this comment.
to normalize a couple of possible encodings in one place, but that was broader than this linter really needed.
| legacyUnionMarker = "union" | ||
| legacyUnionDiscriminatorMarker = "unionDiscriminator" | ||
| legacyUnionDiscriminatorLowerMarker = "uniondiscriminator" | ||
| legacyUnionMemberMarker = "unionMember" |
There was a problem hiding this comment.
| legacyUnionMarker = "union" | |
| legacyUnionDiscriminatorMarker = "unionDiscriminator" | |
| legacyUnionDiscriminatorLowerMarker = "uniondiscriminator" | |
| legacyUnionMemberMarker = "unionMember" | |
| UnionMarker = "union" | |
| UnionDiscriminatorMarker = "unionDiscriminator" | |
| UnionDiscriminatorLowerMarker = "uniondiscriminator" | |
| UnionMemberMarker = "unionMember" |
|
|
||
| func qualifyFieldName(typeName string, field *ast.Field) string { | ||
| fieldName := utils.FieldName(field) | ||
| if fieldName == "" { |
There was a problem hiding this comment.
Why do you fallback fieldName ?
db91ce1 to
e29cad4
Compare
|
@sivchari if the changes are good i can continue with CRD reachability and CEL enforcement. |
| } | ||
|
|
||
| func normalizedMarkerValue(value string) string { | ||
| return strings.ToLower(strings.TrimSpace(strings.Trim(value, `"'`))) |
There was a problem hiding this comment.
I'm not sure why we make value lower.
| UnionDiscriminatorMarker = "unionDiscriminator" | ||
|
|
||
| // UnionDiscriminatorLowerMarker is the lowercase variant of UnionDiscriminatorMarker accepted by existing APIs. | ||
| UnionDiscriminatorLowerMarker = "uniondiscriminator" |
There was a problem hiding this comment.
Is it right ? At least, I've only found useDiscriminator in k/k
https://github.com/search?q=repo%3Akubernetes%2Fkubernetes+uniondiscriminator&type=code
There was a problem hiding this comment.
yeah it was just too defensive
| if ok { | ||
| return normalizedMarkerValue(value) == "true" | ||
| } |
cb6425b to
594b077
Compare
594b077 to
f66067a
Compare
|
The Kubernetes project currently lacks enough contributors to adequately respond to all PRs. This bot triages PRs according to the following rules:
You can:
Please send feedback to sig-contributor-experience at kubernetes/community. /lifecycle stale |
|
/remove-lifecycle stale |
JoelSpeed
left a comment
There was a problem hiding this comment.
How would we feel about optionally also enforcing CEL validations for the union membership?
There are two patterns we use in openshift, one for required members, one for optional members. They are tied only to the discriminator value and then the field name itself, so scale well.
The linter could automatically detect these/configure these for users possibly?
Signed-off-by: Matteo Fari <matteofari06@gmail.com>
f66067a to
c17d507
Compare
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: MatteoFari The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
I originally thought about this PR as a first part and later add CRD reachability and CEL gating checks. Do you prefer that I include them in this PR? |
Description
This PR introduces a new
discriminatedunionslinter to enforce structural best practices for discriminated unions.The new linter,
discriminatedunions, does the following:+unionMember,optional).nonMemberFields: Forbid | Allow), withForbidas default.This is the first part and intentionally scopes to structure validation only.
CRD reachability and CEL gating checks will follow.
Part of #20