-
Notifications
You must be signed in to change notification settings - Fork 25
New Linter: NonPointerStructs #189
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: JoelSpeed The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
I concur with the description WRT non-pointer struct fields. For builtin types with standard Go clients, the struct will be serialized as For types which use other clients and implement something closer to the "everything is a pointer" ideal, serialization might not have the struct at all. For "required", validation will produce a "field is required" error on the struct key itself, and for "optional" it will see the absence of the struct key. Slightly different errors, but same count and flavor, and sufficiently unambiguous (subjective). Now, should we talk about non-pointer primitive fields WRT optional/required and the impact of "omitempty"? :) We have fields that are required strings which use omitempty and which do not. We have fields that are required ints, but none I could see use omitempty. We have fields that are optional strings which use omitempty and which do not. We have fields that are optional ints, but AFAICT they all use omitempty. So we get to think about all those modes. |
I don't understand the reasoning behind these two rules. It seems this would limit the evolution of CRDs. For example, consider the following scenario: type MySpec struct {
// +required
Foo Foo
}
type Foo struct {
// +required
field1 string
// +optional
field2 string
}Now, let's say a developer wants to add a new field, type MySpec struct {
// +required -> +optional (This change would be blocked by the linter)
Foo Foo
// +optional
Bar *Bar
}
type Foo struct {
// +required
field1 string
// +optional
field2 string
}
type Bar struct {
// +required
field3 string
// +optional
field4 string
}The proposed linting rule would prevent changing What is the recommended approach for developers in this situation? |
How is that compatible? Anyone using your typed client is broken. Assuming you're OK with that, the next rule applies:
If We can't even do tricks like decoding into We could have different rules for builtins vs. CRDs but it seems minimally useful for the cost? |
Yes, this is backward incompatible change. I made a typo in my last comment.
I agree that CRDs should have constraints that are as consistent as possible with native types. CRDs are often published and consumed as libraries, where backward compatibility is just as critical. This is precisely why a linting rule that prevents this common and backward-compatible evolution pattern would be problematic for the CRD ecosystem. It would force developers into making breaking changes where they would otherwise not be necessary. |
|
If you can't change Foo to a pointer, you can't really do a one-of without making your clients exceedingly wierd. I think it's allowed to change a struct with one field into a one-of, assuming you knew that was coming. That would appear to violate "Any non-pointer struct field that has no required fields, must be marked optional" but "Any non-point(sic) struct that validates a minimum number of properties (e.g. a non-discriminated union must have exactly one field set) must be marked as required" also applies. Perhaps:
|
It can be achieved like this. https://go.dev/play/p/Nabp1xS4UmV . Are we sure all native types are conforming with these rules? |
|
Discussed a bit IRL. The problem is that a non-pointer struct field which only has optional fuels, but is "required" is meaningless unless we do some validation on the serialized form, and even then is still ambiguous wrt Go clients. To go with the example above, suppose a client has: The serialized form will still have The same problem also exists in reverse. If I have an optional struct-type field with a required child field, I can't tell if you intended to invoke the optionalness or if you should get an error because of requiredness. So it is simpler just say "you can't do that". |
|
Now, I understand the reasoning behind these. Thank you for explaining this, @thockin . There will be a few exemptions, mostly for discriminated unions, that should be fine. |
Per conversation at the maintainer summit on Sunday, for built-in types, we must follow these rules
This new linter is enforcing the first half of this, I believe existing linters that we have will cover the second half already (see
optionalfieldsandrequiredfields)/assign @thockin
@thockin Can you please confirm the above is what we agreed