Repository navigation
feat(gax): add resolveResponsePayload helper for response.payload #9745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| */ | ||
|
|
||
| import {Attributes, Span} from '@opentelemetry/api'; | ||
| import * as protobuf from 'protobufjs'; | ||
| import {GoogleError} from '../googleError'; | ||
| import {Status} from '../status'; | ||
| /** | ||
|
|
@@ -798,9 +799,46 @@ export function isServerSideError( | |
| return resolveServerErrorCode(e, rpcType) !== undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the bytes of binary data: an ArrayBuffer, or a view of one such as a Buffer | ||
| * or Uint8Array. | ||
| * | ||
| * @param {unknown} value - The value to check. | ||
| * @returns {Uint8Array | undefined} The bytes of `value`, or undefined if it is not binary data. | ||
| */ | ||
| function toBytes(value: unknown): Uint8Array | undefined { | ||
| if (ArrayBuffer.isView(value)) { | ||
| return new Uint8Array(value.buffer, value.byteOffset, value.byteLength); | ||
| } | ||
| if (value instanceof ArrayBuffer) { | ||
| return new Uint8Array(value); | ||
| } | ||
| return undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Encodes bytes as base64, using Buffer when it is available and a pure JavaScript | ||
| * encoder otherwise (e.g. in browsers). | ||
| * | ||
| * @param {Uint8Array} bytes - The bytes to encode. | ||
| * @returns {string} The base64 encoding of `bytes`. | ||
| */ | ||
| function encodeBase64(bytes: Uint8Array): string { | ||
| if (typeof Buffer !== 'undefined') { | ||
| return Buffer.from( | ||
| bytes.buffer, | ||
| bytes.byteOffset, | ||
| bytes.byteLength, | ||
| ).toString('base64'); | ||
| } | ||
| return protobuf.util.base64.encode(bytes, 0, bytes.length); | ||
| } | ||
|
|
||
| /** | ||
| * Safely converts a value to a JSON string without throwing exceptions on | ||
| * circular references, BigInt values, or non-serializable properties. | ||
| * circular references, BigInt values, or non-serializable properties. Binary data | ||
| * (e.g. a Buffer or Uint8Array) is serialized as a base64 string rather than one | ||
| * JSON entry per byte. | ||
| * | ||
| * @param {unknown} value - The value to serialize. | ||
| * @returns {string | undefined} The serialized JSON string, or undefined if `value` is undefined or unserializable. | ||
|
|
@@ -813,13 +851,24 @@ export function safeJsonStringify(value: unknown): string | undefined { | |
| const ancestors: unknown[] = []; | ||
| return JSON.stringify( | ||
| value, | ||
| function (this: unknown, _key: string, val: unknown) { | ||
| function (this: unknown, key: string, val: unknown) { | ||
| if (typeof val === 'bigint') { | ||
| return val.toString(); | ||
| } | ||
| if (typeof val !== 'object' || val === null) { | ||
| return val; | ||
| } | ||
| // Encode binary data as base64. Buffer#toJSON runs before this replacer and | ||
| // turns a Buffer into {type: 'Buffer', data: [...]}, so in that case the | ||
| // original Buffer is read back from the holder. | ||
| const bytes = toBytes( | ||
| (val as {type?: unknown}).type === 'Buffer' | ||
| ? (this as Record<string, unknown>)[key] | ||
| : val, | ||
| ); | ||
| if (bytes) { | ||
| return encodeBase64(bytes); | ||
| } | ||
| // Track object ancestry to replace circular references. | ||
| if (ancestors.includes(this)) { | ||
| while ( | ||
|
|
@@ -845,6 +894,38 @@ export function safeJsonStringify(value: unknown): string | undefined { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Resolves the `response.payload` attribute: the full response payload of a call, | ||
| * serialized as JSON without truncation or redaction. String payloads are returned | ||
| * as-is and binary payloads (e.g. a Buffer or Uint8Array) as base64, as are binary | ||
| * values nested in the response (e.g. `bytes` fields). Prefers `rawResponse`, the | ||
| * unprocessed response gax provides for paginated and long-running calls (the full | ||
| * page response or the raw Operation), over the processed `response`. | ||
| * | ||
| * @param {unknown} response - The response passed to the callback or resolved by the promise. | ||
| * @param {unknown} [rawResponse] - The raw (unprocessed) response, when gax provides one. | ||
| * @returns {string | undefined} The serialized payload, or undefined if there is no response. | ||
| */ | ||
| export function resolveResponsePayload( | ||
| response: unknown, | ||
| rawResponse?: unknown, | ||
| ): string | undefined { | ||
| const payload = rawResponse ?? response; | ||
| if (payload === undefined || payload === null) { | ||
| return undefined; | ||
| } | ||
| // TODO(security): The payload is returned in full because `response.payload` is defined | ||
| // as the raw response, so it can contain sensitive data (e.g. secret values or access | ||
| // tokens). Only emit it when logging is explicitly enabled (see `checkLoggingEnabled`), | ||
| // and consider redacting known sensitive fields. | ||
| if (typeof payload === 'string') { | ||
| return payload; | ||
| } | ||
| // Binary payloads are base64 encoded and, like strings, not wrapped in JSON quotes. | ||
| const bytes = toBytes(payload); | ||
| return bytes ? encodeBase64(bytes) : safeJsonStringify(payload); | ||
| } | ||
|
Comment on lines
+909
to
+927
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Consider removing the unused |
||
|
|
||
| /** | ||
| * Extracts the server-side error details message attached by GFE/backend on | ||
| * server-side errors, checking the error and its `.cause` chain and falling | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Now that
safeJsonStringify(lines 840-850) automatically base64-encodesBuffer,Uint8Array, andArrayBuffervalues in its replacer,isBuffer(line 778) and the 20-linecleanMapBuffer-encoding loop inresolveServerExceptionDetailsin the same file are redundant;resolveServerExceptionDetailscan passmapdirectly tosafeJsonStringify(map).