Skip to content

Commit a67ef11

Browse files
committed
sketch open api
1 parent 0d0bcb7 commit a67ef11

File tree

1 file changed

+119
-21
lines changed
  • keps/sig-api-machinery/1027-api-unions

1 file changed

+119
-21
lines changed

keps/sig-api-machinery/1027-api-unions/README.md

Lines changed: 119 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -247,13 +247,19 @@ kubebuilder types):
247247
discriminator values). This field MUST be required if there is no default
248248
option, omitempty if the default option is the empty string, or optional and
249249
omitempty if a default value is specified with the `// +default` marker.
250-
- `// +unionMember=<memberName> before a field means that this
250+
- `// +unionMember=<memberName>,<optional> before a field means that this
251251
field is a member of a union. The `<memberName>` is the name of the field that will be set as the discriminator value.
252252
It MUST correspond to one of the valid enum values of the discriminator's enum
253253
type. It defaults to the go (i.e `CamelCase`) representation of the field name if not specified.
254254
`<memberName>` should only be set if authors want to customize how the fields
255255
are represented in the discriminator field. `<memberName>` should match the
256-
serialized JSON name of the field case-insensitively.
256+
serialized JSON name of the field case-insensitively. The comma separated
257+
optional value determines whether or not the member field must be set when the
258+
discriminator selects it. Meaning, when `optional` is present on a field, the
259+
discriminator can select the field even if the field is not set. If optional
260+
is not present in the `unionMember` tag, then the object will fail validation
261+
if the discriminator selects the field but it is nil. A field can be marked as
262+
optional without specifying memberName via `// +unionMember,optional`.
257263
- `// +unionDiscriminatedBy=<discriminatorName>` before a member field identifies which
258264
discriminator (and thus which union) the member field belongs to. Optional
259265
unless there are multiple unions/discriminators in a single struct. If used,
@@ -270,15 +276,10 @@ Because, there are only a few specific values that the discriminator can be, we
270276
propose that all discriminators should be defined as an enum, and should be
271277
tagged so via the enum go marker `// +enum`.
272278

273-
Required unions will have the number of valid discriminator values equal to the
274-
number of member fields (see exception below on empty union members). Optional
275-
unions will have the number of valid discriminator values equal to the number of
276-
member fields, plus one additional value for when "no member" is desired. By
277-
convention, this "no member" discriminator value should be the empty string.
278-
279-
We define optional unions as union where "at most" one member field of the union
280-
must be non-nil (as opposed to a required union, where "exactly" one member
281-
field of the union must be non-nil).
279+
If no option is a valid option for a union, such an option must be defined as a
280+
member of the discriminator values enum. By convention, this "no member"
281+
discriminator should be the empty string, but there is nothing stopping API
282+
authors from defining their own "no option" discriminator value.
282283

283284
##### Empty Union Members
284285

@@ -423,16 +424,113 @@ for validation, but is "on-top" of this proposal.
423424
A new extension is created in the openapi to describe the behavior:
424425
`x-kubernetes-unions`.
425426

426-
This is a list of unions that are part of this structure/object. Here is what
427-
each list item is made of:
428-
- `discriminator: <discriminator>` is set to the name of the discriminator
429-
field, if present,
430-
- `fields-to-discriminateBy: {"<fieldName>": "<discriminateName>"}` is a map of
431-
fields that belong to the union to their discriminated names. The
432-
discriminatedValue will typically be set to the name of the Go variable.
427+
This is a list of unions that are part of this structure/object. Each item in
428+
the list represents a discriminator for the union, a list of valid discriminator
429+
unions that do not correspond to member fields, and for each member field,
430+
the discriminator value of that field and whether or not that field is optional.
433431

434432
Conversion between OpenAPI v2 and OpenAPI v3 will preserve these fields.
435433

