From d9bd41bff7212ad6edca73d0adda192729c27402 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 26 Sep 2024 14:37:58 +0100 Subject: [PATCH 1/9] AIP-193: Move JSON representation details to the bottom, and clarify This is more of an informative section than anything else - API designers only need to know about it in order to understand the JSON response they might see from curl etc. --- aip/general/0193.md | 127 +++++++++++++++++++++++++++----------------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index ffcfec0d1..54c420711 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -36,54 +36,6 @@ important to [set the right tone][writing-tone] when writing messages. ### Error Response -#### JSON representation - -A JSON representation of an error response might look like the following -example. - -For the purposes of following best practices, it’s helpful to break the -error response into sections. Note that the order in which you write the -error message is often different from how the error is presented to -users. - - -```json -{ - "error": { - "code": 429, - "message": "The zone 'us-east1-a' does not have enough resources available to fulfill the request. Try a different zone, or try again later.", - "status": "RESOURCE_EXHAUSTED", - "details": [ - { - "@type": "type.googleapis.com/google.rpc.ErrorInfo", - "reason": "RESOURCE_AVAILABILITY", - "domain": "compute.googleapis.com", - "metadata": { - "zone": "us-east1-a", - "vmType": "e2-medium", - "attachment": "local-ssd=3,nvidia-t4=2", - "zonesWithCapacity": "us-central1-f,us-central1-c" - } - }, - { - "@type": "type.googleapis.com/google.rpc.LocalizedMessage", - "locale": "en-US", - "message": "An VM instance with is currently unavailable in the zone. Consider trying your request in the zone(s), which currently has/have capacity to accommodate your request. Alternatively, you can try your request again with a different VM hardware configuration or at a later time. For more information, see the troubleshooting documentation." - }, - { - "@type": "type.googleapis.com/google.rpc.Help", - "links": [ - { - "description": "Additional information on this error", - "url": "https://cloud.google.com/compute/docs/resource-error" - } - ] - } - ] - } -} -``` - #### google.rpc.Status Services must return a [`google.rpc.Status`][Status] message when an API @@ -458,6 +410,85 @@ If the user does have proper permission, but the requested resource or parent does not exist, the service **must** error with `NOT_FOUND` (HTTP 404). +## HTTP/1.1+JSON representation + +When clients use HTTP/1.1 as per [AIP-127](./0127.md), the error information +is returned in the body of the response, as a JSON object. For backward +compatibility reasons, this does not map precisely to `google.rpc.Status`, +but contains the same core information. The schema is defined in the following proto: + +```proto +message Error { + message Status { + // The HTTP status code that corresponds to `google.rpc.Status.code`. + int32 code = 1; + // This corresponds to `google.rpc.Status.message`. + string message = 2; + // This is the enum version for `google.rpc.Status.code`. + google.rpc.Code status = 4; + // This corresponds to `google.rpc.Status.details`. + repeated google.protobuf.Any details = 5; + } + + Status error = 1; +} +``` + +The most important difference is that the `code` field in the JSON is an HTTP status code, +*not* the direct value of `google.rpc.Status.code`. For example, a `google.rpc.Status` +message with a `code` value of 5 would be mapped to an object including the following +code-related fields (as well as the message, details etc): + +```json +{ + "error": { + "code": 404, // The HTTP status code for "not found" + "status": "NOT_FOUND" // The name in google.rpc.Code for value 5 + } +} +``` + +The following JSON shows a fully populated HTTP/1.1+JSON representation of an error response. + + +```json +{ + "error": { + "code": 429, + "message": "The zone 'us-east1-a' does not have enough resources available to fulfill the request. Try a different zone, or try again later.", + "status": "RESOURCE_EXHAUSTED", + "details": [ + { + "@type": "type.googleapis.com/google.rpc.ErrorInfo", + "reason": "RESOURCE_AVAILABILITY", + "domain": "compute.googleapis.com", + "metadata": { + "zone": "us-east1-a", + "vmType": "e2-medium", + "attachment": "local-ssd=3,nvidia-t4=2", + "zonesWithCapacity": "us-central1-f,us-central1-c" + } + }, + { + "@type": "type.googleapis.com/google.rpc.LocalizedMessage", + "locale": "en-US", + "message": "An VM instance with is currently unavailable in the zone. Consider trying your request in the zone(s), which currently has/have capacity to accommodate your request. Alternatively, you can try your request again with a different VM hardware configuration or at a later time. For more information, see the troubleshooting documentation." + }, + { + "@type": "type.googleapis.com/google.rpc.Help", + "links": [ + { + "description": "Additional information on this error", + "url": "https://cloud.google.com/compute/docs/resource-error" + } + ] + } + ] + } +} +``` + + ## Rationale ### Requiring ErrorInfo From bdb00a0f4481545e49386b6748ecbc9ef2565c43 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 26 Sep 2024 15:04:48 +0100 Subject: [PATCH 2/9] AIP-193: First part of restructuring the description of Status fields (This doesn't include anything about the details "payloads" yet.) --- aip/general/0193.md | 135 +++++++++++++++++--------------------------- 1 file changed, 52 insertions(+), 83 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index 54c420711..63d3a7e97 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -34,98 +34,67 @@ is necessary, you **should** provide a link where a reader can get more information or ask questions to help resolve the issue. It is also important to [set the right tone][writing-tone] when writing messages. -### Error Response - -#### google.rpc.Status - -Services must return a [`google.rpc.Status`][Status] message when an API -error occurs. For each error the service **must** populate the `message` -string in [`google.rpc.Status`][Status] as follows: - -- Present a developer-facing, human-readable "debug message" -- Present the message in the service's native language to explain both - the error and offer an actionable resolution to it ([citation][Error - model]) - -**Note:** Sometimes a service will elect to always present -`Status.message` in English rather than the application's native -language so that messages are easily searchable in common knowledge -bases, such as StackOverflow™. - -Important points: - -- This `message` string is considered a problem description. It is - intended for developers to understand the problem and is more - detailed than [`ErrorInfo.reason`][ErrorInfo-reason], discussed in the - next section. -- Use simple descriptive language that is easy to understand (without - technical jargon) to clearly state the problem that results in an - error. -- For pre-existing (brownfield) APIs, the information in - `google.rpc.Status` already exists and can’t be changed. In this - case, do not edit anything in this `google.rpc.Status`, because some - users might have automation running against the pre-existing content. - For example, any changes in text for the `message` string could cause - breakages. For more information, see [Changing Error - Messages](#changing-error-messages). - -##### [google.rpc.Status payload][Status] +The following sections describe the fields of `google.rpc.Status`. -Details of this object are summarized in the following fields, field -descriptions, and examples: +### Status.message -- `code int32` - - The status code, which **must** be an enum value of - [`google.rpc.Code`][Code]. - - *Example*: 8 -- `message string` - - A developer-facing error message, which **should** be in English. - Any user-facing error message **should** be located in the - LocalizedMessage payload. - - *Example message*: \"The zone \'us-east1-a\' does not have enough - resources available to fulfill the request. Try a different zone, or - try again later.\" -- `details Object (repeated)` - - Additional text details about the error, which includes - `ErrorInfo`,`LocalizedMessage`, and so on. - - *Example*: Described in the [ErrorDetails](#error-details) section. - - -**Important:** In the context of the `google.rpc.Status` protobuf -message, the value of the field `code` is the numeric equivalent to the -enum value chosen from [`google.rpc.Code`][Code]. For example, if -[`INVALID_ARGUMENT`][InvalidArgument] is chosen, the value of `code` -will be `3` (in the context of the Protobuf message, -`google.rpc.Status`). However, when the error is expressed as JSON, as -in the [sample above](#json-representation), the value of the field by -the same name, `"code"`, is the HTTP status code equivalent of the -selected `google.rpc.Code`. For example, the value of the field `"code"` -in the JSON reply would be `400`. - -#### [ErrorDetails][details] +The `message` field is a developer-facing, human-readable "debug message" +which **should** be in English. (Localized messages are expressed using +a `LocalizedMessage` within the `details` field. See +[`LocalizedMessage`](#localizedmessage) for more details.) Any dynamic aspects of +the message **must** be included as metadata within the `ErrorInfo` that appears +in [`details`](#status-details). -Google defines a set of [standard detail payloads][details] for error -details, which cover most common needs for API errors. Services -**should** use these standard detail payloads when feasible. +The message is considered a problem description. It is intended for +developers to understand the problem and is more detailed than +[`ErrorInfo.reason`][ErrorInfo-reason], discussed [later](#errorinfo). -Structured details with machine-readable identifiers **must** be used so -that users can write code against specific aspects of the error. Error -message strings **may** change over time; however, if an error message -does not have a machine-readable identifier *in addition to* the `code` -string, changing the error message **must** be considered a -backwards-incompatible change. +Messages **should** use simple descriptive language that is easy to understand +(without technical jargon) to clearly state the problem that results in an +error. + +For pre-existing (brownfield) APIs which have returned errors without +additional details in the past, the value of `message` must remain the same +for any given error, as developers have previously had no option but to use +this for error handling. For more information, see +[Changing Error Messages](#changing-error-messages). + +### Status.code + +The `code` field is the status code, which **must** be the numeric value of +one of the elements of the [`google.rpc.Code`][Code] enum. -The following section describes `ErrorInfo`, `LocalizedMessage`, and -`Help` in more detail. +For example, the value `5` is the numeric value of the `NOT_FOUND` +enum element. -##### Uniqueness +### Status.details + +The `details` field allows messages with additional error information to +be included in the error response, each packed in a `google.protobuf.Any` +message. + +Google defines a set of [standard detail payloads][details] for error +details, which cover most common needs for API errors. +Services **should** use these standard detail payloads when feasible. Each type of detail payload **must** be included at most once. For example, there **must not** be more than one [`BadRequest`][BadRequest] message in the `details`, but there **may** be a `BadRequest` and a [`PreconditionFailure`][PreconditionFailure]. -##### ErrorInfo payload +Structured details with machine-readable identifiers **must** be used so +that users can write code against specific aspects of the error. Error +message strings **may** change over time; however, if an error message +does not have a machine-readable identifier *in addition to* the message, +changing the error message **must** be considered a backwards-incompatible +change. For more information, see +[Changing Error Messages](#changing-error-messages). + +All error responses **must** include an `ErrorInfo` message within `details`. + +The following sections describe the most common standard detail payloads. + +#### ErrorInfo The [`ErrorInfo`][ErrorInfo] message is the required way to send a machine-readable identifier. All error responses **must** include an @@ -243,7 +212,7 @@ map for the error payload to be backwards compatible, even if the value for a particular key is empty. Keys **must** be expressed as lower camel-case. -##### Localization +#### Localization If a localized error message is required, the service **must** include [`google.rpc.LocalizedMessage`][LocalizedMessage] in `Status.details`. @@ -251,7 +220,7 @@ The value of the `Status.message` string **should** be presented in the service's native language in the `LocalizedMessage.message` string, while also ensuring that the `locale` string shows the correct language. -##### LocalizedMessage payload +#### LocalizedMessage payload The LocalizedMessage payload **should** contain the complete resolution to the error. If more information is needed than can fit in this @@ -293,7 +262,7 @@ descriptions, and examples: common resolution in the message and use the `Help` payload to link to relevant documentation. -##### Help payload +#### Help payload When `LocalizedMessage.message` doesn’t provide the user sufficient context or actionable next steps, or if there are multiple points of From b6e62f9cacbf4b5ec7a59107bf9d7e2e33cac2b9 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Thu, 26 Sep 2024 15:43:14 +0100 Subject: [PATCH 3/9] Rewrite ErrorInfo description Note: this has removed the term "dynamic variables" in favor of only referring to it as "metadata". It's handy to have a single well-defined term, and given that the field name is `metatadata`, I've kept that. --- aip/general/0193.md | 162 +++++++++++++++++--------------------------- 1 file changed, 61 insertions(+), 101 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index 63d3a7e97..ea9d1bed8 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -51,12 +51,12 @@ developers to understand the problem and is more detailed than Messages **should** use simple descriptive language that is easy to understand (without technical jargon) to clearly state the problem that results in an -error. +error, and offer an actionable resolution to it. -For pre-existing (brownfield) APIs which have returned errors without -additional details in the past, the value of `message` must remain the same -for any given error, as developers have previously had no option but to use -this for error handling. For more information, see +For pre-existing (brownfield) APIs which have previously returned errors +without machine-readable identifiers, the value of `message` **must** +remain the same for any given error, as developers have previously had no +option but to use this for error handling. For more information, see [Changing Error Messages](#changing-error-messages). ### Status.code @@ -96,121 +96,81 @@ The following sections describe the most common standard detail payloads. #### ErrorInfo -The [`ErrorInfo`][ErrorInfo] message is the required way to send a -machine-readable identifier. All error responses **must** include an -`ErrorInfo` payload in `details`. Variable information **should** be +The [`ErrorInfo`][ErrorInfo] message is the primary way to send a +machine-readable identifier. Contextual information **should** be included in `metadata` in `ErrorInfo` and **must** be included if it appears within an error message. -When introducing an error that represents a failure scenario that did -not previously occur for the service, the payload **must** include -`ErrorInfo` and any variables found in dynamic segments of the error -message **must** be present in `ErrorInfo.metadata` (See [Dynamic -variables](#dynamic-variables).) +The `reason` field is a short snake_case description of the cause of the +error. Error reasons are unique within a particular domain of errors. +The reason **must** be at most 63 characters and match a regular expression of +`[A-Z][A-Z0-9_]+[A-Z0-9]`. (This is UPPER_SNAKE_CASE, without leading +or trailing underscores, and without leading digits.) -**Note:** `ErrorInfo` represents a special case. There **must** be -exactly one `ErrorInfo`. It is required. +The reason should be terse, but meaningful enough for a human reader to +understand what the reason refers to. -[ErrorInfo Payload][ErrorInfo] +Good examples: -Details of this object are summarized in the following fields, field -descriptions, and examples: +- `CPU_AVAILABILITY` +- `NO_STOCK` +- `CHECKED_OUT` +- `AVAILABILITY_ERROR` -- `reason string` - - A short snake_case description of why the error occurred. Error - reasons are unique within a particular domain of errors. The error - reason **must** do the following: - - Be at most 63 characters and match a regular expression of - `/[A-Z][A-Z0-9_]+[A-Z0-9]/`, which represents UPPER_SNAKE_CASE. - - Be meaningful enough for a human reader to understand what the - reason refers to. - - Be unique and consumable by machine actors for automation. - - *Example*: CPU_AVAILABILITY
- Distill your error message into its simplest form. For example, the - `reason string` could be one of the following text examples in - UPPER_SNAKE_CASE: `UNAVAILABLE`, `NO_STOCK`, `CHECKED_OUT`, - `AVAILABILITY_ERROR`, if your error message is, - - > The Book, "The Great Gatsby", is unavailable at the Library, - > "Garfield East". It is expected to be available again on 2199-05-13. - - - In contrast, using either of the following reasons is not - recommended: `THE_BOOK_YOU_WANT_IS_NOT_AVAILABLE`, `ERROR`. And, - using either of the following reasons breaches the required - formatting and is not allowed: `librariesAreGreat`, `noBooks`. - -- `domain string` - - The logical grouping to which the `reason` belongs. The error domain - is typically the registered service name of the tool or product that - generated the error. The domain must be a globally unique value. - - *Example*:
`pubsub.googleapis.com` - -- `metadata map` - - Additional structured details about this error, which **should** - provide important context for customers to identify resolution - steps. Keys **should** match `/[a-z][a-zA-Z0-9-_]+/`, and be - limited to 64 characters in length. When identifying the current - value of an exceeded limit, the units **should** be contained in the - key, not the value. - - *Example*: - ``` - "vmType": "e2-medium", - "attachment": "local-ssd=3,nvidia-t4=2", - "zone": "us-east1-a" - ``` - - For guidance on using the metadata map, see [Dynamic - Variables](#dynamic-variables). +Bad examples: -##### Dynamic variables +- `THE_BOOK_YOU_WANT_IS_NOT_AVAILABLE` (overly verbose) +- `ERROR` (too general) -The best, actionable error messages include dynamic segments. These -variable parts of the message are specific to a particular request. -Without such context, it is unlikely that the message will be fully -actionable by the user. +The `domain` field is the logical grouping to which the `reason` belongs. +The domain **must** be a globally unique value, and is typically the name of the service +that generated the error, e.g. `pubsub.googleapis.com`. -This practice is critical so that machine actors do not need to rely on -`LocalizedMessage.message`, which is subject to change and is not part -of the API contract. +The (reason, domain) pair form a machine-readable way of identifying a particular error. +Services **must** use the same (reason, domain) pair for the same error, and +**must not** use the same (reason, domain) pair for logically different errors. +The decision about whether two errors are "the same" or not is not always clear, but +**should** generally be considered in terms of the expected action a client might take +to resolve them. -Consider the following example: +The `metadata` field is a map of key/value pairs providing additional +dynamic information as context. Each key within `metadata` **must** be at most +64 characters long, and conform to the regular expression `[a-z][a-zA-Z0-9-_]+`. -> The Book, "The Great Gatsby", is unavailable at the Library, "Garfield -> East". It is expected to be available again on 2199-05-13. +Any request-specific information which contributes to the `Status.message` or +`LocalizedMessage.message` messages **must** be represented within `metadata`. +This practice is critical so that machine actors do not need to parse error +messages to extract information. -The preceding error message is made actionable by the context, both -originating from the request, the title of the Book, the name of the -Library, and by the information that is known only by the service, that -is, the expected return date of the Book. +For example consider the following message: -All dynamic variables found in error messages **must** also be present -in the `map`, `ErrorInfo.metadata` (found on the -*required* `ErrorInfo`). For example, the `metadata` map for the sample -error message above will include *at least* the following key/value -pairs: +> An <e2-medium> VM instance with <local-ssd=3,nvidia-t4=2> is currently unavailable +> in the <us-east1-a> zone. Consider trying your request in the <us-central1-f,us-central1-c> +> zone(s), which currently has/have capacity to accommodate your request. Alternatively, +> you can try your request again with a different VM hardware configuration +> or at a later time. For more information, see the troubleshooting documentation. -``` -bookTitle: "The Great Gatsby" -library: "Garfield East" -expectedReturnDate: "2199-05-13" -``` +The `ErrorInfo.metadata` map for the same error could be: -The following example shows an additional example of a metadata map for -information about virtual machines: +- `"zone": "us-east1-a"` +- `"vmType": "e2-medium"` +- `"attachment": "local-ssd=3,nvidia-t4=2"` +- `"zonesWithCapacity": "us-central1-f,us-central1-c"` -``` -vmType: "", -attachment: "" -zone: "" -``` +Additional contextual information that does not appear in an error message +**may** also be included in `metadata` to allow programmatic use by the client. + +The metadata included for any given (reason,domain) pair can evolve over time: + +- New keys **may** be included +- All keys that have been included **must** continue to be included (but may have empty values) -Dynamic variables that do not appear in an error message **may** also be -included in `metadata` to provide additional information to the client -to be used programmatically. +In other words, once a user has observed a given key for a (reason, domain) pair, the +service **must** allow them to rely on it continuing to be present in the future. -Once present in `metadata`, keys **must** continue to be included in the -map for the error payload to be backwards compatible, even if the value -for a particular key is empty. Keys **must** be expressed as lower -camel-case. +The set of keys provided in each (reason, domain) pair is independent from other pairs, +but services **should** aim for consistent key naming. For example, two error reasons +within the same domain should not use metadata keys of `vmType` and `virtualMachineType`. #### Localization From cf6a28f46b7f7402fee40001593b9cdd48753048 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Fri, 27 Sep 2024 15:30:29 +0100 Subject: [PATCH 4/9] AIP-193: Update the LocalizedMessage section Note that we don't currently (as far as I'm aware) have a good standardized way of letting users specify a locale. (Can they use Accept-Language, for example? What about in gRPC?) We can tackle that separately. This commit changes the guidance in a couple of important ways: - It says that the locale should be a user-specified one rather than a "service's native language" - It calls out the ability to use LocalizedMessage even when we don't have a user-specified language, when Status.message can't be changed for compatibility reasons This commit *doesn't* address the issue of the target audience of LocalizedMessage; later text claims this is aimed at end-users whereas Status.message is more aimed at developers. I'm not convinced by that at the moment. --- aip/general/0193.md | 78 +++++++++++++++++---------------------------- 1 file changed, 30 insertions(+), 48 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index ea9d1bed8..0b43ee26e 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -172,55 +172,37 @@ The set of keys provided in each (reason, domain) pair is independent from other but services **should** aim for consistent key naming. For example, two error reasons within the same domain should not use metadata keys of `vmType` and `virtualMachineType`. -#### Localization - -If a localized error message is required, the service **must** include -[`google.rpc.LocalizedMessage`][LocalizedMessage] in `Status.details`. -The value of the `Status.message` string **should** be presented in the -service's native language in the `LocalizedMessage.message` string, -while also ensuring that the `locale` string shows the correct language. - -#### LocalizedMessage payload - -The LocalizedMessage payload **should** contain the complete resolution -to the error. If more information is needed than can fit in this +#### LocalizedMessage + +[`google.rpc.LocalizedMessage`][LocalizedMessage] is used to provide an error +message which **should** be localized to a user-specified locale where +possible. + +If the [`Status.message`](#status-message) field has a sub-optimal value +which cannot be changed due to the constraints in the +[Changing Error Messages](#changing-error-messages) section, `LocalizedMessage` +**may** be used to provide a better error message even when no user-specified +locale is available. + +Regardless of how the locale for the message was determined, both the `locale` +and `message` fields **must** be populated. + +The `locale` field specifies the locale of the message, +following [IETF bcp47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) (Tags for +Identifying Languages). Example values: `"en-US"`, `"fr-CH"`, `"es-MX"`. + +The `message` field contains the localized text itself. This +**should** include a brief description of the error and a call to action +to resolve the error. The message **should** include contextual information +to make the message as specific as possible. Any contextual information +in the message **must** be included in `ErrorInfo.metadata`. See +[`ErrorInfo`](#errorinfo) for more details of how contextual information +may be included in a message and the corresponding metadata. + +The `LocalizedMessage` payload **should** contain the complete resolution +to the error. If more information is needed than can reasonably fit in this payload, then additional resolution information **must** be provided in -the Help payload. See the [Help payload](#help-payload) -section for guidance. - -For pre-existing (brownfield) APIs, the content in this `message` string -will differ from the `message` string in `google.rpc.status`. - -[LocalizedMessage Payload][LocalizedMessage] - -Details of this object are summarized in the following fields, field -descriptions, and examples: - -- `locale string` - - The locale that follows the specification defined in [IETF - bcp47](https://www.rfc-editor.org/rfc/bcp/bcp47.txt) (Tags for - Identifying Languages). - - *Example*: **"en-US"**, **"fr-CH"**, **"es-MX"** -- `message string` - - The error message that the customer will receive through their - chosen service, which **should** include a brief description of the - error and a call to action to resolve the error. The message - **should** include, where needed, data provided in other fields such - as metadata. - - Give users clear and concise instructions for resolving the error, - which **must** be explicit as possible; for example: - - > Consider trying your request in the , - > zone(s), which currently has/have capacity to - > accommodate your request. Alternatively, you can try your request - > again with a different VM hardware configuration or at a later - > time. - - - If the error resolution exceeds the number of characters supported in - the problem description (`message` string of `google.rpc.Status`), - or requires multiple troubleshooting steps, include the most - common resolution in the message and use the `Help` payload to - link to relevant documentation. +a `Help` payload. See the [Help](#help) section for guidance. #### Help payload From 63e0f942e8a56b5c752580c54305cd22306b1fa3 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Fri, 27 Sep 2024 15:47:27 +0100 Subject: [PATCH 5/9] AIP-193: Update the Help section Significant changes here (all up for discussion): - Explicitly include Status.message as well as LocalizedMessage.message - Remove the standardized https://cloud.google.com/PRODUCT/docs/ERROR-REASON format requirement - Require the description to be suitable as text for a link - Require the description to be plain text - Require the URL to be absolute and include a scheme - Add authentication requirements --- aip/general/0193.md | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index 0b43ee26e..bd4f7ec31 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -204,33 +204,39 @@ to the error. If more information is needed than can reasonably fit in this payload, then additional resolution information **must** be provided in a `Help` payload. See the [Help](#help) section for guidance. -#### Help payload +#### Help -When `LocalizedMessage.message` doesn’t provide the user sufficient +When other textual error messages (in `Status.message` or +`LocalizedMessage.message`) don't provide the user sufficient context or actionable next steps, or if there are multiple points of failure that need to be considered in troubleshooting, a link to supplemental troubleshooting documentation **must** be provided in the `Help` payload. Provide this information in addition to a clear problem definition and -actionable resolution, not as an alternative to them; for example. +actionable resolution, not as an alternative to them. The linked +documentation **must** clearly relate to the error. If a single page +contains information about multiple errors, the +[`ErrorInfo.reason`](#errorinfo) value **must** be used to narrow down +the relevant information. -**Note:** For more information, see the troubleshooting documentation: -[https://cloud.google.com/compute/docs/resource-error][resource-error]. +The `description` field is a textual description of the linked information. +This **must** be suitable to display to a user as text for a hyperlink. +This **must** be plain text (not HTML, Markdown etc). -[Help payload][Help] +Example `description` value: `"Troubleshooting documentation for STOCKOUT errors"` -Details of this object are summarized in the following fields, field -descriptions, and examples: +The `url` field is the URL to link to. This **must** be an absolute URL, +including scheme. -- `description string` - - Describes what the link offers. - - *Example*: "Troubleshooting documentation for STOCKOUT errors" -- `url string` - - The URL of the link. For error documentation this must follow the - standardized format listed in the following example. - - *Example*: - https://cloud.google.com/PRODUCT/docs/ERROR-REASON +Example `url` value: +`"https://cloud.google.com/compute/docs/resource-error"` + +For publicly-documented services, even those with access controls on actual +usage, the linked content **must** be accessible without authentication. + +For privately-documented services, the linked content **may** require +authentication. ### Error messages From dfcae90ca45c95685fd6dc010d3978703b1fb105 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 8 Oct 2024 15:22:12 +0100 Subject: [PATCH 6/9] AIP-193: Update error messages section (including rationale) Note: this removes the "message alignment" section which I found confusing. We could restore and edit it should we wish. --- aip/general/0193.md | 100 ++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index bd4f7ec31..d222e2f94 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -240,64 +240,36 @@ authentication. ### Error messages -The following sections describe best practices for error messages not -described previously. +Textual error messages can be present in both `Status.message` and +`LocalizedMessage.message` fields. Messages should be succinct but +actionable, with request-specific information (such as a resource name +or region) providing precise details where appropriate. Any request-specific +details **must** be present in [`ErrorInfo.metadata`](#errorinfo). #### Changing error messages -##### Method +Changing the content of `Status.message` over time must be done carefully, +to avoid breaking clients who have previously had to rely on the message +for all information. See [the rationale section](#updating-statusmessage) +for more details. -The method of changing error messages depends on whether an ErrorInfo -payload is present in the message. +For a given RPC: -If the error message contains ErrorInfo payload (a machine-readable -identifier): +- If the RPC has *always* returned `ErrorInfo` with machine-readable + information, the content of `Status.message` **may** change over time. + (For example, the API producer may provide a clearer explanation, + or more request-specific information.) +- Otherwise, the content of `Status.message` **must** be stable, + providing the same text with the same request-specific information. + Instead of changing `Status.message`, the API **should** include a + [`LocalizedMessage`](#localizedmessage) within `Status.details`. -- `Status.message` *string* and `LocalizedMessage.message` *string* can - change -- New metadata fields can be added -- However, existing metadata fields **must not** be removed and existing - metadata field keys cannot be modified. -If the error message does *not* contain ErrorInfo payload (usually for -pre-existing APIs): +Even if an RPC has always returned `ErrorInfo`, the API **may** keep +the existing `Status.message` stable and add a +[`LocalizedMessage`](#localizedmessage) within `Status.details`. -- The `LocalizedMessage.message` *string* can change -- However, the `Status.message` *string* **must** not be changed, as - this change is backward-incompatible. - -##### Message alignment - -`LocalizedMessage` is populated with the same message as -`Status.message`. Should you choose, you can also present a different -`LocalizedMessage.message` from `Status.message`. For reasons why or why not, - see [the rationale](#LocalizedMessage). - -Keeping the aforementioned guidance in mind, the safest and recommended -method to update an error message for a service is to add -[`google.rpc.LocalizedMessage`][LocalizedMessage] to -[`Status.details`][Status details]. `LocalizedMessage` is meant for -displaying messages to end users. - -Add as much information as the consumer of the error needs to resolve -the error, but succinctly. - -When including `LocalizedMessage`, both `locale` and `message` **must** -be populated. If the service is to be localized, the value of `locale` -**must** change dynamically. See "[Localization](#localization)". -Otherwise, `locale` **must** always present the service's default -locale; for example, "en-US". - -When adding an error message using `LocalizedMessage`, `ErrorInfo` -**must** be introduced either before or at the same time. If there are -dynamic segments found in the text, the values of these variables -**must** be included in `ErrorInfo.metadata`. See, "[Dynamic -variables](#dynamic-variables)". - -**Warning:** If `LocalizedMessage` is released without `ErrorInfo`, the -same concerns regarding changing the value of the `message` field of -`LocalizedMissage` apply: clients may misuse this textual error -message--matching on string content. +The content of `LocalizedMessage.details` **may** change over time. ### Partial errors @@ -445,16 +417,24 @@ differs from `Status.message` include the following: ### Updating Status.message -**Because Status.message is part of the API contract**, avoid updating -it entirely in favor of updating LocalizedMessage; for example, clients -often perform string matches on the text to differentiate one error for -another and even parse the error message to extract variables from -dynamic segments. - -Clients **must** only perform string matches if their only option for -extracting dynamic information is the message itself. If structured data -is present in metadata, the recommended practice is to key into that -data instead. +If a client has ever observed an error with `Status.message` populated +(which it always will be) but without `ErrorInfo`, the developer of that client +may well have had to resort to parsing `Status.message` in order to find out +information beyond just what `Status.code` conveys. That information may be +found by matching specific text (e.g. "Connection closed with unknown cause") +or by parsing the message to find out metadata values (e.g. a region with +insufficient resources). At that point, `Status.message` is implicitly part +of the API contract, so **must not** be updated - that would be a breaking +change. This is one reason for introducing `LocalizedMessage` into the +`Status.details`. + +RPCs which have **always** included `ErrorInfo` are in a better position: +the contract is then more about the stability of `ErrorInfo` for any given +error: the reason and domain need to be consistent over time, and the +metadata provided for any given (reason,domain) can only be expanded. +It's still possible that clients could be parsing `Status.message` instead of +using `ErrorInfo`, but they will always have had a more robust option +available to them. ## Further reading From a5eff3dffc32858f1d24a57378e92eae19a4f1b1 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Tue, 8 Oct 2024 15:30:01 +0100 Subject: [PATCH 7/9] AIP-193: Update the rationale around LocalizedMessage Note: this removes the idea that LocalizedMessage is for "end users" as a distinction between that and Status.message being for developers. If we still want that distinction, we should talk about it more and clarify it - in particular, if we're using LocalizedMessage as an alternative for Status.message where the latter can't be changed, they can't really cater for different audiences. --- aip/general/0193.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index d222e2f94..4eb4d2d67 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -392,28 +392,28 @@ variable information, so there needs to be machine-readable component of *every* error response that enables clients to use such information programmatically. -### LocalizedMessage +### Including LocalizedMessage `LocalizedMessage` was selected as the location to present alternate -error messages. This is desirable when clients need to display a crafted -error message directly to end users. `LocalizedMessage` can be used with -a static `locale`. This may seem misleading, but it allows the service -to eventually localize without having to duplicate or move the error -message, which would be a backwards incompatible change. - -Reasons to present the same error message in both locations include the following: - -- The service plans to localize either immediately or in the near future. See, - "[Localization](#localization)". -- This practice enables clients to find an error message consistently in one - location, `LocalizedMessaage.message`, across all methods of the API Service. - -Reasons to present an error message in `LocalizedMessage.message` that -differs from `Status.message` include the following: - -- The service requires an end-user-facing error message that differs - from the "debug message". -- Ongoing, iterative error message improvements are expected. +error messages. While `LocalizedMessage` **may** use a locale specified +in the request, a service **may** provide a `LocalizedMessage` even without +a user-specified locale, typically to provide a better error message in +[situations where `Status.message` cannot be changed](updating-statusmessage). +Where the locale is not specified by the user, it **should** be `en-US` +(US English). + +A service **may** include `LocalizedMessage` even when the same message is +provided in `Status.message` and when localization into a user-specified locale +is not supported. Reasons for this include: + +- An intention to support user-specified localization in the near future, allowing + clients to consistently use `LocalizedMessage` and not change their error-reporting + code when the functionality is introduced. +- Consistency across all RPCs within a service: if some RPCs include + `LocalizedMessage` and some only use `Status.message` for error messages, clients + have to be aware of which RPCs will do what, or implement a fall-back mechanism. + Providing `LocalizedMessage` on all RPCs allows simple and consistent client code + to be written. ### Updating Status.message From fe6c3182a7c6446ebf17c2f9404f6fe65585c959 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Fri, 18 Oct 2024 15:59:41 +0100 Subject: [PATCH 8/9] AIP-193 review feedback and add a dated changelog entry --- aip/general/0193.md | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index 4eb4d2d67..bd2f68ef0 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -55,8 +55,7 @@ error, and offer an actionable resolution to it. For pre-existing (brownfield) APIs which have previously returned errors without machine-readable identifiers, the value of `message` **must** -remain the same for any given error, as developers have previously had no -option but to use this for error handling. For more information, see +remain the same for any given error. For more information, see [Changing Error Messages](#changing-error-messages). ### Status.code @@ -82,15 +81,9 @@ example, there **must not** be more than one [`BadRequest`][BadRequest] message in the `details`, but there **may** be a `BadRequest` and a [`PreconditionFailure`][PreconditionFailure]. -Structured details with machine-readable identifiers **must** be used so -that users can write code against specific aspects of the error. Error -message strings **may** change over time; however, if an error message -does not have a machine-readable identifier *in addition to* the message, -changing the error message **must** be considered a backwards-incompatible -change. For more information, see -[Changing Error Messages](#changing-error-messages). - -All error responses **must** include an `ErrorInfo` message within `details`. +All error responses **must** include an `ErrorInfo` within `details`. This +provides machine-readable identifiers so that users can write code against +specific aspects of the error. The following sections describe the most common standard detail payloads. @@ -430,7 +423,7 @@ change. This is one reason for introducing `LocalizedMessage` into the RPCs which have **always** included `ErrorInfo` are in a better position: the contract is then more about the stability of `ErrorInfo` for any given -error: the reason and domain need to be consistent over time, and the +error. The reason and domain need to be consistent over time, and the metadata provided for any given (reason,domain) can only be expanded. It's still possible that clients could be parsing `Status.message` instead of using `ErrorInfo`, but they will always have had a more robust option @@ -444,6 +437,7 @@ available to them. ## Changelog +- **2024-10-18**: Rewrite/restructure for clarity. - **2024-01-10**: Incorporate guidance for writing effective messages. - **2023-05-17**: Change the recommended language for `Status.message` to be the service's native language rather than English. From 068f4672b8e779c79139d61840d7f6521dc88353 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 21 Oct 2024 09:04:52 +0100 Subject: [PATCH 9/9] AIP-193: Address review comments --- aip/general/0193.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aip/general/0193.md b/aip/general/0193.md index bd2f68ef0..c954a0631 100644 --- a/aip/general/0193.md +++ b/aip/general/0193.md @@ -100,7 +100,7 @@ The reason **must** be at most 63 characters and match a regular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`. (This is UPPER_SNAKE_CASE, without leading or trailing underscores, and without leading digits.) -The reason should be terse, but meaningful enough for a human reader to +The reason **should** be terse, but meaningful enough for a human reader to understand what the reason refers to. Good examples: @@ -234,7 +234,7 @@ authentication. ### Error messages Textual error messages can be present in both `Status.message` and -`LocalizedMessage.message` fields. Messages should be succinct but +`LocalizedMessage.message` fields. Messages **should** be succinct but actionable, with request-specific information (such as a resource name or region) providing precise details where appropriate. Any request-specific details **must** be present in [`ErrorInfo.metadata`](#errorinfo).