Skip to content

Commit 33bdd60

Browse files
authored
Merge pull request #8486 from JoelSpeed/update-required-fields-conventions
Update guidance on required field serialization
2 parents f68ed6c + 48f3ac9 commit 33bdd60

File tree

1 file changed

+63
-21
lines changed

1 file changed

+63
-21
lines changed

contributors/devel/sig-architecture/api-conventions.md

Lines changed: 63 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -822,50 +822,92 @@ indicated in the Retry-After header, if it is present).
822822

823823
Fields must be either optional or required.
824824

825+
A field that is required means that the writer must express an opinion about the value of the field.
826+
Required fields are always present, and readers can rely on the field being present in the object.
827+
828+
An optional field is one where the writer may choose to omit the field entirely.
829+
Readers should not assume that the field is present, unless the field also has a server-side default value.
830+
831+
Default values can only be set on optional fields.
832+
825833
Optional fields have the following properties:
826834

827835
- They have the `+optional` comment tag in Go.
828836
- They are a pointer type in the Go definition (e.g. `AwesomeFlag *SomeFlag`) or
829837
have a built-in `nil` value (e.g. maps and slices).
838+
- They are marked with the `omitempty` json struct tag in the Go definition.
830839
- The API server should allow POSTing and PUTing a resource with this field
831840
unset.
832841

833-
In most cases, optional fields should also have the `omitempty` struct tag (the
834-
`omitempty` option specifies that the field should be omitted from the json
835-
encoding if the field has an empty value). However, If you want to have
836-
different logic for an optional field which is not provided vs. provided with
837-
empty values, do not use `omitempty` (e.g. https://github.com/kubernetes/kubernetes/issues/34641).
842+
When the field type has a built-in `nil` value, such as a map or a slice, and
843+
your use case means that you need to be able to distinguish between
844+
"field not set" and "field set to an empty value", you should use a pointer to
845+
the type, even though it has a built-in `nil` value.
846+
See https://github.com/kubernetes/kubernetes/issues/34641.
838847

839848
Note that for backward compatibility, any field that has the `omitempty` struct
840-
tag will be considered to be optional, but this may change in the future and
841-
having the `+optional` comment tag is highly recommended.
849+
tag, and is not explicitly marked as `+required`, will be considered to be optional.
850+
This is expected to change in the future, and new fields should explicitly set either
851+
an `+optional` or `+required` comment tag.
842852

843-
Required fields have the opposite properties, namely:
853+
Required fields have the following properties:
844854

845855
- They do not have an `+optional` comment tag.
846-
- They do not have an `omitempty` struct tag.
847-
- They are not a pointer type in the Go definition (e.g. `AnotherFlag SomeFlag`).
856+
- They mark themselves as required explicitly with a `+required` comment tag.
848857
- The API server should not allow POSTing or PUTing a resource with this field
849858
unset.
859+
- They _typically_ do not use pointer types in the Go definition (e.g. `AnotherFlag SomeFlag`), though required fields where the zero value is a valid value must use pointer types, paired with an `omitempty` struct tag to avoid spurious null serializations.
860+
861+
For more details on how to use pointers and `omitempty` with fields, see [Serialization of optional/required fields](#serialization-of-optionalrequired-fields).
850862

851863
Using the `+optional` or the `omitempty` tag causes OpenAPI documentation to
852864
reflect that the field is optional.
853865

866+
### Serialization of optional/required fields
867+
854868
Using a pointer allows distinguishing unset from the zero value for that type.
855-
There are some cases where, in principle, a pointer is not needed for an
856-
optional field since the zero value is forbidden, and thus implies unset. There
857-
are examples of this in the codebase. However:
869+
There are some cases where, in principle, a pointer is not needed for a
870+
field since the zero value is forbidden, and thus implies unset.
871+
There are examples of this in the codebase. However:
872+
873+
- It can be difficult for implementors to anticipate all cases where an empty
874+
value might need to be distinguished from a zero value.
875+
- Structs are not omitted from encoder output even where `omitempty` is specified,
876+
which is messy.
877+
878+
To determine whether a field should be a pointer, consider the following:
879+
880+
```mermaid
881+
graph TD;
882+
A[Start] --> B{Is the zero value a valid user choice?};
883+
B -- Yes --> C[Use a pointer and omitempty];
884+
B -- No --> D{Is the field optional or required?};
885+
D -- Optional --> E["Does the field type have a built-in nil value (map or slice)?"];
886+
E -- Yes --> F[Do not use a pointer, use omitempty];
887+
E -- No --> C;
888+
D -- Required --> F
889+
```
890+
There are several implications of the above:
891+
- For lists and maps where the zero valid is a valid user choice, this means that `[]` and `{}` have a semantically different meaning than unset, in this case it is appropriate to use `*[]T` or `*map[T]S` respectively.
892+
- For `bool` types, the zero value is `false`, which is always a valid user choice. `bool` types should always be pointers, and should always use the `omitempty` tag.
893+
- When a field is required, and the zero value is not valid, a structured client who has not expressed an explicit choice will have their request rejected by the API server based on the invalid value, rather than the field being unset.
894+
- For example, a string with a minimum length of 1; Validation would not understand if the field was unset, or set to the empty string deliberately, but would still reject the request because it did not meet the length requirements.
895+
- Technically, using a pointer in these cases is also acceptable, but not advised as it makes coding more complex, and increases the risk of nil pointer exceptions.
896+
- In these cases, not using `omitempty` provides the same result, but pollutes the marshaled object with zero values and is not recommended.
897+
- For structs, the zero value can only be valid when the struct has no required fields, and does not require at least one property to be set.
898+
- Required structs should use `omitzero` to avoid marshalling the zero value.
899+
900+
#### Serialization of custom resources
901+
902+
When custom resources are admitted by the API server, openapi validation is applied to the object _prior_ to any structured client observing the object.
858903

859-
- it can be difficult for implementors to anticipate all cases where an empty
860-
value might need to be distinguished from a zero value
861-
- structs are not omitted from encoder output even where omitempty is specified,
862-
which is messy;
863-
- having a pointer consistently imply optional is clearer for users of the Go
864-
language client, and any other clients that use corresponding types
904+
For a field where the zero value is not valid, the openapi validation will reject the object if the field is present and set to the zero value,
905+
before a controller or validation webhook could observe the field.
865906

866-
Therefore, we ask that pointers always be used with optional fields that do not
867-
have a built-in `nil` value.
907+
This means that there is no need to distinguish in a custom resource, between unset and the zero value for fields where the zero value is not valid.
908+
In these cases, pointers are not needed, as the zero value indicates to the structured client that the field is unset.
868909

910+
This can be beneficial to API authors, as it reduces the complexity of the API, and reduces the risk of nil pointer exceptions in controllers.
869911

870912
## Defaulting
871913

0 commit comments

Comments
 (0)