434+
The following is an example of what the generated OpenAPI definition will look
435+
like for a given go type.
436+
437+
```
438+
const (
439+
FieldA Union1Type = "FieldA"
440+
FieldB Union1Type = "FieldB"
441+
FieldC Union1Type = "FieldC"
442+
FieldD Union1Type = "FieldD"
443+
FieldNone Union1Type = ""
444+
)
445+
const (
446+
Alpha Union2Type = "ALPHA"
447+
Beta Union2Type = "BETA"
448+
Gamma Union2Type = "GAMMA"
449+
Null Union2Type = "NULL"
450+
)
451+
452+
// This will generate one union, with two fields and a discriminator.
453+
type Union struct {
454+
// +unionDiscriminator
455+
// +required
456+
Union1 Union1Type `json:"union1"`
457+
458+
// +unionMember
459+
// +unionDiscriminatedBy=Union1
460+
// +optional
461+
FieldA int `json:"fieldA"`
462+
// +unionMember,optional
463+
// +unionDiscriminatedBy=Union1
464+
// +optional
465+
FieldB int `json:"fieldB"`
466+
467+
// +unionDiscriminator
468+
// +required
469+
Union2 Union2Type `json:"union2"`
470+
471+
// +unionMember=ALPHA,
472+
// +unionDiscriminatedBy=Union2
473+
// +optional
474+
Alpha int `json:"alpha"`
475+
// +unionMember=BETA,optional
476+
// +unionDiscriminatedBy=Union2
477+
// +optional
478+
Beta int `json:"beta"`
479+
}
480+
```
481+
482+
turns into:
483+
```
484+
OpenAPIDefinition{
485+
Schema: spec.Schema{
486+
SchemaProps: spec.SchemaProps{
487+
... // schema props omitted
488+
},
489+
VendorExtensible: spec.VendorExtensible{
490+
Extensions: spec.Extensions{
491+
"x-kubernetes-unions": []interface{}{
492+
map[string]interface{}{
493+
"discriminator": "Union1",
494+
"emptyMembers":[]string{
495+
"FieldC",
496+
"FieldD",
497+
"",
498+
},
499+
"fields-to-discriminateBy": map[string]interface{}{
500+
"FieldA": map[string]interface{}{
501+
"discriminatorValue": "FieldA",
502+
"optional": false,
503+
}
504+
"FieldB": map[string]interface{}{
505+
"discriminatorValue": "FieldB",
506+
"optional": true,
507+
}
508+
}
509+
},
510+
map[string]interface{}{
511+
"discriminator": "Union2",
512+
"emptyMembers":[]string{
513+
"GAMMA",
514+
"NULL",
515+
},
516+
"fields-to-discriminateBy": map[string]interface{}{
517+
"Alpha": map[string]interface{}{
518+
"discriminatorValue": "ALPHA",
519+
"optional": false,
520+
}
521+
"Beta": map[string]interface{}{
522+
"discriminatorValue": "BETA",
523+
"optional": true,
524+
}
525+
}
526+
}
527+
}
528+
}
529+
}
530+
}
531+
}
532+
```
533+
436534
### Normalization and Validation
437535

438536
#### Normalization
@@ -462,11 +560,11 @@ called by the request handlers shortly after mutating admission occurs.
462560
Objects must be validated AFTER the normalization process.
463561

464562
Some validation situations specific to unions are:
465-
1. When multiple union fields are set and the discriminator is not set we should
563+
1. When multiple union fields are set and the discriminator has not been modified we should
466564
error loudly that the client must change the discriminator if it changes any
467565
union member fields.
468-
2. When the server receiveds a request with a discriminator set to a given
469-
field, but that given field is empty, the server should fail with a clear
566+
2. When the server receives a request with a discriminator set to a given
567+
field, but that given field is empty and not marked as optional, the server should fail with a clear
470568
error message. Note this does not apply to discriminator values that do not
471569
correspond to any field (as in the "empty union members case").
472570

0 commit comments

Comments
 (0)