Skip to content

Commit 84abae7

Browse files
authored
feat(AIP-193): specify how to "change" messages. (#1098)
Introduces the concept that `LocalizedMessage` can be used to include a second error message aside from the debug message on `Status`. This message can be updated ongoingly. Further, explains how to use the `metadata` `map<string, string>` on `ErrorInfo` for dynamic variables found in error messages and otherwise. Fixes: #1096
1 parent d8428b1 commit 84abae7

1 file changed

Lines changed: 169 additions & 14 deletions

File tree

aip/general/0193.md

Lines changed: 169 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,39 @@ Error messages **should** be brief but actionable. Any extra information
3232
necessary, you **should** provide a link where a reader can get more
3333
information or ask questions to help resolve the issue.
3434

35+
36+
A JSON representation of an error response might look like the
37+
following:
38+
39+
<a name="sample"></a>
40+
41+
```json
42+
{
43+
"error": {
44+
"code": 429,
45+
"message": "The zone 'us-east-1' does not have enough resources available to fulfill the request. Try a different zone, or try again later.",
46+
"status": "RESOURCE_EXHAUSTED",
47+
"details": [
48+
{
49+
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
50+
"reason": "STOCKOUT",
51+
"domain": "compute.googleapis.com",
52+
"metadata": {
53+
"zone": "us-east1-a",
54+
"vmType": "e2-medium",
55+
"attachment": "local-ssd=3,nvidia-t4=2"
56+
}
57+
},
58+
{
59+
"@type": "type.googleapis.com/google.rpc.LocalizedMessage",
60+
"locale": "en-US",
61+
"message": "A e2-medium with local-ssd=3,nvidia-t4=2 is currently unavailable in the us-east1-a zone.\nCapacity changes frequently, so try your request in a different zone, with a different VM hardware configuration, or at a later time. For more information, see troubleshooting documentation."
62+
}
63+
]
64+
}
65+
}
66+
```
67+
3568
### Details
3669

3770
Google defines a set of [standard detail payloads][details] for error details,
@@ -62,11 +95,117 @@ a [`PreconditionFailure`][PreconditionFailure].
6295
**Note:** `ErrorInfo` represents a special case. There **must** be exactly one
6396
`ErrorInfo`. It is required.
6497

65-
### Localization
98+
### Error messages
99+
100+
For each error, the service **must** populate the `message` field on
101+
[`google.rpc.Status`][Status]. This error message,
102+
103+
- is a developer-facing, human-readable "debug message"
104+
- is presented in the service's native language
105+
- both explains the error and offers an actionable resolution to it
106+
([citation](https://cloud.google.com/apis/design/errors#error_model))
107+
108+
**Note:** Sometimes a service will elect to always present
109+
`Status.message` in English rather than the application's native
110+
language so that messages are easily searchable in common knowledge
111+
bases, such as StackOverflow&trade;.
112+
113+
When introducing an error that represents a failure scenario that did
114+
not previously occur for the service, the payload **must** include
115+
`ErrorInfo` and any variables found in dynamic segments of the error
116+
message **must** be present in `ErrorInfo.metadata`. See, "[Dynamic
117+
variables](#dynamic-variables)".
118+
119+
#### Changing error messages
120+
121+
<a name="status-message-warning"></a>
122+
123+
`Status.message` **may** change. However, **use extreme caution** when
124+
doing so. API consumers often treat this error message as **part of the
125+
API contract**. Clients perform string matches on the text to
126+
differentiate one error for another and even parse the error message to
127+
extract variables from dynamic segments.
128+
129+
There is a safer alternative. The service can chose to include an error
130+
message by adding [`google.rpc.LocalizedMessage`][LocalizedMessage] to
131+
[`Status.details`][Status details].
132+
133+
The error message presented in `LocalizedMessage.message` **may** be the
134+
same as `Status.message` or it **may** be an alternate message.
135+
136+
Reasons to present the same error message in both locations include the
137+
following:
138+
139+
- The service plans to localize either immediately or in the near
140+
future. See, "[Localization](#localization)".
141+
- It affords clients the ability to find an error message consistently
142+
in one location, `LocalizedMessaage.message`, across all methods of
143+
the API Service.
144+
145+
Reasons to present an error message in `LocalizedMessage.message` that
146+
differs from `Status.message` include the following:
147+
148+
- The service requires an end-user facing error message that differs
149+
from the "debug message".
150+
- Ongoing, iterative error message improvements are expected.
66151

67-
Error messages **must** be in English. If a localized error message is also
68-
required, the service **should** use [`google.rpc.LocalizedMessage`][details]
69-
in the `details` field.
152+
When including `LocalizedMessage`, both fields, `locale` and `message`,
153+
**must** be populated. If the service is to be localized, the value of
154+
`locale` **must** change dynamically. See,
155+
"[Localization](#localization)". Otherwise, `locale` **must** always
156+
present the service's default locale, e.g. "en-US".
157+
158+
When adding an error message via `LocalizedMessage`, `ErrorInfo`
159+
**must** be introduced either before or at the same time. If there are
160+
dynamic segments found in the text, the values of these variables
161+
**must** be included in `ErrorInfo.metadata`. See, "[Dynamic
162+
variables](#dynamic-variables)".
163+
164+
**Warning:** If `LocalizedMessage` is released without `ErrorInfo`, the
165+
same [concerns](#status-message-warning) regarding client misuse of
166+
textual error messages apply.
167+
168+
#### Dynamic variables
169+
170+
The best, actionable error messages include dynamic segments. These
171+
variable parts of the message are specific to a particular request.
172+
Consider the following example:
173+
174+
> The Book, "The Great Gatsby", is unavailable at the Library, "Garfield
175+
> East". It is expected to be available again on 2199-05-13.
176+
177+
The preceding error message is made actionable by the context, both that
178+
originates from the request, the title of the Book and the name of the
179+
Library, and by the information that is known only by the service, i.e.
180+
the expected return date of the Book.
181+
182+
All dynamic variables found in error messages **must** also be present
183+
in the `map<string, string>`, `ErrorInfo.metadata` (found on the
184+
*required* `ErrorInfo`). For example, the `metadata` map for the sample
185+
error message above will include *at least* the following key/value
186+
pairs:
187+
188+
```yaml
189+
bookTitle: "The Great Gatsby"
190+
library: "Garfield East"
191+
expectedReturnDate: "2199-05-13"
192+
```
193+
194+
Dynamic variables that do not appear in an error message **may** also be
195+
included in `metadata` to provide additional information to the client
196+
to be used programmatically.
197+
198+
Once present in `metadata`, keys **must** continue to be included in the
199+
map for the error payload to be backwards compatible, even if the value
200+
for a particular key is empty. Keys **must** be expressed as lower
201+
camel-case.
202+
203+
#### Localization
204+
205+
The value of `Status.message` **should** be presented in the service's
206+
native language. If a localized error message is required, the service
207+
**must** include [`google.rpc.LocalizedMessage`][LocalizedMessage] in
208+
`Status.details`.
70209

71210
### Partial errors
72211

@@ -96,16 +235,26 @@ does not exist, the service **must** error with `NOT_FOUND` (HTTP 404).
96235

97236
## Rationale
98237

99-
`ErrorInfo` is required because it further identifies an error. The
100-
`Status` field, with under 20 [allowed values][Code], is rarely enough
101-
to disambiguate one error from another accross an entire API Service.
102-
Error messages often contain dynamic segments that express values, such
103-
as project numbers or locations, and these values are often read from
104-
the string message using brittle extraction methods, like RegExp. There
105-
needs to be a machine readable component of *every* error response that
106-
enables clients to pull such information programatically--without
107-
parsing the string error message. For these reasons, `ErrorInfo` is
108-
required for *every* error response.
238+
### Requiring ErrorInfo
239+
240+
`ErrorInfo` is required because it further identifies an error. With
241+
only approximately twenty [available values][Code] for `Status.status`,
242+
it is difficult to disambiguate one error from another across an entire
243+
[API Service][API Service].
244+
245+
Also, error messages often contain dynamic segments that express
246+
variable information, so there needs to be machine readable component of
247+
*every* error response that enables clients to use such information
248+
programmatically.
249+
250+
### LocalizedMessage
251+
252+
`LocalizedMessage` was selected as the location to present alternate
253+
error messages. This is desirable when clients need to display a crafted
254+
error message directly to end users. `LocalizedMessage` can be used with
255+
a static `locale`. This may seem misleading, but it allows the service
256+
to eventually localize without having to duplicate or move the error
257+
message, which would be a backwards incompatible change.
109258

110259
## Further reading
111260

@@ -115,6 +264,9 @@ required for *every* error response.
115264

116265
## Changelog
117266

267+
- **2023-05-17**: Change the recommended language for `Status.message`
268+
to be the service's native language rather than English.
269+
- **2023-05-17**: Specify requirements for changing error messages.
118270
- **2023-05-10**: Require [`ErrorInfo`][ErrorInfo] for all error
119271
responses.
120272
- **2023-05-04**: Require uniqueness by message type for error details.
@@ -133,8 +285,11 @@ required for *every* error response.
133285
[ErrorInfo]: https://github.com/googleapis/googleapis/blob/6f3fcc058ff29989f6d3a71557a44b5e81b897bd/google/rpc/error_details.proto#L27-L76
134286
[PreconditionFailure]: https://github.com/googleapis/googleapis/blob/6f3fcc058ff29989f6d3a71557a44b5e81b897bd/google/rpc/error_details.proto#L139-L166
135287
[BadRequest]: https://github.com/googleapis/googleapis/blob/477a59d764428136ba1d857a9633c0d231de6efa/google/rpc/error_details.proto#L168-L218
288+
[LocalizedMessage]: https://github.com/googleapis/googleapis/blob/e9897ed945336e2dc967b439ac7b4be6d2c62640/google/rpc/error_details.proto#L275-L285
136289
[grpc status code documentation]: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md
137290
[Code]: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
138291
[Status]: https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto
292+
[Status details]: https://github.com/googleapis/googleapis/blob/aeae5ea2b01ece6c0cee046ae84b881cdc62b95d/google/rpc/status.proto#L46-L48
139293
[long-running operations]: ./0151.md
294+
[API Service]: https://cloud.google.com/apis/design/glossary#api_service
140295
<!-- prettier-ignore-end -->

0 commit comments

Comments
 (0)