Skip to content

Commit 75b1c97

Browse files
authored
Add generic advice for OAS (#373)
1 parent 931c01f commit 75b1c97

File tree

1 file changed

+130
-33
lines changed

1 file changed

+130
-33
lines changed

aep/general/0146/aep.md.j2

Lines changed: 130 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,12 @@ For example, an API **should not** use a completely generic field (such as
2020
correspond to one of a known number of schemas. Instead, the API **should** use
2121
a [`oneof`](./oneof) to represent the known schemas.
2222

23-
### Generic fields in protobuf APIs
23+
### Oneof
2424

25-
#### Oneof
26-
27-
A `oneof` **may** be used to introduce a type union: the user or API is able to
28-
specify one of the fields inside the `oneof`. Additionally, a `oneof` **may**
29-
be used with the same type (usually strings) to represent a semantic difference
30-
between the options.
31-
32-
Because the individual fields in the `oneof` have different keys, a developer
33-
can programmatically determine which (if any) of the fields is populated.
25+
A `oneof` (proto) or `oneOf` (OpenAPI) **may** be used to introduce a type
26+
union: the user or API is able to specify one of the fields or schemas.
27+
Additionally, it **may** be used with the same type (usually strings) to
28+
represent a semantic difference between the options.
3429

3530
A `oneof` preserves the largest degree of type safety and semantic meaning for
3631
each option, and APIs **should** generally prefer them over other generic or
@@ -39,11 +34,51 @@ when there is a large (or unlimited) number of potential options, or when there
3934
is a large resource structure that would require a long series of "cascading
4035
oneofs".
4136

42-
**Note:** Adding additional possible fields to an existing `oneof` is a
43-
non-breaking change, but moving existing fields into or out of a `oneof` is
44-
breaking (it creates a backwards-incompatible change in Go protobuf stubs).
37+
**Note:** Adding additional fields or schemas to an existing `oneof` is a
38+
non-breaking change. However, removing or moving fields is breaking.
4539

46-
#### Maps
40+
{% tab proto %}
41+
42+
Because the individual fields in the `oneof` have different keys, a developer
43+
can programmatically determine which (if any) of the fields is populated.
44+
45+
Moving existing fields into or out of a `oneof` creates a
46+
backwards-incompatible change in Go protobuf stubs.
47+
48+
{% tab oas %}
49+
50+
```yaml
51+
components:
52+
schemas:
53+
PaymentMethod:
54+
type: object
55+
properties:
56+
payment:
57+
oneOf:
58+
- type: object
59+
properties:
60+
creditCard:
61+
type: string
62+
description: Credit card token
63+
- type: object
64+
properties:
65+
bankAccount:
66+
type: string
67+
description: Bank account identifier
68+
- type: object
69+
properties:
70+
giftCard:
71+
type: string
72+
description: Gift card code
73+
```
74+
75+
Because the individual schemas in the `oneOf` can be distinguished (through
76+
discriminators or schema validation), a developer can programmatically
77+
determine which (if any) of the schemas matches the provided value.
78+
79+
{% endtabs %}
80+
81+
### Maps
4782

4883
Maps **may** be used in situations where many values _of the same type_ are
4984
needed, but the keys are unknown or user-determined.
@@ -54,25 +89,77 @@ sometimes be suited to a situation where many objects of the same type are
5489
needed, with different behavior based on the names of their keys (for example,
5590
using keys as environment names).
5691

57-
#### Struct
92+
{% tab proto %}
93+
94+
{% tab oas %}
95+
96+
```yaml
97+
components:
98+
schemas:
99+
Configuration:
100+
type: object
101+
properties:
102+
environments:
103+
type: object
104+
additionalProperties:
105+
type: object
106+
properties:
107+
replicas:
108+
type: integer
109+
region:
110+
type: string
111+
```
112+
113+
Maps are represented using `additionalProperties` in OpenAPI.
58114

59-
The [`google.protobuf.Struct`][struct] object **may** be used to represent
60-
arbitrary nested JSON. Keys can be strings, and values can be floats, strings,
61-
booleans, arrays, or additional nested structs, allowing for an arbitrarily
62-
nested structure that can be represented as JSON (and is automatically
63-
represented as JSON when using REST/JSON).
115+
{% endtabs %}
64116

65-
A `Struct` is most useful when the API does not know the schema in advance, or
66-
when a API needs to store and retrieve arbitrary but structured user data.
67-
Using a `Struct` is convenient for users in this case because they can easily
68-
get JSON objects that can be natively manipulated in their environment of
69-
choice.
117+
### Free-form objects
70118

71-
If a API needs to reason about the _schema_ of a `Struct`, it **should** use
72-
[JSONSchema][] for this purpose. Because JSONSchema is itself JSON, a valid
73-
JSONSchema document can itself be stored in a `Struct`.
119+
Free-form objects **may** be used to represent arbitrary nested JSON. Keys can
120+
be strings, and values can be numbers, strings, booleans, arrays, or additional
121+
nested objects, allowing for an arbitrarily nested structure that is
122+
represented as JSON.
74123

75-
#### Any
124+
A free-form object is most useful when the API does not know the schema in
125+
advance, or when a API needs to store and retrieve arbitrary but structured
126+
user data. Using a free-form object is convenient for users in this case
127+
because they can easily get JSON objects that can be natively manipulated in
128+
their environment of choice.
129+
130+
If a API needs to reason about the _schema_ of a free-form object, it
131+
**should** use [JSONSchema][] for this purpose. Because JSONSchema is itself
132+
JSON, a valid JSONSchema document can itself be stored in a free-form object.
133+
134+
{% tab proto %}
135+
136+
The [`google.protobuf.Struct`][struct] object represents arbitrary nested JSON,
137+
and is automatically represented as JSON when using REST/JSON.
138+
139+
{% tab oas %}
140+
141+
```yaml
142+
components:
143+
schemas:
144+
CustomResource:
145+
type: object
146+
properties:
147+
name:
148+
type: string
149+
metadata:
150+
type: object
151+
description: Arbitrary structured data
152+
additionalProperties: true
153+
```
154+
155+
Free-form objects are represented using `type: object` with
156+
`additionalProperties: true` or without defined properties.
157+
158+
{% endtabs %}
159+
160+
### Any
161+
162+
{% tab proto %}
76163

77164
The [`google.protobuf.Any`][any] object can be used to send an arbitrary
78165
serialized protocol buffer and a type definition.
@@ -83,12 +170,22 @@ the proto. Additionally, even if the consumer _does_ have the proto, the
83170
consumer has to ensure the type is registered and then deserialize manually,
84171
which is an often-unfamiliar process.
85172

86-
Because of this, `Any` **should not** be used unless other options are
87-
infeasible.
173+
{% tab oas %}
174+
175+
```yaml
176+
components:
177+
schemas:
178+
GenericValue:
179+
type: object
180+
properties:
181+
data: {} # Empty schema - accepts any value
182+
```
88183

89-
### Generic fields in OAS APIs
184+
An empty schema (represented as `{}`) accepts any valid JSON value, including
185+
primitives, objects, and arrays. This is the OpenAPI equivalent of
186+
`google.protobuf.Any`.
90187

91-
**Note:** OAS-specific guidance not yet written.
188+
{% endtabs %}
92189

93190
<!-- prettier-ignore-start -->
94191
[any]: https://github.com/protocolbuffers/protobuf/tree/master/src/google/protobuf/any.proto

0 commit comments

Comments
 (0)