GrpcWebClientReadableStream: keep falsy data (#1230)

This commit is contained in:
pro-wh 2022-10-25 00:49:54 -07:00 committed by GitHub
parent b0ea9c7c45
commit e11903b337
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 7 deletions

View File

@ -123,10 +123,10 @@ class GrpcWebClientBase {
let unaryStatus;
let unaryMsg;
GrpcWebClientBase.setCallback_(
stream, (error, response, status, metadata) => {
stream, (error, response, status, metadata, unaryResponseReceived) => {
if (error) {
reject(error);
} else if (response) {
} else if (unaryResponseReceived) {
unaryMsg = response;
} else if (status) {
unaryStatus = status;
@ -222,9 +222,16 @@ class GrpcWebClientBase {
* @static
* @template RESPONSE
* @param {!ClientReadableStream<RESPONSE>} stream
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=)|
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=, ?boolean)|
* function(?RpcError,?RESPONSE)} callback
* @param {boolean} useUnaryResponse
* @param {boolean} useUnaryResponse Pass true to have the client make
* multiple calls to the callback, using (error, response, status,
* metadata, unaryResponseReceived) arguments. One of error, status,
* metadata, or unaryResponseReceived will be truthy to indicate which piece
* of information the client is providing in that call. After the stream
* ends, it will call the callback an additional time with all falsy
* arguments. Pass false to have the client make one call to the callback
* using (error, response) arguments.
*/
static setCallback_(stream, callback, useUnaryResponse) {
let isResponseReceived = false;
@ -272,11 +279,11 @@ class GrpcWebClientBase {
message: 'Incomplete response',
});
} else {
callback(null, responseReceived);
callback(null, responseReceived, null, null, /* unaryResponseReceived= */ true);
}
}
if (useUnaryResponse) {
callback(null, null); // trigger unaryResponse
callback(null, null);
}
});
}

View File

@ -75,6 +75,77 @@ testSuite({
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testRpcFalsyResponse_ForNonProtobufDescriptor() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const response = await new Promise((resolve, reject) => {
client.rpcCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
assertNull(error);
resolve(response);
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
});
assertEquals(0, response);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testRpcResponseThenableCall() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return new MockReply('value');
});
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
const response = await responsePromise;
assertEquals('value', response.data);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testRpcFalsyResponseThenableCall_ForNonProtobufDescriptor() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
const response = await responsePromise;
assertEquals(0, response);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testDeadline() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);

View File

@ -170,16 +170,18 @@ class GrpcWebClientReadableStream {
if (FrameType.DATA in messages[i]) {
const data = messages[i][FrameType.DATA];
if (data) {
let isResponseDeserialized = false;
let response;
try {
response = self.responseDeserializeFn_(data);
isResponseDeserialized = true;
} catch (err) {
self.handleError_(new RpcError(
StatusCode.INTERNAL,
`Error when deserializing response data; error: ${err}` +
`, response: ${response}`));
}
if (response) {
if (isResponseDeserialized) {
self.sendDataCallbacks_(response);
}
}