From a804c378d71d38b4d446e27e2560d5eaa95a5714 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 18 Apr 2023 13:21:20 -0400 Subject: [PATCH 01/35] get branch in sync with upstream main --- test/showcase-echo-client/package.json | 35 ++++++++-- test/test-application/src/index.ts | 94 ++++++++++++++++++++++++-- 2 files changed, 115 insertions(+), 14 deletions(-) diff --git a/test/showcase-echo-client/package.json b/test/showcase-echo-client/package.json index 5a1f8a808..aab2afc7e 100644 --- a/test/showcase-echo-client/package.json +++ b/test/showcase-echo-client/package.json @@ -10,7 +10,6 @@ "build/src", "build/protos" ], - "types": "build/src/index.d.ts", "keywords": [ "google apis client", "google api client", @@ -22,24 +21,46 @@ "cloud", "google showcase", "showcase", + "compliance", "echo", "identity", - "messaging", - "testing" + "sequence service" ], "scripts": { "compile": "tsc -p . && cp -r protos build/", "compile-protos": "compileProtos src", + "docs": "jsdoc -c .jsdoc.js", + "predocs-test": "npm run docs", + "docs-test": "linkinator docs", + "fix": "gts fix", + "lint": "gts check", + "prepare": "npm run compile-protos && npm run compile", "prefetch": "rm -rf node_modules package-lock.json google-gax*.tgz && cd ../.. && npm pack && mv google-gax*.tgz test/showcase-echo-client/google-gax.tgz", - "prepare": "npm run compile-protos && npm run compile" + + "system-test": "c8 mocha build/system-test", + "test": "c8 mocha build/test" }, "dependencies": { "google-gax": "./google-gax.tgz" }, "devDependencies": { - "@types/node": "^16.0.0", - "gapic-tools": "^0.1.7", - "typescript": "^4.5.5" + "@types/mocha": "^10.0.1", + "@types/node": "^18.11.18", + "@types/sinon": "^10.0.13", + "c8": "^7.13.0", + "gts": "^3.1.1", + "jsdoc": "^4.0.2", + "jsdoc-fresh": "^2.0.1", + "jsdoc-region-tag": "^2.0.1", + "linkinator": "^4.1.2", + "mocha": "^10.2.0", + "null-loader": "^4.0.1", + "pack-n-play": "^1.0.0-2", + "sinon": "^15.0.1", + "ts-loader": "^8.4.0", + "typescript": "^4.8.4", + "webpack": "^4.46.0", + "webpack-cli": "^4.10.0" }, "engines": { "node": ">=v14" diff --git a/test/test-application/src/index.ts b/test/test-application/src/index.ts index cd6a9cb8c..3f77d5ea3 100644 --- a/test/test-application/src/index.ts +++ b/test/test-application/src/index.ts @@ -78,13 +78,76 @@ async function testShowcase() { await testChatThrows(fallbackClient); // fallback does not support bidi streaming await testWait(fallbackClient); - await testEcho(restClient); - await testExpand(restClient); // REGAPIC supports server streaming - await testPagedExpand(restClient); - await testPagedExpandAsync(restClient); - await testCollectThrows(restClient); // REGAPIC does not support client streaming - await testChatThrows(restClient); // REGAPIC does not support bidi streaming - await testWait(restClient); + // await testEcho(fallbackClient); + // await testEchoError(fallbackClient); + // await testExpandThrows(fallbackClient); // fallback does not support server streaming + // await testPagedExpand(fallbackClient); + // await testPagedExpandAsync(fallbackClient); + // await testCollectThrows(fallbackClient); // fallback does not support client streaming + // await testChatThrows(fallbackClient); // fallback does not support bidi streaming + // await testWait(fallbackClient); + + // await testEcho(restClient); + // await testExpand(restClient); // REGAPIC supports server streaming + // await testPagedExpand(restClient); + // await testPagedExpandAsync(restClient); + // await testCollectThrows(restClient); // REGAPIC does not support client streaming + // await testChatThrows(restClient); // REGAPIC does not support bidi streaming + // await testWait(restClient); +} + +function getStreamingSequenceRequest(){ + const request = new protos.google.showcase.v1beta1.CreateStreamingSequenceRequest() + + let firstDelay = new protos.google.protobuf.Duration(); + firstDelay.nanos=150; + + let firstStatus = new protos.google.rpc.Status(); + firstStatus.code=14; + firstStatus.message="UNAVAILABLE"; + + let firstResponse = new protos.google.showcase.v1beta1.StreamingSequence.Response(); + firstResponse.delay=firstDelay; + firstResponse.status=firstStatus; + + // The Index you want the stream to fail or send the status + // This should be index + 1 so if you want to send status at index 0 + // you would provide firstResponse.sendStatusAtIndex=1 + + firstResponse.sendStatusAtIndex=1; + + let secondDelay = new protos.google.protobuf.Duration(); + secondDelay.nanos=150; + + let secondStatus = new protos.google.rpc.Status(); + secondStatus.code= 4; + secondStatus.message="DEADLINE_EXCEEDED"; + + let secondResponse = new protos.google.showcase.v1beta1.StreamingSequence.Response(); + secondResponse.delay=secondDelay; + secondResponse.status=secondStatus; + secondResponse.sendStatusAtIndex=2 + + let thirdDelay = new protos.google.protobuf.Duration(); + thirdDelay.nanos=500000; + + let thirdStatus = new protos.google.rpc.Status(); + thirdStatus.code=0; + thirdStatus.message="OK"; + + let thirdResponse = new protos.google.showcase.v1beta1.StreamingSequence.Response(); + thirdResponse.delay=thirdDelay; + thirdResponse.status=thirdStatus; + thirdResponse.sendStatusAtIndex=11; + + let streamingSequence = new protos.google.showcase.v1beta1.StreamingSequence() + streamingSequence.responses = [firstResponse,secondResponse,thirdResponse]; + // streamingSequence.responses = []; + + streamingSequence.content = "This is testing the brand new and shiny StreamingSequence server 3"; + request.streamingsequence = streamingSequence + + return request } async function testEcho(client: EchoClient) { @@ -245,6 +308,7 @@ async function testCollect(client: EchoClient) { resolve(result.content ?? ''); } }); +<<<<<<< Updated upstream for (const word of words) { const request = {content: word}; stream.write(request); @@ -259,6 +323,22 @@ async function testCollect(client: EchoClient) { }); const result = await promise; assert.strictEqual(result, words.join(' ')); +======= + return attemptStream + } + let attemptStream; + //TODO(coleleah): handle this more elegantly + if (sequence.responses){ + const numResponses = sequence.responses.length + console.log(numResponses); + + attemptStream = await multipleSequenceAttempts(numResponses) + }else{ + const numResponses = 3 + attemptStream = await multipleSequenceAttempts(numResponses) + + } +>>>>>>> Stashed changes } async function testCollectThrows(client: EchoClient) { From ecfb2f7eef0990103e92e10a53cad994c6113e29 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 22 Aug 2023 16:56:05 -0400 Subject: [PATCH 02/35] feat: update server streaming retries Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 195540efe2e84c2e155f0259afae3edce104a40b Author: Leah Cole Date: Tue Aug 22 16:34:36 2023 -0400 resolve some warnings commit 6e4311c0ce0fabe12755b8268c23f2b5e249bbca Author: Leah Cole Date: Tue Aug 22 16:28:37 2023 -0400 fix last error (now just warnings!) commit d729f28bb882e0d85629c57ab369df31f2ec8e26 Author: Leah Cole Date: Tue Aug 22 16:22:39 2023 -0400 fix no-async-promise-executor lint errors commit 087944aeb505f326a56e829155414116b31ac813 Author: Leah Cole Date: Tue Aug 22 14:59:23 2023 -0400 fix more lint things (more to come) commit 5d2be7aad8ed46d41facae9b2211f0740346ce97 Author: Leah Cole Date: Tue Aug 22 14:19:40 2023 -0400 add missing retry-request related dependency commit fd811e370a7784ea9d3367f00bdea4faabdafb61 Author: Leah Cole Date: Tue Aug 22 14:17:19 2023 -0400 fix some lint issues (more to come) commit 94b5a37c03e01e91a5e42a1bbf19767711b271d0 Author: Leah Cole Date: Tue Aug 22 14:02:07 2023 -0400 npm run fix and remove todo commit 0acc287a089b11b044453788a9df1220645b504f Author: Leah Cole Date: Tue Aug 22 14:00:43 2023 -0400 add missing license headers commit 8535c29cda942f4f0319527fc791f2becbae417c Merge: 8cb5718 b322f78 Author: Alexander Fenster Date: Tue Aug 22 10:56:33 2023 -0700 Merge branch 'main' into gax4upgrade-2 commit 8cb5718e41e9010bf1dcfabb53bda1dea2239e77 Author: Leah Cole Date: Tue Aug 22 10:51:14 2023 -0400 remove todos commit b322f784ec7768e0da781fd54ea5bc9d2347c8cd Author: Mend Renovate Date: Tue Aug 22 13:10:13 2023 +0200 chore(deps): update dependency protobufjs to v7.2.5 (#1494) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [protobufjs](https://protobufjs.github.io/protobuf.js/) ([source](https://togithub.com/protobufjs/protobuf.js)) | [`7.2.4` -> `7.2.5`](https://renovatebot.com/diffs/npm/protobufjs/7.2.4/7.2.5) | [![age](https://developer.mend.io/api/mc/badges/age/npm/protobufjs/7.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/protobufjs/7.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/protobufjs/7.2.4/7.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/protobufjs/7.2.4/7.2.5?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
protobufjs/protobuf.js (protobufjs) ### [`v7.2.5`](https://togithub.com/protobufjs/protobuf.js/blob/HEAD/CHANGELOG.md#725-2023-08-21) [Compare Source](https://togithub.com/protobufjs/protobuf.js/compare/protobufjs-v7.2.4...protobufjs-v7.2.5) ##### Bug Fixes - crash in comment parsing ([#​1890](https://togithub.com/protobufjs/protobuf.js/issues/1890)) ([eaf9f0a](https://togithub.com/protobufjs/protobuf.js/commit/eaf9f0a5a4009a8981c69af78365dfc988ed925b)) - deprecation warning for new Buffer ([#​1905](https://togithub.com/protobufjs/protobuf.js/issues/1905)) ([e93286e](https://togithub.com/protobufjs/protobuf.js/commit/e93286ef70d2e673c341ac08a192cc2abe6fd2eb)) - possible infinite loop when parsing option ([#​1923](https://togithub.com/protobufjs/protobuf.js/issues/1923)) ([f2a8620](https://togithub.com/protobufjs/protobuf.js/commit/f2a86201799af5842e1339c22950abbb3db00f51))
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/gax-nodejs). commit 762591ed28801e5311ab737b04185781a41752e6 Author: Mend Renovate Date: Tue Aug 22 12:56:13 2023 +0200 fix(deps): update dependency protobufjs-cli to v1.1.2 (#1495) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [protobufjs-cli](https://togithub.com/protobufjs/protobuf.js) | [`1.1.1` -> `1.1.2`](https://renovatebot.com/diffs/npm/protobufjs-cli/1.1.1/1.1.2) | [![age](https://developer.mend.io/api/mc/badges/age/npm/protobufjs-cli/1.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/protobufjs-cli/1.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/protobufjs-cli/1.1.1/1.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/protobufjs-cli/1.1.1/1.1.2?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
protobufjs/protobuf.js (protobufjs-cli) ### [`v1.1.2`](https://togithub.com/protobufjs/protobuf.js/releases/tag/protobufjs-cli-v1.1.2): protobufjs-cli: v1.1.2 [Compare Source](https://togithub.com/protobufjs/protobuf.js/compare/protobufjs-cli-v1.1.1...protobufjs-cli-v1.1.2) ##### Bug Fixes - possible infinite loop when parsing option ([#​1923](https://togithub.com/protobufjs/protobuf.js/issues/1923)) ([f2a8620](https://togithub.com/protobufjs/protobuf.js/commit/f2a86201799af5842e1339c22950abbb3db00f51))
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/gax-nodejs). commit 459573e3120ce74b069a7925b4484a4b947b492a Merge: bfd5c1e 2fd932e Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Date: Mon Aug 21 22:30:55 2023 -0400 Merge branch 'gax-4.0.3' into gax4upgrade-2 commit bfd5c1e30982738f41b949ad05783e7439db1539 Author: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Date: Thu Jul 27 15:24:01 2023 -0500 chore(main): release 4.0.3 (#1482) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> commit 1a77db9183309bbf5fbc04a8de6a4938ccd1e6fc Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Thu Jul 27 15:05:21 2023 -0500 fix: make gapic-tools depend on gax-nodejs (#1480) * fix: make gapic-tools depend on gax-nodejs commit 39396f94dedbde287b2183bbcf91c185dbb0aa4d Author: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Date: Tue Jul 25 17:46:33 2023 -0500 chore(main): release 4.0.2 (#1479) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> commit 1ce3fbed477531b9a43ad199faa7c7b3a465ae74 Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Tue Jul 25 17:30:18 2023 -0500 fix: update some pnpm deps (#1478) * fix: update some pnpm deps commit 39daee1b90d8228d413433d7226ae773893a2869 Author: Leah Cole Date: Mon Aug 21 15:42:05 2023 -0400 fix ts error, branch is in sync with gax-4.0.1 commit 3067f89e33af4c8b19703439769de53c26f5d7ad Author: Leah Cole Date: Mon Aug 21 15:15:12 2023 -0400 make sequence service match #1001, fix tsconfig commit 587629a7f5d8e22f1f5b941ca7c116daf2fe4abe Author: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Date: Fri Jul 21 15:16:53 2023 -0700 chore(main): release 4.0.1 (#1474) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> commit 359220a8941a6e8d22edbd830b98fb5f7a1cddf4 Author: Mend Renovate Date: Fri Jul 21 23:55:11 2023 +0200 fix(deps): update dependency retry-request to v6 (#1477) Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> commit 894a1c84be07ab12ec0bb491731618fdabeec5f8 Author: Mend Renovate Date: Fri Jul 21 23:38:15 2023 +0200 fix(deps): update dependency google-auth-library to v9 (#1476) commit a7f5003addd18245f77c4a2a72b936c85de69695 Author: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri Jul 21 14:13:15 2023 -0700 build: add extra test for Node 20, update windows tests (#1468) * build: add extra test for Node 20, update windows tests Source-Link: https://github.com/googleapis/synthtool/commit/38f5d4bfd5d51116a3cf7f260b8fe5d8a0046cfa Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:ef104a520c849ffde60495342ecf099dfb6256eab0fbd173228f447bc73d1aa9 Co-authored-by: Owl Bot Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> commit aad22248b24d6a7572948460d9e8241925bfb926 Author: Daniel Bankhead Date: Tue Jul 18 15:37:51 2023 -0700 Fix: `rimraf` version + remove conflicting types (#1475) * Fix: `rimraf` version + remove conflicting types * chore: refactor `rimraf` import * chore: clean up commit 07ef0602a521acf49ee76d6baba6b3dd00fc7a34 Author: Mend Renovate Date: Tue Jul 18 03:57:35 2023 +0200 chore(deps): update dependency c8 to v8 (#1457) commit e79cb287ae1ab3b33939ff85ec3f0255ac65be78 Author: Megan Potter <57276408+feywind@users.noreply.github.com> Date: Mon Jul 17 13:47:46 2023 -0400 fix: the return types for IAM service methods should be arrays, to match ReturnTuple (#1001) * fix: the return types for IAM service methods should be arrays, to match ReturnTuple * fix: update PR to current types * fix: also fix explicit typings for IAM * build: work around circular dependency with kms (second PR to follow) * docs: broken tsdoc commit 6eb75db56e2f0ab9c3176f8c28769ef71b8e931c Author: Alexander Fenster Date: Thu Jul 13 10:18:06 2023 -0700 fix: replace proto-over-HTTP with REGAPIC (#1471) commit d566e241cabd4bcc5154ed518e175e19d17fca50 Author: Alexander Fenster Date: Thu Jul 13 10:00:57 2023 -0700 test: run speech test from monorepo, use gapic-tools (#1472) commit ebf8bf3b5dd25b2889904acc207e3a8f75586adc Author: Mend Renovate Date: Thu Jul 13 00:06:38 2023 +0200 chore(deps): update dependency protobufjs to v7.2.4 [security] (#1473) * chore(deps): update dependency protobufjs to v7.2.4 [security] * chore: update linkinator config --------- Co-authored-by: Alexander Fenster commit de661e5d3432303faa011bc26fd9db95ce8a781d Author: Simen Bekkhus Date: Wed Jul 12 23:20:05 2023 +0200 fix(deps): update protobufjs (#1467) * fix: update protobufjs * chore: add exclusion to linkinator config --------- Co-authored-by: Alexander Fenster commit 9ee15450f6994090bab8786d7ece4a92c4d18029 Author: Alexander Fenster Date: Tue Jul 11 15:34:54 2023 -0700 fix: add missing devDependency for compodoc (#1470) * fix: add missing devDependency for compodoc * build: try with skipLibCheck * build: revert skipLibCheck * build: install dependency for compodoc * fix(deps): pin compodoc commit c4031e60d1437754f57662d3ce6a9d354df08fab Author: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri Jun 2 13:27:43 2023 -0700 docs: update docs-devsite.sh to use latest node-js-rad version (#1454) Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> Source-Link: https://github.com/googleapis/synthtool/commit/b1ced7db5adee08cfa91d6b138679fceff32c004 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:0527a86c10b67742c409dc726ba9a31ec4e69b0006e3d7a49b0e6686c59cdaa9 Co-authored-by: Owl Bot commit 56171e69e91cb429c24ac77ad39451e97af338ec Author: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Date: Wed May 17 12:24:54 2023 -0700 chore(main): release 4.0.0 (#1445) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> commit 2c80abaff438e79b86e0779289e49b7c696a7d2c Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Wed May 17 11:59:43 2023 -0700 build!: remove is-stream & fast-text-encoding dependencies (#1442) commit 531e373019d8e87d061e5aee544ee5f92cdbd27f Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Wed May 17 11:45:53 2023 -0700 feat!: drop Node 12 & decouple development dependencies (#1439) * feat!: get tools scripts and dependencies running; still need to get top-level module running without tools, remove extra dependencies, and make sure to run commands from the tools dependency * move to node 14 --------- Co-authored-by: Owl Bot commit 6fa194518dd38ba8eb78cc6e7d99a3f8e9bf07e3 Author: Alexander Fenster Date: Tue May 2 13:16:13 2023 -0700 fix: compilation error in fallback.ts (#1444) The compilation failed for me on a clean install, possibly because of some auth library update. We don't really care about the real type of the returned auth client, so a simple type cast should be good. commit fac92bcdb5882bd34e67e002c84d51f780f7034a Author: Leah Cole Date: Mon Aug 21 11:48:37 2023 -0400 Revert "fix: compilation error in fallback.ts (#1444)" This reverts commit 8e6888f469845a0a68dbab3047b3a6843e58da22. commit 8b0de2398f196aef81f7d13861fb9d13d6bcb7e1 Author: Leah Cole Date: Mon Aug 21 11:48:18 2023 -0400 Revert "feat!: drop Node 12 & decouple development dependencies (#1439)" This reverts commit 61a71436e9428c8118831a05fb5c7a3b2b3f99a5. commit 31367d61ec90804d8a59f4174a3523ce10a535f3 Merge: ebc4eec 89ad507 Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Date: Fri Aug 18 10:27:43 2023 -0400 Merge pull request #7 from leahecole/remove-retryrequestoptions Add resumption tests, remove retryrequestoptions commit 6d57b4b23d764e20a5f24d921ebc83624ebfbd11 Author: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Thu Aug 17 19:40:13 2023 +0000 chore: update release-please post-processing for nodejs apiary (#1492) * fix: update release-please post-processing for nodejs apiary Source-Link: https://togithub.com/googleapis/synthtool/commit/59fe44fde9866a26e7ee4e4450fd79f67f8cf599 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:606f3d9d99a1c7cdfa7158cbb1a75bfeef490655e246a2052f9ee741740d736c commit 89ad5078d666de075cefe2b06014cc5f4a2f22f5 Author: Leah Cole Date: Thu Aug 17 14:49:18 2023 -0400 cleanup commit e8161433ed3a82d1eba487cd469c2e6102147f16 Author: Leah Cole Date: Thu Aug 17 14:44:21 2023 -0400 more cleanup commit 55a02beee68cdcbda171aaec2eac2722b31341fa Author: Leah Cole Date: Thu Aug 17 14:30:02 2023 -0400 cleanup comments commit 4b4f87b6e17a9119fc6d1f964f473ca3364fb395 Author: Leah Cole Date: Thu Aug 17 14:28:19 2023 -0400 some cleanup commit df98286f62365a6fa9bbb01989bee029718fe504 Author: Leah Cole Date: Thu Aug 17 14:25:14 2023 -0400 finish unit tests commit 71a913b1093418029fd4b8d7d54cc5a200f99348 Author: Leah Cole Date: Thu Aug 17 12:32:29 2023 -0400 add one resumption unit test commit 4748c9fc3a8cfe31e5abb3e35a6ee0d9a6f0e560 Author: Mend Renovate Date: Thu Aug 17 04:32:24 2023 +0200 fix(deps): update dependency google-proto-files to v4 (#1490) Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> commit 5c7dfd021d1dc5d311e42d8c17b78fe616fed6fe Author: Mend Renovate Date: Wed Aug 16 22:30:21 2023 +0200 fix(deps): update dependency proto3-json-serializer to v2 (#1489) commit 7a742ccaa5dbf3cf2dcb3e463cb3a67515590b01 Author: Leah Cole Date: Wed Aug 16 16:14:35 2023 -0400 remove retryrequestoptions from new retry logic commit 561f6b93e12ae225c05ce744b9c64505cbf99ce3 Author: Leah Cole Date: Wed Aug 16 15:50:35 2023 -0400 make sure test-application passes commit 28a77eef8092ff6e329cd732e46327b1cf71725f Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Wed Aug 16 12:43:54 2023 -0700 build: update linter (#1491) * build: update linter commit 8a63ed3ccfe76fab7567562d40da527c83f4d264 Author: Leah Cole Date: Wed Aug 16 14:24:04 2023 -0400 actually finish regen process commit 7acf0058fd3306317b08c68db5226556987dc8ed Author: Leah Cole Date: Wed Aug 16 13:52:01 2023 -0400 finish regen process commit 03768de3479bb683160c54609971474f607d26c1 Author: Leah Cole Date: Wed Aug 16 17:48:38 2023 +0000 regen gapic showcase commit 80f7747cebfcb241bd007e1b0cf50a899e8b0799 Author: Leah Cole Date: Wed Aug 16 13:44:09 2023 -0400 npm run fix commit 8ae18ae6d0731b3d3d5ffb13872ba44a588f5919 Author: Leah Cole Date: Wed Aug 16 13:05:00 2023 -0400 move resumption to retryoptions commit 702ec918b63b5946555a16f0093ec37de6089fa6 Author: Leah Cole Date: Fri Aug 11 14:24:09 2023 -0400 fix lint commit 34c1600e6b522a8ac935f4fcc864c084023c0dc8 Author: Leah Cole Date: Fri Aug 11 14:05:15 2023 -0400 uncomment main test function commit c2cba5b6802f180ab6bdfec030f94642c771bdf0 Author: Leah Cole Date: Fri Aug 11 14:04:51 2023 -0400 remove debugging statements commit 882969ac5b94d9349eeba19f4795bd7dbcbfe1cb Author: Leah Cole Date: Thu Aug 10 16:58:19 2023 -0400 WIP: debugging commit ce37b66bd426d906b4f240aa542a4f85c3efc63f Author: Leah Cole Date: Thu Aug 10 14:25:57 2023 -0400 fix broken tests with regen client commit b71edf7f18b15d6fcfa693c103259f0766736f55 Author: Leah Cole Date: Thu Aug 10 14:20:47 2023 -0400 manually add newRetry parameter commit d61098a866095ef0287c8237dd844326d922b0df Author: Leah Cole Date: Thu Aug 10 18:05:07 2023 +0000 update teh client again commit 9be128482599c95e7e1027da4e1f38b52c120f89 Author: Leah Cole Date: Thu Aug 10 13:49:42 2023 -0400 remove unnecessary client files commit 9699060103b948f91ad64667a204d259c94e1f97 Author: Leah Cole Date: Thu Aug 10 17:42:58 2023 +0000 regen clients again commit 6f2f6767557b1197bf294bdc9bd88bc83f8851aa Author: Leah Cole Date: Thu Aug 10 12:41:54 2023 -0400 WIP: print statements commit 327a44c41a2c4113d481aec801538a5a1f2a4169 Author: Leah Cole Date: Wed Aug 9 16:06:45 2023 -0400 regenerate showcase commit 04989edb41bdc189af0214b1a849ef3b6bb63d9b Author: Leah Cole Date: Wed Aug 9 19:47:32 2023 +0000 try regenerating showcase commit 1ee50c5db70ae99112d34d4b9b23efa5c4680e47 Author: Leah Cole Date: Wed Aug 9 15:15:29 2023 -0400 update showcase client commit a17f048a20f0e25a5e20cca1a747b4da6275775e Author: Leah Cole Date: Wed Aug 9 19:12:25 2023 +0000 add new showcase commit ebc4eecfc0b6f17cdad8e24a439419e201ec7af3 Author: Leah Cole Date: Thu Aug 10 12:04:13 2023 -0400 add todo commit d7e59675f4f688450e409e6964dfd5b841e58682 Author: Leah Cole Date: Wed Aug 9 12:14:33 2023 -0400 remove return statement commit e77aee91464e518cc9c64a66ca23f49f8ab2a1f5 Author: Leah Cole Date: Wed Aug 9 11:58:58 2023 -0400 todos commit 2e4ad05d430669277ba50f441e4fe41b0ebfc0d0 Merge: b9d57be 4f71c6b Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Date: Wed Aug 9 12:13:39 2023 -0400 Merge pull request #5 from leahecole/docstrings resolve docstring todos commit 4f71c6b02ab4f488b0e6108d0f9f88a33f719c2e Author: Leah Cole Date: Wed Aug 9 12:13:16 2023 -0400 fix return type commit cb1a33fdb5ae92e6bec07e449ce434e976eb47d4 Author: Leah Cole Date: Wed Aug 9 12:05:50 2023 -0400 remove retryStream after talking to gal commit 9b41fc93d0ea276c691bcdb396a8bc4552921da4 Author: Leah Cole Date: Tue Aug 8 16:01:53 2023 -0400 resolve docstring todos commit b9d57be194d4032103d512e3a3e6fccd643da0b5 Author: Leah Cole Date: Tue Aug 8 14:48:46 2023 -0400 npm run fix commit ffb2943c9dce7f5fd19c209845b5af853c03f78c Author: Leah Cole Date: Tue Aug 8 14:47:22 2023 -0400 merge commit 383718fc1f1fdfb565b084c2c0e103a4f7707bf1 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu Aug 3 16:16:00 2023 -0700 added unit tests and fixed unit test alignment commit 8be2b65619c3ee64e23b0b77e27ae8b643c0168e Merge: bf207a7 872ba3d Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu Aug 3 12:53:18 2023 -0700 Merge branch 'gax-3.6.0-3' of https://github.com/leahecole/gax-nodejs into gax-3.6.0-3 commit bf207a713dd5cbe945c70caad0b66d41848159d4 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu Aug 3 12:52:55 2023 -0700 added new test to test-application commit 872ba3dda6e28e2d1d614057a25630fad688730b Author: Leah Cole Date: Thu Aug 3 13:21:34 2023 -0400 fix broken test commit ea358ff7dc487295405b0f13c4722440910dfd09 Merge: efd41b1 ead6d35 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Aug 2 13:55:31 2023 -0700 Merge branch 'gax-3.6.0-3' of https://github.com/leahecole/gax-nodejs into gax-3.6.0-3 commit ead6d356f1e72c5f51da0a71aea09ac0a9e2ed7e Author: Leah Cole Date: Wed Aug 2 16:54:57 2023 -0400 fix failing test commit efd41b1af404469fa23933a7d3092c49d5470fab Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Aug 2 13:54:44 2023 -0700 Added test to test-application commit debcdece9a9ea2e54327f4b43bbd102edf078fd6 Author: Leah Cole Date: Wed Aug 2 16:04:41 2023 -0400 address more todos commit 6ef735bbeb39e059b0fe5fe43fb150e0ce16c656 Author: Leah Cole Date: Wed Aug 2 14:08:32 2023 -0400 work on some todos commit b3493c2a82ece0704493ee813513b5e90b248df2 Merge: 4d03684 55eb385 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Aug 2 10:36:22 2023 -0700 Merge branch 'gax-3.6.0-3' of https://github.com/leahecole/gax-nodejs into gax-3.6.0-3 commit 4d03684db5ab18abd6d03148095ad0554336afe3 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Aug 2 10:35:36 2023 -0700 wip commit commit ea8020f9084ff068a3139a8b19be6b8c0caa74e3 Author: Mend Renovate Date: Wed Aug 2 13:20:13 2023 +0200 fix(deps): update dependency @grpc/grpc-js to ~1.9.0 (#1486) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@grpc/grpc-js](https://grpc.io/) ([source](https://togithub.com/grpc/grpc-node)) | [`~1.8.0` -> `~1.9.0`](https://renovatebot.com/diffs/npm/@grpc%2fgrpc-js/1.8.21/1.9.0) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@grpc%2fgrpc-js/1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@grpc%2fgrpc-js/1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@grpc%2fgrpc-js/1.8.21/1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@grpc%2fgrpc-js/1.8.21/1.9.0?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
grpc/grpc-node (@​grpc/grpc-js) ### [`v1.9.0`](https://togithub.com/grpc/grpc-node/releases/tag/%40grpc/grpc-js%401.9.0): @​grpc/grpc-js 1.9.0 [Compare Source](https://togithub.com/grpc/grpc-node/compare/@grpc/grpc-js@1.8.21...@grpc/grpc-js@1.9.0) - Implement channel idle timeout and the channel option `grpc.client_idle_timeout_ms` ([#​2471](https://togithub.com/grpc/grpc-node/issues/2471)) - Implement [gRFC A62: `pick_first`: sticky TRANSIENT_FAILURE and address order randomization](https://togithub.com/grpc/proposal/blob/master/A62-pick-first.md) ([#​2511](https://togithub.com/grpc/grpc-node/issues/2511)) - Fix premature leaving of context due to improper `Http2ServerCallStream` handling ([#​2501](https://togithub.com/grpc/grpc-node/issues/2501) contributed by [@​CedricKassen](https://togithub.com/CedricKassen)) - Add channel option `grpc-node.tls_enable_trace` to enable Node TLS tracing ([#​2507](https://togithub.com/grpc/grpc-node/issues/2507)) - Cancel deadline timer on server when call is cancelled ([#​2508](https://togithub.com/grpc/grpc-node/issues/2508)) Experimental changes: - Added `grpc.experimental.createResolver`
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/gax-nodejs). commit 55eb3856c01149af096a72bc3d0918002b72cfdb Author: Leah Cole Date: Tue Aug 1 16:24:20 2023 -0400 fix broken test commit 75efdefc66adf4b0ed780d398750fa431afae479 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Mon Jul 31 15:53:47 2023 -0700 added test and StreamingSequence factory commit a6d92144d0cbc8fc3fa2cfc7cf8d66218cabb3a9 Author: Leah Cole Date: Fri Jul 28 17:09:53 2023 -0700 Fix all expected passing tests commit b24269638f57eef8469610cf9f4eca2cda28dd61 Author: Leah Cole Date: Fri Jul 28 17:00:23 2023 -0700 remove one failing test, add a passing more relevant test commit 2432c984b06e64f70f9c33d66a9f80e215775103 Author: Leah Cole Date: Fri Jul 28 16:19:55 2023 -0700 add afterEach for sinon.restore commit c50286d0aac62144d025a6bf177db10e55856deb Author: Leah Cole Date: Fri Jul 28 16:18:43 2023 -0700 make checkRetrySettings actually return something commit 3d5b520abf44dd030165bbc0d8629d90aa1cb9d0 Author: Leah Cole Date: Fri Jul 28 14:35:38 2023 -0700 fix another test commit 2e392a22c6c1bb09796f0e186275e53761af57b1 Author: Leah Cole Date: Fri Jul 28 14:13:08 2023 -0700 fix one test commit 05917c71b48b7ee5a461dccd8e635190c755589b Author: Leah Cole Date: Fri Jul 28 14:10:53 2023 -0700 fix one test commit 8116222e050072eddefa18203bd8df3bd91faa03 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Fri Jul 28 13:25:19 2023 -0700 removed unused variables commit 9755031accf1c93b529b5f693b0e268f7a926fcd Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Fri Jul 28 13:13:27 2023 -0700 wip commit commit 2b2f84924b8dc723b913500e431631c4c55cf401 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Fri Jul 28 12:19:52 2023 -0700 cleaned up streamHandoffHelper by adding helpers commit a47c4fe34ab7c8c6f8204a6cec4b652c6bf86b63 Author: Mend Renovate Date: Thu Jul 27 22:50:13 2023 +0200 chore(deps): update dependency @compodoc/compodoc to v1.1.21 (#1453) [![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com) This PR contains the following updates: | Package | Change | Age | Adoption | Passing | Confidence | |---|---|---|---|---|---| | [@compodoc/compodoc](https://togithub.com/compodoc/compodoc) | [`1.1.19` -> `1.1.21`](https://renovatebot.com/diffs/npm/@compodoc%2fcompodoc/1.1.19/1.1.21) | [![age](https://developer.mend.io/api/mc/badges/age/npm/@compodoc%2fcompodoc/1.1.21?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@compodoc%2fcompodoc/1.1.21?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@compodoc%2fcompodoc/1.1.19/1.1.21?slim=true)](https://docs.renovatebot.com/merge-confidence/) | [![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@compodoc%2fcompodoc/1.1.19/1.1.21?slim=true)](https://docs.renovatebot.com/merge-confidence/) | --- ### Release Notes
compodoc/compodoc (@​compodoc/compodoc) ### [`v1.1.21`](https://togithub.com/compodoc/compodoc/blob/HEAD/CHANGELOG.md#1121) [Compare Source](https://togithub.com/compodoc/compodoc/compare/1.1.20...1.1.21) ##### Bug fixes - feat(app): downgrade Marked version ([e0a4b78](https://togithub.com/compodoc/compodoc/commit/e0a4b78)), closes [#​1349](https://togithub.com/compodoc/compodoc/issues/1349) ### [`v1.1.20`](https://togithub.com/compodoc/compodoc/blob/HEAD/CHANGELOG.md#1120---2023-05-23) [Compare Source](https://togithub.com/compodoc/compodoc/compare/1.1.19...1.1.20) ##### Merged - fix(Input): Add support for Object Expressions in Input decorators [#​1326](https://togithub.com/compodoc/compodoc/pull/1326), Thanks [valentinpalkovic](https://togithub.com/valentinpalkovic) - fix(app): overview depth [#​1310](https://togithub.com/compodoc/compodoc/pull/1310), Thanks [albeniraouf](https://togithub.com/albeniraouf) - Translates to Bulgarian [#​1312](https://togithub.com/compodoc/compodoc/pull/1312), Thanks [3phase](https://togithub.com/3phase) ##### Bug fixes - feat(app): Directive composition API for directives and components ([127076a](https://togithub.com/compodoc/compodoc/commit/127076a)), closes [#​1340](https://togithub.com/compodoc/compodoc/issues/1340) - feat(app): Required Inputs ([e1a5396](https://togithub.com/compodoc/compodoc/commit/e1a5396)), closes [#​1340](https://togithub.com/compodoc/compodoc/issues/1340) - feat(app): Standalone components, directives and pipes support ([cb02ca0](https://togithub.com/compodoc/compodoc/commit/cb02ca0)), closes [#​1323](https://togithub.com/compodoc/compodoc/issues/1323) - fix(app): support exportAs for directives ([76a8f34](https://togithub.com/compodoc/compodoc/commit/76a8f34)), closes [#​1328](https://togithub.com/compodoc/compodoc/issues/1328) - feat(app): bump [@​compodoc/ngd-transformer](https://togithub.com/compodoc/ngd-transformer) ([ef9bd94](https://togithub.com/compodoc/compodoc/commit/ef9bd94)), closes [#​1311](https://togithub.com/compodoc/compodoc/issues/1311) - fix(app): service/injectable export in module providers ([34967a9](https://togithub.com/compodoc/compodoc/commit/34967a9)), closes [#​1290](https://togithub.com/compodoc/compodoc/issues/1290) - fix(app): missing rel attribute with \_blank links ([c8379e0](https://togithub.com/compodoc/compodoc/commit/c8379e0)), closes [#​1282](https://togithub.com/compodoc/compodoc/issues/1282) - feat(app): Add specific id in each html section ([03ac1ad](https://togithub.com/compodoc/compodoc/commit/03ac1ad)), closes [#​1241](https://togithub.com/compodoc/compodoc/issues/1241) - fix(app): Invalid links to a class when the class name includes an interface name ([047cedb](https://togithub.com/compodoc/compodoc/commit/047cedb)), closes [#​1239](https://togithub.com/compodoc/compodoc/issues/1239) - fix(routing): path wrongly resolved during routing analysis ([1722ca3](https://togithub.com/compodoc/compodoc/commit/1722ca3)), closes [#​1170](https://togithub.com/compodoc/compodoc/issues/1170)
--- ### Configuration 📅 **Schedule**: Branch creation - "after 9am and before 3pm" (UTC), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://developer.mend.io/github/googleapis/gax-nodejs). commit e4f548254bfce3daa3b02ae81764bb3394fc4f23 Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Thu Jul 27 15:35:40 2023 -0500 fix: release new version of gapic-tools (#1483) Need to do this until I set up the release-please process properly. commit 73fcc79413d7ec041d0be06c95a47ea0141c391c Merge: 4450439 b7db054 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu Jul 27 13:33:39 2023 -0700 Merge branch 'gax-3.6.0-3' of https://github.com/leahecole/gax-nodejs into gax-3.6.0-3 commit 44504395f47cb094411e21f7d4071970f3fb231f Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu Jul 27 13:32:12 2023 -0700 refactored to use retry.retryCodesOrShouldRetryFn commit 2fd932e96255f7ece901b2c3e1b08873632a0836 Author: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Date: Thu Jul 27 15:24:01 2023 -0500 chore(main): release 4.0.3 (#1482) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> commit d0f410d2e08f393f2661c8c92568a0b518fddf99 Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Thu Jul 27 15:05:21 2023 -0500 fix: make gapic-tools depend on gax-nodejs (#1480) * fix: make gapic-tools depend on gax-nodejs commit b7db0545983f0384edc51c62d6311b8aca520006 Author: Leah Cole Date: Thu Jul 27 15:48:36 2023 -0400 fix one instance of shoudlretryfn commit 0bbce136021a3c88108f4317323d0869b959250c Merge: 5062a61 bda9605 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu Jul 27 12:27:23 2023 -0700 Merge pull request #4 from leahecole/b/279946426 B/279946426 commit bda9605b9ea6ac897fc20919e3dd58b8e779a74c Merge: 4dab819 5062a61 Author: Leah E. Cole <6719667+leahecole@users.noreply.github.com> Date: Thu Jul 27 22:05:59 2023 +0300 Merge branch 'gax-3.6.0-3' into b/279946426 commit 4dab819d44c466bf4fd23ceeaaa721f789ac4227 Author: Leah Cole Date: Thu Jul 27 14:41:55 2023 -0400 remove typo commit 5062a616f98c0b70827d2c94197033c29a2c0b52 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu Jul 27 10:44:34 2023 -0700 uncommit streaming unit tests commit 7eba460024e498eaa91f1427c7d44bacf2b49b61 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Jul 26 15:51:48 2023 -0700 Added getResumptionRequestFn commit 561cec4e111f2c79815a602a69c8e330b1508521 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Jul 26 13:31:35 2023 -0700 WIP Commit: Added shouldRetryFn and fixed tests commit 0b6784f596dfa9786a214143eef6bdda20bfa570 Author: Leah Cole Date: Wed Jul 26 15:38:08 2023 -0400 a few more assertions commit 30193ad2f41c4e29a3292eb4ef517d9e19791b54 Author: Leah Cole Date: Wed Jul 26 13:37:06 2023 -0400 fix lint, add todos commit 2bb9ae256af30ce27837c71b34da0705604186a3 Author: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Date: Tue Jul 25 17:46:33 2023 -0500 chore(main): release 4.0.2 (#1479) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> commit 39583d5f4faab89b511fe317bd1ec3334c2ea3f5 Author: sofisl <55454395+sofisl@users.noreply.github.com> Date: Tue Jul 25 17:30:18 2023 -0500 fix: update some pnpm deps (#1478) * fix: update some pnpm deps commit 6927a11d6748ea35b8437a9482b1f3c20bd916e1 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Tue Jul 25 15:26:46 2023 -0700 WIP Commit commit bf39468b65c7e13f2efe50b57396911c7a3becd7 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Tue Jul 25 11:33:00 2023 -0700 WIP commit commit e791c79947f227e90bbd54dda644e6805948f785 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Mon Jul 24 10:51:59 2023 -0700 fixed failing unit test commit 8663eb14f9c6c6ab3a6009f6725b347698f6e1f3 Author: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Date: Fri Jul 21 15:16:53 2023 -0700 chore(main): release 4.0.1 (#1474) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> commit 6401a88c50fa0eb3eb8a73cefc830896369c3330 Author: Mend Renovate Date: Fri Jul 21 23:55:11 2023 +0200 fix(deps): update dependency retry-request to v6 (#1477) Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> commit 8afdd591646a190fde38728f0df14c604643f5cc Author: Mend Renovate Date: Fri Jul 21 23:38:15 2023 +0200 fix(deps): update dependency google-auth-library to v9 (#1476) commit 9e93eddc27fbb140a2bdaaef2e9d0db3a05921f4 Author: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri Jul 21 14:13:15 2023 -0700 build: add extra test for Node 20, update windows tests (#1468) * build: add extra test for Node 20, update windows tests Source-Link: https://github.com/googleapis/synthtool/commit/38f5d4bfd5d51116a3cf7f260b8fe5d8a0046cfa Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:ef104a520c849ffde60495342ecf099dfb6256eab0fbd173228f447bc73d1aa9 Co-authored-by: Owl Bot Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> commit 0252683e00ab3486efb1d606996c2b9bf0c6e085 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Fri Jul 21 13:15:50 2023 -0700 changed name of forwardClientEvents commit f5f27a8a51feabecc90b854860fa5266c18cf7b2 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Fri Jul 21 13:03:48 2023 -0700 lint changes commit 6631734a51fb4bc270c7012d03af1f3c6eba62ec Author: Leah Cole Date: Thu Jul 20 16:12:27 2023 -0400 reorganize streaming unit tests commit e4daff99df9ad612b4713d3b467713e394339e73 Author: Leah Cole Date: Thu Jul 20 15:51:07 2023 -0400 convert retryCodes to retryCodesorShouldRetryFn commit f9f8b1328c38718bf621b92338b3d81297525aa6 Author: Daniel Bankhead Date: Tue Jul 18 15:37:51 2023 -0700 Fix: `rimraf` version + remove conflicting types (#1475) * Fix: `rimraf` version + remove conflicting types * chore: refactor `rimraf` import * chore: clean up commit f4a46be2b6a1e2771085ca86f5ce64a1fbdb3c1a Author: Mend Renovate Date: Tue Jul 18 03:57:35 2023 +0200 chore(deps): update dependency c8 to v8 (#1457) commit 2f71435ef3fb8c9d14d00acf172ff1e4b36b82a7 Author: Leah Cole Date: Mon Jul 17 16:01:31 2023 -0400 finish rebasing Gal's work into mine commit 4fe5f2d5c677ed02e9876e7b8b818ac067aac674 Author: Leah Cole Date: Mon Jul 17 15:41:58 2023 -0400 uncomment for rebase commit 8709ac9e84a76d2c1bdac8af476e0f8e977471e0 Author: Leah Cole Date: Mon Jul 17 15:40:15 2023 -0400 uncomment tests for rebase commit 866fd2aaa03d445c4ca393f8abcfa4f03dcd3f6e Author: Leah Cole Date: Mon Jul 17 15:28:32 2023 -0400 WIP: param conversion commit 4a67dd412083e632b24c3136f850a417741bf98f Author: Leah Cole Date: Thu Jul 13 15:56:02 2023 -0400 WIP: test and parameter conversion commit 786b7be8b7f850d46547a3240b6cc81954a9459e Author: Leah Cole Date: Thu Jul 13 15:55:32 2023 -0400 fix: update retryrequestoptions to be aligned with retry-request commit 0a671ea24fb0a0c11aaaea507e0dab4d44a06c5e Author: Leah Cole Date: Tue Jun 27 15:18:26 2023 -0400 passing tests for warnings commit 385b7861d3a335840c7894dc5e22f07e4ac355a1 Author: Leah Cole Date: Fri Jun 23 14:25:55 2023 -0400 WIP: adding more unit tests commit 820ed4ff340ac3e06d48e57ada33a8826e66ea8b Author: Leah Cole Date: Wed Jun 21 15:41:41 2023 -0400 add test for both parameters being set commit 3ffc9684c4d21d309b97df6c2d0ed1d9548953a3 Author: Leah Cole Date: Fri Jun 16 15:11:51 2023 -0400 WIP: experimenting with spies in streaming calls commit 8a05223c68507cc0b10b455a0cd377dd8a96eaf7 Author: Leah Cole Date: Tue Jun 13 16:48:28 2023 -0400 WIP: adding unit tests for option conversion commit 48eed955e7329f55f9427a7bc0656cfe2af395e8 Author: Megan Potter <57276408+feywind@users.noreply.github.com> Date: Mon Jul 17 13:47:46 2023 -0400 fix: the return types for IAM service methods should be arrays, to match ReturnTuple (#1001) * fix: the return types for IAM service methods should be arrays, to match ReturnTuple * fix: update PR to current types * fix: also fix explicit typings for IAM * build: work around circular dependency with kms (second PR to follow) * docs: broken tsdoc commit 4266f43922d0d582b8eced11f4a21c98a8b451fe Author: Alexander Fenster Date: Thu Jul 13 10:18:06 2023 -0700 fix: replace proto-over-HTTP with REGAPIC (#1471) commit c1c4dc19da45780ade32178f9f7ac76e8bb5aef1 Author: Alexander Fenster Date: Thu Jul 13 10:00:57 2023 -0700 test: run speech test from monorepo, use gapic-tools (#1472) commit 7b8414bbb0c535e23dac90e8461a8145d6b6d08d Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Jul 12 15:44:36 2023 -0700 added tests commit d2a5ccbf93a987bc4ae6fd6bc3e319ddf10d26f6 Author: Mend Renovate Date: Thu Jul 13 00:06:38 2023 +0200 chore(deps): update dependency protobufjs to v7.2.4 [security] (#1473) * chore(deps): update dependency protobufjs to v7.2.4 [security] * chore: update linkinator config --------- Co-authored-by: Alexander Fenster commit 0a7dd948573bd9553a0e9548e9ab92dbcfcb7414 Author: Simen Bekkhus Date: Wed Jul 12 23:20:05 2023 +0200 fix(deps): update protobufjs (#1467) * fix: update protobufjs * chore: add exclusion to linkinator config --------- Co-authored-by: Alexander Fenster commit 170a5b69a8d4610fb95ad149ba3632904286a904 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Jul 12 11:17:25 2023 -0700 uncomment tests and remove streaming retries.ts commit 6fd1c08de3375df460507ce0ea0d588fb9bd1d65 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Jul 12 10:49:40 2023 -0700 added check in forwardEvents for API retry commit f551ba8ca5d573a245830916f93fa65cfd44df67 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Jul 12 10:30:31 2023 -0700 add checks to forwardClientEvents commit 115e317728c8ae6fa0e61f54d0087e26382d8230 Author: Alexander Fenster Date: Tue Jul 11 15:34:54 2023 -0700 fix: add missing devDependency for compodoc (#1470) * fix: add missing devDependency for compodoc * build: try with skipLibCheck * build: revert skipLibCheck * build: install dependency for compodoc * fix(deps): pin compodoc commit f420a787774464130caa27724d04ba339b796110 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Mon Jul 10 15:44:53 2023 -0700 added check to see which retryCodes are allowed commit c628a344404ec68ded19dfa67f0be73d562c139e Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Fri Jul 7 11:24:40 2023 -0700 server stream update commit 17621154683532383505d8d07e8f82fdc4aad8ac Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Mon Jun 26 14:01:08 2023 -0700 added retry logic commit c51b69b14808717a295851af2b263289dc35f5cc Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Wed Jun 14 11:06:09 2023 -0700 updated retry logic commit 1ac4135dc151adec5d744d1ce5a60202b221a299 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Tue Jun 13 13:04:37 2023 -0700 added retry logic commit 6e7135f53e83e799d0b57ee5eb08e0d609dea165 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Mon Jun 5 15:23:23 2023 -0700 added changes commit 82aaa3996514aa74cd7078da095d7509fc91cf3d Author: gcf-owl-bot[bot] <78513119+gcf-owl-bot[bot]@users.noreply.github.com> Date: Fri Jun 2 13:27:43 2023 -0700 docs: update docs-devsite.sh to use latest node-js-rad version (#1454) Co-authored-by: sofisl <55454395+sofisl@users.noreply.github.com> Source-Link: https://github.com/googleapis/synthtool/commit/b1ced7db5adee08cfa91d6b138679fceff32c004 Post-Processor: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest@sha256:0527a86c10b67742c409dc726ba9a31ec4e69b0006e3d7a49b0e6686c59cdaa9 Co-authored-by: Owl Bot commit 1473e5a8c7c5c3f5b068462fbaf275077216ae34 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Tue May 30 14:11:16 2023 -0700 changed StreamRetries to StreamRetryRequest commit 5d4e24206b720a07995227011ffe5d5940c3fe4e Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Thu May 25 13:47:32 2023 -0700 Added new streamingRetryRequest option commit b7b416cec3a3abd98bc4304eb7264f6963daaf1b Author: Leah Cole Date: Wed May 24 18:04:23 2023 -0400 add one dev dependency commit b2769be042ea4b872483dc1bca270d648b6efb6b Author: Leah Cole Date: Wed May 24 17:51:41 2023 -0400 Revert "feat!: drop Node 12 & decouple development dependencies (#1439)" This reverts commit 61a71436e9428c8118831a05fb5c7a3b2b3f99a5. commit 10b92429add5eaa05a548da9d1cfa396defb1618 Author: Leah Cole Date: Wed May 24 17:49:03 2023 -0400 Revert "build!: remove is-stream & fast-text-encoding dependencies (#1442)" This reverts commit 111133c5a2bd5a54ebe99a337de9a5a58ee67d2e. commit 1489e3711b9362c6c2d4e91e5bf6d10db34397f3 Author: Leah Cole Date: Wed May 24 17:49:01 2023 -0400 Revert "chore(main): release 4.0.0 (#1445)" This reverts commit 064e131ba7371e6d994e3d73790fa5e1fd09189a. commit e445d8ff207539bf4fef4595f69497cac9240083 Author: Leah Cole Date: Tue May 2 16:06:55 2023 -0400 revert our changes to how the server runs commit 3806b29afef55b9639a1e54da7006aaa92bd1dd8 Author: Leah Cole Date: Tue May 2 15:27:22 2023 -0400 replace status codes with enums commit a56867d5a56da088da0c846174a35944ca74eed2 Author: Leah Cole Date: Tue May 2 14:58:32 2023 -0400 remove not needed things commit 8a4806c4f679206c8f31e03be0b5fd42a17d41c9 Author: Leah Cole Date: Tue May 2 14:31:55 2023 -0400 revert copyright year commit b430bb2ebc4e0ed8cd8e84d27b75bb37213d027b Author: Leah Cole Date: Tue May 2 14:27:36 2023 -0400 pacakge.json change commit ad7cb0c10828b710996b9f54fdaeba0ca44961d6 Author: Leah Cole Date: Tue May 2 14:26:38 2023 -0400 revert package.json changes commit a6df06029d8c0d0679f3a2f0c0e56f1ec1003ce8 Author: Cloud Composer Team Date: Tue May 2 17:47:21 2023 +0000 run npm run fix on the showcase client commit 8cac3da20b6c58d35ceecc73ecb7142998102a0b Author: Cloud Composer Team Date: Wed Apr 26 19:44:49 2023 +0000 update showcase-echo-client commit 17b32ca0ccc4c2103688abccc85fc2095d3f19a4 Author: Leah Cole Date: Tue Apr 25 14:14:18 2023 -0400 Add comment to alex` commit 6ae2f431ceb04efa25ffd6def7cc19f573e6e0bf Author: Leah Cole Date: Tue Apr 25 13:32:23 2023 -0400 add back imports for running w/ local showcase-server commit 1c64a2330082baf9ba610a81cb94ec89d9180baa Author: Leah Cole Date: Tue Apr 25 13:30:49 2023 -0400 add first couple of tests for sequence streaming commit 2022441d1bae0a22fc8e6e9475d531a9a0310c2d Author: Leah Cole Date: Tue Apr 25 11:09:31 2023 -0400 WIP: Getting tests ready for PR commit 11df39223f15a6b1a3b588ca6bc9bd43304e2183 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Mon Apr 24 21:28:44 2023 +0300 updated streamingNotRetryEligible commit 0671a2f5ad8e25bed27e5ea5efd12d420f73dd01 Author: Gal Zahavi <38544478+galz10@users.noreply.github.com> Date: Fri Apr 21 22:00:44 2023 +0300 WIP: edit streamingNotRetryEligible test Co-authored-by: Leah E. Cole commit ed0918da295a8d92f4bfcfe6ef9bc8fb632d830f Author: Leah Cole Date: Wed Apr 19 16:20:14 2023 -0400 WIP: test for non retry eligible calls commit 3bea8a058365137d093cafc7983c791fc64e7f96 Author: Leah Cole Date: Wed Apr 19 15:37:17 2023 -0400 fix broken tests, add comments with next steps commit b2c5afd750ba1c255382bd2d8a0ce1e71f3e28d2 Author: Leah Cole Date: Tue Apr 18 13:54:54 2023 -0400 fix merge conflict commit 8be6b90c01bd81dd0db448d4c2e28a201c820eb7 Author: Gal Zahavi Date: Tue Apr 18 17:41:59 2023 +0000 chore : added sequenceStreamingService --- .github/.OwlBot.lock.yaml | 3 +- .kokoro/presubmit/node14/common.cfg | 24 + .kokoro/presubmit/node14/samples-test.cfg | 12 + .kokoro/presubmit/node14/system-test.cfg | 12 + .kokoro/presubmit/node14/test.cfg | 0 .kokoro/release/docs-devsite.sh | 5 +- CHANGELOG.md | 27 + client-libraries.md | 19 +- package.json | 38 +- release-please-config.json | 15 +- samples/package.json | 4 +- src/clientInterface.ts | 1 + src/createApiCall.ts | 62 +- src/fallback.ts | 35 +- src/fallbackProto.ts | 71 - src/gax.ts | 251 +++- src/iamService.ts | 18 +- src/normalCalls/retries.ts | 6 +- src/normalCalls/timeout.ts | 20 + src/paginationCalls/pageDescriptor.ts | 12 +- src/protosList.json | 8 + src/streamArrayParser.ts | 2 +- src/streamingCalls/streamDescriptor.ts | 11 +- src/streamingCalls/streaming.ts | 357 ++++- src/streamingCalls/streamingApiCaller.ts | 10 +- src/streamingRetryRequest.ts | 205 +++ test/browser-test/karma.conf.js | 4 +- test/browser-test/package.json | 3 + test/browser-test/test/test.endtoend.ts | 7 +- test/browser-test/test/test.grpc-fallback.ts | 28 +- test/showcase-echo-client/package.json | 39 +- .../protos/google/showcase/v1beta1/echo.proto | 145 +- .../google/showcase/v1beta1/sequence.proto | 258 ++++ test/showcase-echo-client/src/index.ts | 11 +- .../src/v1beta1/echo_client.ts | 834 ++++++------ .../src/v1beta1/echo_client_config.json | 8 + .../src/v1beta1/echo_proto_list.json | 3 +- .../showcase-echo-client/src/v1beta1/index.ts | 3 +- .../src/v1beta1/sequence_service_client.ts | 1182 +++++++++++++++++ .../sequence_service_client_config.json | 50 + .../v1beta1/sequence_service_proto_list.json | 4 + test/showcase-server/package.json | 2 +- test/showcase-server/src/index.ts | 8 +- test/system-test/test.clientlibs.ts | 39 +- test/test-application/package.json | 4 +- test/test-application/src/index.ts | 760 +++++++++-- test/unit/apiCallable.ts | 107 +- test/unit/gax.ts | 19 +- test/unit/grpc-fallback.ts | 23 +- test/unit/streaming.ts | 842 ++++++++++-- test/unit/streamingRetryRequest.ts | 122 ++ tools/package.json | 22 +- tools/src/compileProtos.ts | 10 +- tools/test/compileProtos.ts | 25 +- tools/test/fixtures/echo.js | 19 +- tools/test/minify.ts | 5 +- tsconfig.json | 3 + 57 files changed, 4831 insertions(+), 986 deletions(-) create mode 100644 .kokoro/presubmit/node14/common.cfg create mode 100644 .kokoro/presubmit/node14/samples-test.cfg create mode 100644 .kokoro/presubmit/node14/system-test.cfg create mode 100644 .kokoro/presubmit/node14/test.cfg delete mode 100644 src/fallbackProto.ts create mode 100644 src/streamingRetryRequest.ts create mode 100644 test/showcase-echo-client/protos/google/showcase/v1beta1/sequence.proto create mode 100644 test/showcase-echo-client/src/v1beta1/sequence_service_client.ts create mode 100644 test/showcase-echo-client/src/v1beta1/sequence_service_client_config.json create mode 100644 test/showcase-echo-client/src/v1beta1/sequence_service_proto_list.json create mode 100644 test/unit/streamingRetryRequest.ts diff --git a/.github/.OwlBot.lock.yaml b/.github/.OwlBot.lock.yaml index 0b836e119..d9b4b9749 100644 --- a/.github/.OwlBot.lock.yaml +++ b/.github/.OwlBot.lock.yaml @@ -13,4 +13,5 @@ # limitations under the License. docker: image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest - digest: sha256:e6d785d6de3cab027f6213d95ccedab4cab3811b0d3172b78db2216faa182e32 + digest: sha256:606f3d9d99a1c7cdfa7158cbb1a75bfeef490655e246a2052f9ee741740d736c +# created: 2023-08-17T19:15:55.176034173Z diff --git a/.kokoro/presubmit/node14/common.cfg b/.kokoro/presubmit/node14/common.cfg new file mode 100644 index 000000000..bda58c77b --- /dev/null +++ b/.kokoro/presubmit/node14/common.cfg @@ -0,0 +1,24 @@ +# Format: //devtools/kokoro/config/proto/build.proto + +# Build logs will be here +action { + define_artifacts { + regex: "**/*sponge_log.xml" + } +} + +# Download trampoline resources. +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" + +# Use the trampoline script to run in docker. +build_file: "gax-nodejs/.kokoro/trampoline_v2.sh" + +# Configure the docker image for kokoro-trampoline. +env_vars: { + key: "TRAMPOLINE_IMAGE" + value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" +} +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/gax-nodejs/.kokoro/test.sh" +} diff --git a/.kokoro/presubmit/node14/samples-test.cfg b/.kokoro/presubmit/node14/samples-test.cfg new file mode 100644 index 000000000..3fb9c46ca --- /dev/null +++ b/.kokoro/presubmit/node14/samples-test.cfg @@ -0,0 +1,12 @@ +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/gax-nodejs/.kokoro/samples-test.sh" +} + +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "long-door-651-kokoro-system-test-service-account" +} \ No newline at end of file diff --git a/.kokoro/presubmit/node14/system-test.cfg b/.kokoro/presubmit/node14/system-test.cfg new file mode 100644 index 000000000..8a980142a --- /dev/null +++ b/.kokoro/presubmit/node14/system-test.cfg @@ -0,0 +1,12 @@ +# Download resources for system tests (service account key, etc.) +gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" + +env_vars: { + key: "TRAMPOLINE_BUILD_FILE" + value: "github/gax-nodejs/.kokoro/system-test.sh" +} + +env_vars: { + key: "SECRET_MANAGER_KEYS" + value: "long-door-651-kokoro-system-test-service-account" +} \ No newline at end of file diff --git a/.kokoro/presubmit/node14/test.cfg b/.kokoro/presubmit/node14/test.cfg new file mode 100644 index 000000000..e69de29bb diff --git a/.kokoro/release/docs-devsite.sh b/.kokoro/release/docs-devsite.sh index 2198e67fe..3596c1e4c 100755 --- a/.kokoro/release/docs-devsite.sh +++ b/.kokoro/release/docs-devsite.sh @@ -25,5 +25,6 @@ if [[ -z "$CREDENTIALS" ]]; then fi npm install -npm install --no-save @google-cloud/cloud-rad@^0.2.5 -npx @google-cloud/cloud-rad \ No newline at end of file +npm install --no-save @google-cloud/cloud-rad@^0.3.7 +# publish docs to devsite +npx @google-cloud/cloud-rad . cloud-rad diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a6e87677..00e2523e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,33 @@ [1]: https://www.npmjs.com/package/gax-nodejs?activeTab=versions +## [4.0.3](https://github.com/googleapis/gax-nodejs/compare/v4.0.2...v4.0.3) (2023-07-27) + + +### Bug Fixes + +* Make gapic-tools depend on gax-nodejs ([#1480](https://github.com/googleapis/gax-nodejs/issues/1480)) ([d0f410d](https://github.com/googleapis/gax-nodejs/commit/d0f410d2e08f393f2661c8c92568a0b518fddf99)) + +## [4.0.2](https://github.com/googleapis/gax-nodejs/compare/v4.0.1...v4.0.2) (2023-07-25) + + +### Bug Fixes + +* Update some pnpm deps ([#1478](https://github.com/googleapis/gax-nodejs/issues/1478)) ([39583d5](https://github.com/googleapis/gax-nodejs/commit/39583d5f4faab89b511fe317bd1ec3334c2ea3f5)) + +## [4.0.1](https://github.com/googleapis/gax-nodejs/compare/v4.0.0...v4.0.1) (2023-07-21) + + +### Bug Fixes + +* `rimraf` version + remove conflicting types ([#1475](https://github.com/googleapis/gax-nodejs/issues/1475)) ([f9f8b13](https://github.com/googleapis/gax-nodejs/commit/f9f8b1328c38718bf621b92338b3d81297525aa6)) +* Add missing devDependency for compodoc ([#1470](https://github.com/googleapis/gax-nodejs/issues/1470)) ([115e317](https://github.com/googleapis/gax-nodejs/commit/115e317728c8ae6fa0e61f54d0087e26382d8230)) +* **deps:** Update dependency google-auth-library to v9 ([#1476](https://github.com/googleapis/gax-nodejs/issues/1476)) ([8afdd59](https://github.com/googleapis/gax-nodejs/commit/8afdd591646a190fde38728f0df14c604643f5cc)) +* **deps:** Update dependency retry-request to v6 ([#1477](https://github.com/googleapis/gax-nodejs/issues/1477)) ([6401a88](https://github.com/googleapis/gax-nodejs/commit/6401a88c50fa0eb3eb8a73cefc830896369c3330)) +* **deps:** Update protobufjs ([#1467](https://github.com/googleapis/gax-nodejs/issues/1467)) ([0a7dd94](https://github.com/googleapis/gax-nodejs/commit/0a7dd948573bd9553a0e9548e9ab92dbcfcb7414)) +* Replace proto-over-HTTP with REGAPIC ([#1471](https://github.com/googleapis/gax-nodejs/issues/1471)) ([4266f43](https://github.com/googleapis/gax-nodejs/commit/4266f43922d0d582b8eced11f4a21c98a8b451fe)) +* The return types for IAM service methods should be arrays, to match ReturnTuple ([#1001](https://github.com/googleapis/gax-nodejs/issues/1001)) ([48eed95](https://github.com/googleapis/gax-nodejs/commit/48eed955e7329f55f9427a7bc0656cfe2af395e8)) + ## [4.0.0](https://github.com/googleapis/gax-nodejs/compare/v3.6.0...v4.0.0) (2023-05-17) diff --git a/client-libraries.md b/client-libraries.md index 47d115672..d64cea055 100644 --- a/client-libraries.md +++ b/client-libraries.md @@ -99,31 +99,20 @@ binary tranport based on HTTP/2. It's Node.js implementation, #### HTTP/1.1 REST API mode -- `options.fallback`: `true`, `"rest"`, or `false`, use HTTP fallback mode. +- `options.fallback`: `true` or `false`, use HTTP fallback mode. Default value is `false`, unless the `window` object is defined. + For compatibility, you can pass any non-empty string, it will be considered + a `true` value. If you need to use the client library in non-Node.js environment or when gRPC cannot be used for any reason, you can use the HTTP/1.1 fallback mode. In this mode, a special browser-compatible transport implementation is used instead of -gRPC transport. - -There are two supported gRPC fallback modes: - -- set `options.fallback` to `"rest"`: the library will send and receive JSON - payload, HTTP/1.1 REST API endpoints will be used. This mode is recommended - for fallback. - -- set `options.fallback` to `true`: the library will send and receive serialized - protobuf payload to special endpoints accepting serialized protobufs over - HTTP/1.1. +gRPC transport. It will send and receive JSONs over HTTP. In browser context (if the `window` object is defined) the fallback mode is enabled automatically; set `options.fallback` to `false` if you need to override this behavior. -Note that `options.fallback` accepts boolean values (`true` and `false`) for -compatibility only. We recommend using `"rest"` to use HTTP/1.1 instead of gRPC. - ## Calling API methods In all examples below we assume that `client` is an instance of the client diff --git a/package.json b/package.json index f2e67a299..231ee212b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "google-gax", - "version": "4.0.0", + "version": "4.0.3", "description": "Google API Extensions", "main": "build/src/index.js", "types": "build/src/index.d.ts", @@ -10,50 +10,50 @@ "!build/src/**/*.map" ], "dependencies": { - "@grpc/grpc-js": "~1.8.0", + "@grpc/grpc-js": "~1.9.0", "@grpc/proto-loader": "^0.7.0", "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", - "google-auth-library": "^8.0.2", + "extend": "^3.0.2", + "google-auth-library": "^9.0.0", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.2.3", - "retry-request": "^5.0.0" + "proto3-json-serializer": "^2.0.0", + "protobufjs": "7.2.5", + "retry-request": "^6.0.0" }, "devDependencies": { - "@compodoc/compodoc": "1.1.19", + "@babel/plugin-proposal-private-methods": "^7.18.6", + "@compodoc/compodoc": "1.1.21", "@types/mocha": "^9.0.0", "@types/ncp": "^2.0.1", - "@types/node": "^18.0.0", + "@types/node": "^20.5.0", "@types/node-fetch": "^2.5.4", "@types/object-hash": "^3.0.0", "@types/proxyquire": "^1.3.28", "@types/pumpify": "^1.4.1", - "@types/rimraf": "^3.0.2", "@types/sinon": "^10.0.0", "@types/uglify-js": "^3.17.0", - "c8": "^7.0.0", + "c8": "^8.0.0", "codecov": "^3.1.0", "execa": "^5.0.0", - "gapic-tools": "^0.1.7", - "google-proto-files": "^3.0.0", - "gts": "^3.1.0", + "google-proto-files": "^4.0.0", + "gts": "^5.0.0", "linkinator": "^4.0.0", "long": "^4.0.0", "mkdirp": "^2.0.0", "mocha": "^9.0.0", "ncp": "^2.0.0", "null-loader": "^4.0.0", - "protobufjs-cli": "1.1.1", + "protobufjs-cli": "1.1.2", "proxyquire": "^2.0.1", "pumpify": "^2.0.0", - "rimraf": "^3.0.2", + "rimraf": "^5.0.1", "sinon": "^15.0.0", "stream-events": "^1.0.4", "ts-loader": "^8.0.0", - "typescript": "^4.6.4", + "typescript": "^5.1.6", "uglify-js": "^3.17.0", "walkdir": "^0.4.0", "webpack": "^4.0.0", @@ -74,7 +74,7 @@ "compile-http-protos": "pbjs -t static-module -r http_proto --keep-case google/api/http.proto -p ./protos > protos/http.js && pbts protos/http.js -o protos/http.d.ts", "compile-showcase-proto": "pbjs -t json google/showcase/v1beta1/echo.proto google/showcase/v1beta1/identity.proto google/showcase/v1beta1/messaging.proto google/showcase/v1beta1/testing.proto -p ./protos > test/fixtures/google-gax-packaging-test-app/protos/protos.json && pbjs -t static-module -r showcase_protos google/showcase/v1beta1/echo.proto google/showcase/v1beta1/identity.proto google/showcase/v1beta1/messaging.proto google/showcase/v1beta1/testing.proto -p ./protos > test/fixtures/google-gax-packaging-test-app/protos/protos.js && pbts test/fixtures/google-gax-packaging-test-app/protos/protos.js -o test/fixtures/google-gax-packaging-test-app/protos/protos.d.ts", "fix": "gts fix", - "prepare": "npm run compile && prepublishProtos . && mkdirp build/protos && cp -r protos/* build/protos/ && npm run minify-proto-json", + "prepare": "npm run compile && node ./build/tools/src/prepublish.js . && mkdirp build/protos && cp -r protos/* build/protos/ && npm run minify-proto-json", "system-test": "c8 mocha build/test/system-test --timeout 600000 && npm run test-application", "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", "docs-test": "linkinator docs", @@ -83,8 +83,8 @@ "test-application": "cd test/test-application && npm run prefetch && npm install && npm start", "prelint": "cd samples; npm link ../; npm install", "precompile": "gts clean", - "update-protos": "listProtos .", - "minify-proto-json": "minifyProtoJson" + "update-protos": "node ./build/tools/src/listProtos.js .", + "minify-proto-json": "node ./build/tools/src/minify.js" }, "repository": "googleapis/gax-nodejs", "keywords": [ diff --git a/release-please-config.json b/release-please-config.json index a6ee711d8..96b781df8 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -1,9 +1,8 @@ { - "initial-version": "0.1.0", - "packages": { - ".": {}, - "tools": {} - }, - "release-type": "node" - } - \ No newline at end of file + "initial-version": "0.1.0", + "packages": { + ".": {}, + "tools": {} + }, + "release-type": "node" +} \ No newline at end of file diff --git a/samples/package.json b/samples/package.json index 1f790787a..f73facf9b 100644 --- a/samples/package.json +++ b/samples/package.json @@ -14,10 +14,10 @@ "*.js" ], "dependencies": { - "google-gax": "^4.0.0" + "google-gax": "^4.0.3" }, "devDependencies": { - "c8": "^7.0.0", + "c8": "^8.0.0", "mocha": "^9.0.0" } } diff --git a/src/clientInterface.ts b/src/clientInterface.ts index 4eb54cf9a..46d280002 100644 --- a/src/clientInterface.ts +++ b/src/clientInterface.ts @@ -37,6 +37,7 @@ export interface ClientOptions clientConfig?: gax.ClientConfig; fallback?: boolean | 'rest' | 'proto'; apiEndpoint?: string; + newRetry?: boolean; } export interface Descriptors { diff --git a/src/createApiCall.ts b/src/createApiCall.ts index 51a40bd3b..1acd12fe1 100644 --- a/src/createApiCall.ts +++ b/src/createApiCall.ts @@ -28,7 +28,7 @@ import { SimpleCallbackFunction, } from './apitypes'; import {Descriptor} from './descriptor'; -import {CallOptions, CallSettings} from './gax'; +import {CallOptions, CallSettings, checkRetryOptions} from './gax'; import {retryable} from './normalCalls/retries'; import {addTimeoutArg} from './normalCalls/timeout'; import {StreamingApiCaller} from './streamingCalls/streamingApiCaller'; @@ -63,7 +63,6 @@ export function createApiCall( // function. Currently client librares are only calling this method with a // promise, but it will change. const funcPromise = typeof func === 'function' ? Promise.resolve(func) : func; - // the following apiCaller will be used for all calls of this function... const apiCaller = createAPICaller(settings, descriptor); @@ -72,9 +71,21 @@ export function createApiCall( callOptions?: CallOptions, callback?: APICallback ) => { - const thisSettings = settings.merge(callOptions); - let currentApiCaller = apiCaller; + const gaxStreamingRetries = (currentApiCaller as StreamingApiCaller) + .descriptor?.gaxStreamingRetries; + let thisSettings: CallSettings; + if (currentApiCaller instanceof StreamingApiCaller) { + // If Gax streaming retries are enabled, check settings passed at call time and convert parameters if needed + const thisSettingsTemp = checkRetryOptions( + callOptions, + gaxStreamingRetries + ); + thisSettings = settings.merge(thisSettingsTemp); + } else { + thisSettings = settings.merge(callOptions); + } + // special case: if bundling is disabled for this one call, // use default API caller instead if (settings.isBundling && !thisSettings.isBundling) { @@ -89,23 +100,38 @@ export function createApiCall( const streaming = (currentApiCaller as StreamingApiCaller).descriptor ?.streaming; + const retry = thisSettings.retry; - if ( - !streaming && - retry && - retry.retryCodes && - retry.retryCodes.length > 0 - ) { - retry.backoffSettings.initialRpcTimeoutMillis = - retry.backoffSettings.initialRpcTimeoutMillis || - thisSettings.timeout; - return retryable( - func, - thisSettings.retry!, - thisSettings.otherArgs as GRPCCallOtherArgs, - thisSettings.apiName + if (!streaming && retry && retry.getResumptionRequestFn) { + throw new Error( + 'Resumption strategy can only be used with server streaming retries' ); } + if (!streaming && retry && retry.retryCodesOrShouldRetryFn) { + if ( + retry.retryCodesOrShouldRetryFn instanceof Array && + retry.retryCodesOrShouldRetryFn.length > 0 + ) { + retry.backoffSettings.initialRpcTimeoutMillis = + retry.backoffSettings.initialRpcTimeoutMillis || + thisSettings.timeout; + return retryable( + func, + thisSettings.retry!, + thisSettings.otherArgs as GRPCCallOtherArgs, + thisSettings.apiName + ); + } else { + if ( + retry.retryCodesOrShouldRetryFn instanceof Function && + !streaming + ) { + throw new Error( + 'Using a function to determine retry eligibility is only supported with server streaming calls' + ); + } + } + } return addTimeoutArg( func, thisSettings.timeout, diff --git a/src/fallback.ts b/src/fallback.ts index 0f3046e66..cbc49dbff 100644 --- a/src/fallback.ts +++ b/src/fallback.ts @@ -35,7 +35,6 @@ import {GaxCall, GRPCCall} from './apitypes'; import {Descriptor, StreamDescriptor} from './descriptor'; import {createApiCall as _createApiCall} from './createApiCall'; import {FallbackServiceError} from './googleError'; -import * as fallbackProto from './fallbackProto'; import * as fallbackRest from './fallbackRest'; import {isNodeJS} from './featureDetection'; import {generateServiceStub} from './fallbackServiceStub'; @@ -94,7 +93,7 @@ export type AuthClient = export class GrpcClient { auth?: OAuth2Client | GoogleAuth; authClient?: AuthClient; - fallback: boolean | 'rest' | 'proto'; + fallback: boolean; grpcVersion: string; private static protoCache = new Map(); httpRules?: Array; @@ -119,7 +118,11 @@ export class GrpcClient { constructor( options: (GrpcClientOptions | {auth: OAuth2Client}) & { - fallback?: boolean | 'rest' | 'proto'; + /** + * Fallback mode to use instead of gRPC. + * A string is accepted for compatibility, all non-empty string values enable the HTTP REST fallback. + */ + fallback?: boolean | string; } = {} ) { if (!isNodeJS()) { @@ -135,7 +138,7 @@ export class GrpcClient { (options.auth as GoogleAuth) || new GoogleAuth(options as GoogleAuthOptions); } - this.fallback = options.fallback !== 'rest' ? 'proto' : 'rest'; + this.fallback = options.fallback ? true : false; this.grpcVersion = require('../../package.json').version; this.httpRules = (options as GrpcClientOptions).httpRules; this.numericEnums = (options as GrpcClientOptions).numericEnums ?? false; @@ -316,14 +319,8 @@ export class GrpcClient { servicePort = 443; } - const encoder = - this.fallback === 'rest' - ? fallbackRest.encodeRequest - : fallbackProto.encodeRequest; - const decoder = - this.fallback === 'rest' - ? fallbackRest.decodeResponse - : fallbackProto.decodeResponse; + const encoder = fallbackRest.encodeRequest; + const decoder = fallbackRest.decodeResponse; const serviceStub = generateServiceStub( methods, protocol, @@ -362,7 +359,7 @@ export class GrpcClient { export function lro(options: GrpcClientOptions) { options = Object.assign({scopes: []}, options); if (options.protoJson) { - options = Object.assign(options, {fallback: 'rest'}); + options = Object.assign(options, {fallback: true}); } const gaxGrpc = new GrpcClient(options); return new OperationsClientBuilder(gaxGrpc, options.protoJson); @@ -396,10 +393,10 @@ export function createApiCall( func: Promise | GRPCCall, settings: gax.CallSettings, descriptor?: Descriptor, - fallback?: boolean | 'proto' | 'rest' + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _fallback?: boolean | string // unused; for compatibility only ): GaxCall { if ( - (!fallback || fallback === 'rest') && descriptor && 'streaming' in descriptor && (descriptor as StreamDescriptor).type !== StreamType.SERVER_STREAMING @@ -410,14 +407,10 @@ export function createApiCall( ); }; } - if ( - (fallback === 'proto' || fallback === true) && // for legacy reasons, fallback === true means 'proto' - descriptor && - 'streaming' in descriptor - ) { + if (descriptor && 'streaming' in descriptor && !isNodeJS()) { return () => { throw new Error( - 'The gRPC-fallback (proto over HTTP) transport currently does not support streaming calls.' + 'Server streaming over the REST transport is only supported in Node.js.' ); }; } diff --git a/src/fallbackProto.ts b/src/fallbackProto.ts deleted file mode 100644 index bfaa5fc10..000000000 --- a/src/fallbackProto.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2021 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// proto-over-HTTP request encoding and decoding - -import {defaultToObjectOptions} from './fallback'; -import {FetchParameters} from './fallbackServiceStub'; -import {GoogleErrorDecoder} from './googleError'; - -export function encodeRequest( - rpc: protobuf.Method, - protocol: string, - servicePath: string, - servicePort: number, - request: {} -): FetchParameters { - const protoNamespaces: string[] = []; - let currNamespace = rpc.parent!; - while (currNamespace.name !== '') { - protoNamespaces.unshift(currNamespace.name); - currNamespace = currNamespace.parent!; - } - const protoServiceName = protoNamespaces.join('.'); - const rpcName = rpc.name; - - const headers: {[key: string]: string} = { - 'Content-Type': 'application/x-protobuf', - }; - - const method = 'POST'; - const requestMessage = rpc.resolvedRequestType!.fromObject(request); - const body = rpc.resolvedRequestType!.encode(requestMessage).finish(); - const url = `${protocol}://${servicePath}:${servicePort}/$rpc/${protoServiceName}/${rpcName}`; - - return { - method, - url, - headers, - body, - }; -} - -export function decodeResponse( - rpc: protobuf.Method, - ok: boolean, - response: Buffer | ArrayBuffer -): {} { - if (!ok) { - const statusDecoder = new GoogleErrorDecoder(); - const error = statusDecoder.decodeErrorFromBuffer(response); - throw error; - } - - const buffer = - response instanceof ArrayBuffer ? new Uint8Array(response) : response; - const message = rpc.resolvedResponseType!.decode(buffer); - return rpc.resolvedResponseType!.toObject(message, defaultToObjectOptions); -} diff --git a/src/gax.ts b/src/gax.ts index 09e5d86ed..8497d53d8 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -67,19 +67,42 @@ import {toLowerCamelCase} from './util'; /** * Per-call configurable settings for retrying upon transient failure. + * @implements {RetryOptionsType} * @typedef {Object} RetryOptions - * @property {String[]} retryCodes + * @property {String[] | (function)} retryCodesOrShouldRetryFn * @property {BackoffSettings} backoffSettings + * @property {(function)} getResumptionRequestFn */ export class RetryOptions { - retryCodes: number[]; + retryCodesOrShouldRetryFn: number[] | ((error: any) => boolean); backoffSettings: BackoffSettings; - constructor(retryCodes: number[], backoffSettings: BackoffSettings) { - this.retryCodes = retryCodes; + getResumptionRequestFn?: (response: any) => any; + constructor( + retryCodesOrShouldRetryFn: number[] | ((error: any) => boolean), + backoffSettings: BackoffSettings, + getResumptionRequestFn?: (response: any) => any + ) { + this.retryCodesOrShouldRetryFn = retryCodesOrShouldRetryFn; this.backoffSettings = backoffSettings; + this.getResumptionRequestFn = getResumptionRequestFn; } } +/** + * Per-call configurable settings for working with retry-request + * See the repo README for more about the parameters + * https://github.com/googleapis/retry-request + * Will be deprecated in a future release. Only relevant to server streaming calls + * @typedef {Object} RetryOptions + * @property {boolean} objectMode - when true utilizes object mode in streams + * @property {request} request - the request to retry + * @property {number} noResponseRetries - number of times to retry on no response + * @property {number} currentRetryAttempt - what # retry attempt retry-request is on + * @property {Function} shouldRetryFn - determines whether to retry, returns a boolean + * @property {number} maxRetryDelay - maximum retry delay in seconds + * @property {number} retryDelayMultiplier - multiplier to increase the delay in between completion of failed requests + * @property {number} totalTimeout - total timeout in seconds + */ export interface RetryRequestOptions { objectMode?: boolean; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -87,7 +110,10 @@ export interface RetryRequestOptions { retries?: number; noResponseRetries?: number; currentRetryAttempt?: number; - shouldRetryFn?: () => boolean; + shouldRetryFn?: (error: any) => boolean; + maxRetryDelay?: number; + retryDelayMultiplier?: number; + totalTimeout?: number; } /** @@ -209,31 +235,50 @@ export class CallSettings { let longrunning = this.longrunning; let apiName = this.apiName; let retryRequestOptions = this.retryRequestOptions; + // If a method-specific timeout is set in the service config, and the retry codes for that // method are non-null, then that timeout value will be used to // override backoff settings. - if ( - retry !== undefined && - retry !== null && - retry.retryCodes !== null && - retry.retryCodes.length > 0 - ) { - retry.backoffSettings.initialRpcTimeoutMillis = timeout; - retry.backoffSettings.maxRpcTimeoutMillis = timeout; - retry.backoffSettings.totalTimeoutMillis = timeout; + if (retry !== undefined && retry !== null) { + // if verify that the retry codes/retry function are not null or undefined + if ( + retry.retryCodesOrShouldRetryFn !== null && + retry.retryCodesOrShouldRetryFn !== undefined + ) { + // if it's an array of retry codes, make sure it has an element or check if it's a function + if ( + (retry.retryCodesOrShouldRetryFn instanceof Array && + retry.retryCodesOrShouldRetryFn.length > 0) || + retry.retryCodesOrShouldRetryFn instanceof Function + ) { + retry.backoffSettings.initialRpcTimeoutMillis = timeout; + retry.backoffSettings.maxRpcTimeoutMillis = timeout; + retry.backoffSettings.totalTimeoutMillis = timeout; + } + } } + // If the user provides a timeout to the method, that timeout value will be used // to override the backoff settings. if ('timeout' in options) { timeout = options.timeout!; - if ( - retry !== undefined && - retry !== null && - retry.retryCodes.length > 0 - ) { - retry.backoffSettings.initialRpcTimeoutMillis = timeout; - retry.backoffSettings.maxRpcTimeoutMillis = timeout; - retry.backoffSettings.totalTimeoutMillis = timeout; + if (retry !== undefined && retry !== null) { + // if verify that the retry codes/retry function are not null or undefined + if ( + retry.retryCodesOrShouldRetryFn !== null && + retry.retryCodesOrShouldRetryFn !== undefined + ) { + // if it's an array of retry codes, make sure it has an element or if it's a function + if ( + (retry.retryCodesOrShouldRetryFn instanceof Array && + retry.retryCodesOrShouldRetryFn.length > 0) || + retry.retryCodesOrShouldRetryFn instanceof Function + ) { + retry.backoffSettings.initialRpcTimeoutMillis = timeout; + retry.backoffSettings.maxRpcTimeoutMillis = timeout; + retry.backoffSettings.totalTimeoutMillis = timeout; + } + } } } if ('retry' in options) { @@ -262,7 +307,11 @@ export class CallSettings { isBundling = options.isBundling!; } - if ('maxRetries' in options) { + if ('maxRetries' in options && typeof options.maxRetries !== 'undefined') { + console.log( + 'removing timeout in favor of max retries', + retry!.backoffSettings!.totalTimeoutMillis + ); retry!.backoffSettings!.maxRetries = options.maxRetries; delete retry!.backoffSettings!.totalTimeoutMillis; } @@ -273,6 +322,7 @@ export class CallSettings { if ('apiName' in options) { apiName = options.apiName; } + // if ('retryRequestOptions' in options) { retryRequestOptions = options.retryRequestOptions; } @@ -292,23 +342,148 @@ export class CallSettings { } } +/** + * Validates passed retry options in preparation for eventual parameter deprecation + * converts retryRequestOptions to retryOptions + * then sets retryRequestOptions to null + * + * @param {CallOptions} options - a list of passed retry option + * @return {CallOptions} A new CallOptions object. + * + */ + +export function checkRetryOptions( + options?: CallOptions, + gaxStreamingRetries?: boolean +): CallOptions | undefined { + // options will be undefined if no CallOptions object is passed at call time + if (options) { + // if a user provided retry AND retryRequestOptions at call time, throw an error + if (gaxStreamingRetries) { + if ( + options.retry !== undefined && + options.retryRequestOptions !== undefined + ) { + throw new Error('Only one of retry or retryRequestOptions may be set'); + } else { + if (options.retryRequestOptions !== undefined) { + // // Retry settings + if (options.retryRequestOptions.objectMode) { + console.log( + 'objectMode override is not supported. It is set to true internally by default in gax.' + ); + } + if (options.retryRequestOptions.noResponseRetries) { + console.log( + 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.' + ); + } + if (options.retryRequestOptions.currentRetryAttempt) { + console.log( + 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.' + ); + } + let retryCodesOrShouldRetryFn; + + if (options.retryRequestOptions.shouldRetryFn) { + retryCodesOrShouldRetryFn = + options.retryRequestOptions.shouldRetryFn; + } else { + // default to retry code 14 per AIP-194 + retryCodesOrShouldRetryFn = [14]; + } + + //Backoff settings + if ( + options.retryRequestOptions.retries !== null && + options.retryRequestOptions !== undefined + ) { + // don't want to just check for truthiness here in case it's 0 + options.maxRetries = options.retryRequestOptions.retries; + } + // create a default backoff settings object in case the user didn't provide overrides for everything + const backoffSettings = createDefaultBackoffSettings(); + let maxRetryDelayMillis; + let totalTimeoutMillis; + // maxRetryDelay - this is in seconds, need to convert to milliseconds + if (options.retryRequestOptions.maxRetryDelay) { + maxRetryDelayMillis = + options.retryRequestOptions.maxRetryDelay * 1000; + } + // retryDelayMultiplier - should be a one to one mapping to retryDelayMultiplier + const retryDelayMultiplier = + options.retryRequestOptions.retryDelayMultiplier; + // totalTimeout - this is in seconds and needs to be converted to milliseconds and the totalTimeoutMillis parameter + if (options.retryRequestOptions.totalTimeout) { + totalTimeoutMillis = + options.retryRequestOptions.totalTimeout * 1000; + } + + // for the variables the user wants to override, override in the backoff settings object we made + if (maxRetryDelayMillis) { + backoffSettings.maxRetryDelayMillis = maxRetryDelayMillis; + } + if (retryDelayMultiplier) { + backoffSettings.retryDelayMultiplier = retryDelayMultiplier; + } + if (totalTimeoutMillis) { + backoffSettings.totalTimeoutMillis = totalTimeoutMillis; + } + + const convertedRetryOptions = createRetryOptions( + retryCodesOrShouldRetryFn, + backoffSettings + ); + options.retry = convertedRetryOptions; + delete options.retryRequestOptions; // completely remove them to avoid any further confusion + warn( + 'retry_request_options', + 'retryRequestOptions will be deprecated in a future release. Please use retryOptions to pass retry options at call time', + 'DeprecationWarning' + ); + } + } + } else { + // if user is opted into legacy settings but has passed retry settings, let them know there might be an issue if it's a streaming call + if (options.retry !== undefined) { + warn( + 'legacy_streaming_retry_behavior', + 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', + 'DeprecationWarning' + ); + } + if (options.retryRequestOptions !== undefined) { + warn( + 'legacy_streaming_retry_request_behavior', + 'Legacy streaming retry behavior will not honor retryRequestOptions passed at call time. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will convert retryRequestOptions to retry parameters by default in future releases.', + 'DeprecationWarning' + ); + } + } + } + return options; +} + /** * Per-call configurable settings for retrying upon transient failure. * - * @param {number[]} retryCodes - a list of Google API canonical error codes + * @param {number[] | function} retryCodesOrShouldRetryFn - a list of Google API canonical error codes OR a function that returns a boolean to determine retry behavior * upon which a retry should be attempted. * @param {BackoffSettings} backoffSettings - configures the retry * exponential backoff algorithm. + * @param {function} getResumptionRequestFn - a function with a resumption strategy - only used with server streaming retries * @return {RetryOptions} A new RetryOptions object. * */ export function createRetryOptions( - retryCodes: number[], - backoffSettings: BackoffSettings + retryCodesOrShouldRetryFn: number[] | ((response: any) => boolean), + backoffSettings: BackoffSettings, + getResumptionRequestFn?: (response: any) => any ): RetryOptions { return { - retryCodes, + retryCodesOrShouldRetryFn, backoffSettings, + getResumptionRequestFn, }; } @@ -479,7 +654,7 @@ function constructRetry( return null; } - let codes: number[] | null = null; + let codes: number[] | null = null; // this is one instance where it will NOT be an array OR a function because we do not allow shouldRetryFn in the client if (retryCodes && 'retry_codes_name' in methodConfig) { const retryCodesName = methodConfig['retry_codes_name']; codes = (retryCodes[retryCodesName!] || []).map(name => { @@ -526,16 +701,30 @@ function mergeRetryOptions( return null; } - if (!overrides.retryCodes && !overrides.backoffSettings) { + if ( + !overrides.retryCodesOrShouldRetryFn && + !overrides.backoffSettings && + !overrides.getResumptionRequestFn + ) { return retry; } - const codes = overrides.retryCodes ? overrides.retryCodes : retry.retryCodes; + const codesOrFunction = overrides.retryCodesOrShouldRetryFn + ? overrides.retryCodesOrShouldRetryFn + : retry.retryCodesOrShouldRetryFn; const backoffSettings = overrides.backoffSettings ? overrides.backoffSettings : retry.backoffSettings; - return createRetryOptions(codes!, backoffSettings!); + + const getResumptionRequestFn = overrides.getResumptionRequestFn + ? overrides.getResumptionRequestFn + : retry.getResumptionRequestFn; + return createRetryOptions( + codesOrFunction!, + backoffSettings!, + getResumptionRequestFn! + ); } export interface ServiceConfig { diff --git a/src/iamService.ts b/src/iamService.ts index 2a3df9ba6..758536775 100644 --- a/src/iamService.ts +++ b/src/iamService.ts @@ -203,7 +203,7 @@ export class IamClient { getIamPolicy( request: protos.google.iam.v1.GetIamPolicyRequest, options?: gax.CallOptions - ): Promise; + ): Promise<[protos.google.iam.v1.Policy]>; getIamPolicy( request: protos.google.iam.v1.GetIamPolicyRequest, options: gax.CallOptions, @@ -235,7 +235,7 @@ export class IamClient { protos.google.iam.v1.GetIamPolicyRequest | null | undefined, {} | null | undefined > - ): Promise { + ): Promise<[protos.google.iam.v1.Policy]> { let options: gax.CallOptions; if (optionsOrCallback instanceof Function && callback === undefined) { callback = optionsOrCallback as unknown as Callback< @@ -262,7 +262,7 @@ export class IamClient { setIamPolicy( request: protos.google.iam.v1.SetIamPolicyRequest, options?: gax.CallOptions - ): Promise; + ): Promise<[protos.google.iam.v1.Policy]>; setIamPolicy( request: protos.google.iam.v1.SetIamPolicyRequest, options: gax.CallOptions, @@ -294,7 +294,7 @@ export class IamClient { protos.google.iam.v1.SetIamPolicyRequest | null | undefined, {} | null | undefined > - ): Promise { + ): Promise<[protos.google.iam.v1.Policy]> { let options: gax.CallOptions; if (optionsOrCallback instanceof Function && callback === undefined) { callback = optionsOrCallback as unknown as Callback< @@ -320,7 +320,7 @@ export class IamClient { testIamPermissions( request: protos.google.iam.v1.TestIamPermissionsRequest, options?: gax.CallOptions - ): Promise; + ): Promise<[protos.google.iam.v1.TestIamPermissionsResponse]>; testIamPermissions( request: protos.google.iam.v1.TestIamPermissionsRequest, callback: Callback< @@ -352,7 +352,7 @@ export class IamClient { protos.google.iam.v1.TestIamPermissionsRequest | null | undefined, {} | null | undefined > - ): Promise { + ): Promise<[protos.google.iam.v1.TestIamPermissionsResponse]> { let options: gax.CallOptions; if (optionsOrCallback instanceof Function && callback === undefined) { callback = optionsOrCallback as unknown as Callback< @@ -408,7 +408,7 @@ export interface IamClient { protos.google.iam.v1.GetIamPolicyRequest | null | undefined, {} | null | undefined > - ): Promise; + ): Promise<[protos.google.iam.v1.Policy]>; setIamPolicy(request: protos.google.iam.v1.SetIamPolicyRequest): void; setIamPolicy( request: protos.google.iam.v1.SetIamPolicyRequest, @@ -424,7 +424,7 @@ export interface IamClient { protos.google.iam.v1.SetIamPolicyRequest | null | undefined, {} | null | undefined > - ): Promise; + ): Promise<[protos.google.iam.v1.Policy]>; testIamPermissions( request: protos.google.iam.v1.TestIamPermissionsRequest ): void; @@ -442,5 +442,5 @@ export interface IamClient { protos.google.iam.v1.TestIamPermissionsRequest | null | undefined, {} | null | undefined > - ): Promise; + ): Promise<[protos.google.iam.v1.TestIamPermissionsResponse]>; } diff --git a/src/normalCalls/retries.ts b/src/normalCalls/retries.ts index 2a6e6ab52..e00e5087d 100644 --- a/src/normalCalls/retries.ts +++ b/src/normalCalls/retries.ts @@ -107,7 +107,11 @@ export function retryable( return; } canceller = null; - if (retry.retryCodes.indexOf(err!.code!) < 0) { + if ( + retry.retryCodesOrShouldRetryFn !== undefined && + retry.retryCodesOrShouldRetryFn instanceof Array && + retry.retryCodesOrShouldRetryFn.indexOf(err!.code!) < 0 + ) { err.note = 'Exception occurred in retry method that was ' + 'not classified as transient'; diff --git a/src/normalCalls/timeout.ts b/src/normalCalls/timeout.ts index b1790d854..f703e3b14 100644 --- a/src/normalCalls/timeout.ts +++ b/src/normalCalls/timeout.ts @@ -17,6 +17,7 @@ import { GRPCCall, GRPCCallOtherArgs, + ServerStreamingCall, SimpleCallbackFunction, UnaryCall, } from '../apitypes'; @@ -54,3 +55,22 @@ export function addTimeoutArg( return (func as UnaryCall)(argument, metadata!, options, callback); }; } + +export function addServerTimeoutArg( + func: GRPCCall, + timeout: number, + otherArgs: GRPCCallOtherArgs, + abTests?: {} +): SimpleCallbackFunction { + // TODO: this assumes the other arguments consist of metadata and options, + // which is specific to gRPC calls. Remove the hidden dependency on gRPC. + return argument => { + const now = new Date(); + const options = otherArgs.options || {}; + options.deadline = new Date(now.getTime() + timeout); + const metadata = otherArgs.metadataBuilder + ? otherArgs.metadataBuilder(abTests, otherArgs.headers || {}) + : null; + return (func as ServerStreamingCall)(argument, metadata!, options); + }; +} diff --git a/src/paginationCalls/pageDescriptor.ts b/src/paginationCalls/pageDescriptor.ts index a4bf867b1..6bc0a9c46 100644 --- a/src/paginationCalls/pageDescriptor.ts +++ b/src/paginationCalls/pageDescriptor.ts @@ -81,7 +81,11 @@ export class PageDescriptor implements Descriptor { // emit full api response with every page. stream.emit('response', apiResp); for (let i = 0; i < resources.length; ++i) { - if ((stream as any)._readableState.ended) { + // TODO: rewrite without accessing stream internals + if ( + (stream as unknown as {_readableState: {ended: boolean}}) + ._readableState.ended + ) { return; } if (resources[i] === null) { @@ -93,7 +97,11 @@ export class PageDescriptor implements Descriptor { stream.end(); } } - if ((stream as any)._readableState.ended) { + // TODO: rewrite without accessing stream internals + if ( + (stream as unknown as {_readableState: {ended: boolean}})._readableState + .ended + ) { return; } if (!next) { diff --git a/src/protosList.json b/src/protosList.json index 3990c90fc..631558bab 100644 --- a/src/protosList.json +++ b/src/protosList.json @@ -1,5 +1,7 @@ [ "google/api/annotations.proto", + "google/api/apikeys/v2/apikeys.proto", + "google/api/apikeys/v2/resources.proto", "google/api/auth.proto", "google/api/backend.proto", "google/api/billing.proto", @@ -45,6 +47,7 @@ "google/api/servicecontrol/v1/operation.proto", "google/api/servicecontrol/v1/quota_controller.proto", "google/api/servicecontrol/v1/service_controller.proto", + "google/api/servicecontrol/v2/service_controller.proto", "google/api/servicemanagement/v1/resources.proto", "google/api/servicemanagement/v1/servicemanager.proto", "google/api/serviceusage/v1/resources.proto", @@ -77,11 +80,14 @@ "google/monitoring/v3/query_service.proto", "google/monitoring/v3/service.proto", "google/monitoring/v3/service_service.proto", + "google/monitoring/v3/snooze.proto", + "google/monitoring/v3/snooze_service.proto", "google/monitoring/v3/span_context.proto", "google/monitoring/v3/uptime.proto", "google/monitoring/v3/uptime_service.proto", "google/protobuf/any.proto", "google/protobuf/api.proto", + "google/protobuf/bridge/message_set.proto", "google/protobuf/compiler/plugin.proto", "google/protobuf/compiler/ruby/ruby_generated_code.proto", "google/protobuf/compiler/ruby/ruby_generated_code_proto2.proto", @@ -102,7 +108,9 @@ "google/protobuf/wrappers.proto", "google/rpc/code.proto", "google/rpc/context/attribute_context.proto", + "google/rpc/context/audit_context.proto", "google/rpc/error_details.proto", + "google/rpc/http.proto", "google/rpc/status.proto", "google/type/calendar_period.proto", "google/type/color.proto", diff --git a/src/streamArrayParser.ts b/src/streamArrayParser.ts index 697dc2521..7aa03c1f4 100644 --- a/src/streamArrayParser.ts +++ b/src/streamArrayParser.ts @@ -116,7 +116,7 @@ export class StreamArrayParser extends Transform { chunk.slice(objectStart, curIndex + 1), ]); try { - // HTTP reponse.ok is true. + // HTTP response.ok is true. const msgObj = decodeResponse(this.rpc, true, objBuff); this.push(msgObj); } catch (err) { diff --git a/src/streamingCalls/streamDescriptor.ts b/src/streamingCalls/streamDescriptor.ts index d23f80219..569374d31 100644 --- a/src/streamingCalls/streamDescriptor.ts +++ b/src/streamingCalls/streamDescriptor.ts @@ -17,7 +17,6 @@ import {APICaller} from '../apiCaller'; import {Descriptor} from '../descriptor'; import {CallSettings} from '../gax'; - import {StreamType} from './streaming'; import {StreamingApiCaller} from './streamingApiCaller'; @@ -28,19 +27,23 @@ export class StreamDescriptor implements Descriptor { type: StreamType; streaming: boolean; // needed for browser support rest?: boolean; + gaxStreamingRetries?: boolean; - constructor(streamType: StreamType, rest?: boolean) { + constructor( + streamType: StreamType, + rest?: boolean, + gaxStreamingRetries?: boolean + ) { this.type = streamType; this.streaming = true; this.rest = rest; + this.gaxStreamingRetries = gaxStreamingRetries; } getApiCaller(settings: CallSettings): APICaller { // Right now retrying does not work with gRPC-streaming, because retryable // assumes an API call returns an event emitter while gRPC-streaming methods // return Stream. - // TODO: support retrying. - settings.retry = null; return new StreamingApiCaller(this); } } diff --git a/src/streamingCalls/streaming.ts b/src/streamingCalls/streaming.ts index ec3192160..e5b18ecf6 100644 --- a/src/streamingCalls/streaming.ts +++ b/src/streamingCalls/streaming.ts @@ -24,8 +24,11 @@ import { GRPCCallResult, SimpleCallbackFunction, } from '../apitypes'; -import {RetryRequestOptions} from '../gax'; +import {RetryOptions, RetryRequestOptions} from '../gax'; import {GoogleError} from '../googleError'; +import {streamingRetryRequest} from '../streamingRetryRequest'; +import {Status} from '../status'; +import {warn} from '../warnings'; // eslint-disable-next-line @typescript-eslint/no-var-requires const duplexify: DuplexifyConstructor = require('duplexify'); @@ -84,6 +87,11 @@ export class StreamProxy extends duplexify implements GRPCCallResult { stream?: CancellableStream; private _responseHasSent: boolean; rest?: boolean; + new_retry?: boolean; + apiCall?: SimpleCallbackFunction; + argument?: {}; + prevDeadline?: number; + retries?: number = 0; /** * StreamProxy is a proxy to gRPC-streaming method. * @@ -92,7 +100,12 @@ export class StreamProxy extends duplexify implements GRPCCallResult { * @param {StreamType} type - the type of gRPC stream. * @param {ApiCallback} callback - the callback for further API call. */ - constructor(type: StreamType, callback: APICallback, rest?: boolean) { + constructor( + type: StreamType, + callback: APICallback, + rest?: boolean, + new_retry?: boolean + ) { super(undefined, undefined, { objectMode: true, readable: type !== StreamType.CLIENT_STREAMING, @@ -103,6 +116,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { this._isCancelCalled = false; this._responseHasSent = false; this.rest = rest; + this.new_retry = new_retry; } cancel() { @@ -113,10 +127,192 @@ export class StreamProxy extends duplexify implements GRPCCallResult { } } + retry(stream: CancellableStream, retry: RetryOptions) { + let retryArgument = this.argument!; + + if ( + typeof retry.getResumptionRequestFn! === 'function' && + retry.getResumptionRequestFn!(this.argument) + ) { + retryArgument = retry.getResumptionRequestFn!(this.argument); + } + + this.resetStreams(stream); + + const new_stream = this.apiCall!( + retryArgument, + this._callback + ) as CancellableStream; + this.stream = new_stream; + + const retryStream = this.streamHandoffHelper(new_stream, retry); + if (retryStream !== undefined) { + return retryStream; + } + return new_stream; + } + + /** + * Helper function to handle total timeout + max retry check for server streaming retries + * @param {number} deadline - the current retry deadline + * @param {number} maxRetries - maximum total number of retries + * @param {number} totalTimeoutMillis - total timeout in milliseconds + */ + timeoutAndMaxRetryCheck( + deadline: number, + maxRetries: number, + totalTimeoutMillis: number + ): void { + const now = new Date(); + + if ( + this.prevDeadline! !== undefined && + deadline && + now.getTime() >= this.prevDeadline + ) { + const error = new GoogleError( + `Total timeout of API exceeded ${totalTimeoutMillis} milliseconds before any response was received.` + ); + error.code = Status.DEADLINE_EXCEEDED; + this.emit('error', error); + + this.destroy(error); + // Without throwing error you get unhandled error since we are returning a new stream + // There might be a better way to do this + throw error; + } + + if (this.retries && this.retries > maxRetries) { + const error = new GoogleError( + 'Exceeded maximum number of retries before any ' + + 'response was received' + ); + error.code = Status.DEADLINE_EXCEEDED; + this.emit('error', error); + + this.destroy(error); + throw error; + } + } + + /** + * Error handler for server streaming retries + * @param {CancellableStream} stream - the stream being retried + * @param {RetryOptions} retry - Configures the exceptions upon which the + * function should retry, and the parameters to the exponential backoff retry + * algorithm. + * @param {Error} error - error to handle + */ + streamHandoffErrorHandler( + stream: CancellableStream, + retry: RetryOptions, + error: Error + ): void { + let retryStream = this.stream; + const delayMult = retry.backoffSettings.retryDelayMultiplier; + const maxDelay = retry.backoffSettings.maxRetryDelayMillis; + const timeoutMult = retry.backoffSettings.rpcTimeoutMultiplier; + const maxTimeout = retry.backoffSettings.maxRpcTimeoutMillis; + + let delay = retry.backoffSettings.initialRetryDelayMillis; + let timeout = retry.backoffSettings.initialRpcTimeoutMillis; + let now = new Date(); + let deadline = 0; + + if (retry.backoffSettings.totalTimeoutMillis) { + deadline = now.getTime() + retry.backoffSettings.totalTimeoutMillis; + } + const maxRetries = retry.backoffSettings.maxRetries!; + + this.timeoutAndMaxRetryCheck( + deadline, + maxRetries, + retry.backoffSettings.totalTimeoutMillis! + ); + + this.retries!++; + + const e = GoogleError.parseGRPCStatusDetails(error); + let shouldRetry = this.defaultShouldRetry(e!, retry); + if (typeof retry.retryCodesOrShouldRetryFn! === 'function') { + shouldRetry = retry.retryCodesOrShouldRetryFn!(e!); + } + + if (shouldRetry) { + const toSleep = Math.random() * delay; + setTimeout(() => { + now = new Date(); + delay = Math.min(delay * delayMult, maxDelay); + const timeoutCal = timeout && timeoutMult ? timeout * timeoutMult : 0; + const rpcTimeout = maxTimeout ? maxTimeout : 0; + this.prevDeadline = deadline; + const newDeadline = deadline ? deadline - now.getTime() : 0; + timeout = Math.min(timeoutCal, rpcTimeout, newDeadline); + }, toSleep); + } else { + const newError = new GoogleError( + 'Exception occurred in retry method that was ' + + 'not classified as transient' + ); + newError.code = Status.INVALID_ARGUMENT; + this.emit('error', newError); + this.destroy(newError); + return; + } + + if (maxRetries && deadline!) { + const newError = new GoogleError( + 'Cannot set both totalTimeoutMillis and maxRetries ' + + 'in backoffSettings.' + ); + newError.code = Status.INVALID_ARGUMENT; + this.emit('error', newError); + + this.destroy(newError); + } else { + retryStream = this.retry(stream, retry); + this.stream = retryStream; + return; + } + } + + /** + * Used during server streaming retries to handle + * event forwarding, errors, and/or stream closure + * @param {CancellableStream} stream - the stream that we're doing the retry on + * @param {RetryOptions} retry - Configures the exceptions upon which the + * function should retry, and the parameters to the exponential backoff retry + * algorithm. + */ + streamHandoffHelper(stream: CancellableStream, retry: RetryOptions): void { + let enteredError = false; + const eventsToForward = ['metadata', 'response', 'status', 'data']; + eventsToForward.forEach(event => { + stream.on(event, this.emit.bind(this, event)); + }); + + stream.on('error', error => { + enteredError = true; + this.streamHandoffErrorHandler(stream, retry, error); + }); + + stream.on('end', () => { + if (!enteredError) { + enteredError = true; + this.emit('end'); + this.cancel(); + } + }); + } + /** * Forward events from an API request stream to the user's stream. * @param {Stream} stream - The API request stream. + * @param {RetryOptions} retry - Configures the exceptions upon which the + * function should retry, and the parameters to the exponential backoff retry + * algorithm. */ + forwardEvents(stream: Stream) { const eventsToForward = ['metadata', 'response', 'status']; eventsToForward.forEach(event => { @@ -158,21 +354,169 @@ export class StreamProxy extends duplexify implements GRPCCallResult { }); } + defaultShouldRetry(error: GoogleError, retry: RetryOptions) { + if ( + retry.retryCodesOrShouldRetryFn instanceof Array && + retry.retryCodesOrShouldRetryFn.indexOf(error!.code!) < 0 + ) { + return false; + } + return true; + } + + /** + * Forward events from an API request stream to the user's stream. + * @param {Stream} stream - The API request stream. + * @param {RetryOptions} retry - Configures the exceptions upon which the + * function eshould retry, and the parameters to the exponential backoff retry + * algorithm. + */ + forwardEventsNewImplementation( + stream: CancellableStream, + retry: RetryOptions + ): CancellableStream | undefined { + let retryStream = this.stream; + const eventsToForward = ['metadata', 'response', 'status']; + eventsToForward.forEach(event => { + stream.on(event, this.emit.bind(this, event)); + }); + // gRPC is guaranteed emit the 'status' event but not 'metadata', and 'status' is the last event to emit. + // Emit the 'response' event if stream has no 'metadata' event. + // This avoids the stream swallowing the other events, such as 'end'. + stream.on('status', () => { + if (!this._responseHasSent) { + stream.emit('response', { + code: 200, + details: '', + message: 'OK', + }); + } + }); + + // We also want to supply the status data as 'response' event to support + // the behavior of google-cloud-node expects. + // see: + // https://github.com/GoogleCloudPlatform/google-cloud-node/pull/1775#issuecomment-259141029 + // https://github.com/GoogleCloudPlatform/google-cloud-node/blob/116436fa789d8b0f7fc5100b19b424e3ec63e6bf/packages/common/src/grpc-service.js#L355 + stream.on('metadata', metadata => { + // Create a response object with succeeds. + // TODO: unify this logic with the decoration of gRPC response when it's + // added. see: https://github.com/googleapis/gax-nodejs/issues/65 + stream.emit('response', { + code: 200, + details: '', + message: 'OK', + metadata, + }); + this._responseHasSent = true; + }); + + stream.on('error', error => { + const timeout = retry.backoffSettings.totalTimeoutMillis; + const maxRetries = retry.backoffSettings.maxRetries!; + if ((maxRetries && maxRetries > 0) || (timeout && timeout > 0)) { + const e = GoogleError.parseGRPCStatusDetails(error); + let shouldRetry = this.defaultShouldRetry(e!, retry); + if (typeof retry.retryCodesOrShouldRetryFn! === 'function') { + shouldRetry = retry.retryCodesOrShouldRetryFn!(e!); + } + + if (shouldRetry) { + if (maxRetries && timeout!) { + const newError = new GoogleError( + 'Cannot set both totalTimeoutMillis and maxRetries ' + + 'in backoffSettings.' + ); + newError.code = Status.INVALID_ARGUMENT; + this.emit('error', newError); + this.destroy(newError); + return; + } else { + retryStream = this.retry(stream, retry); + this.stream = retryStream; + return retryStream; + } + } else { + const newError = new GoogleError( + 'Exception occurred in retry method that was ' + + 'not classified as transient' + ); + newError.code = Status.INVALID_ARGUMENT; + this.emit('error', newError); + this.destroy(newError); + return; + } + } else { + return GoogleError.parseGRPCStatusDetails(error); + } + }); + return retryStream; + } + + resetStreams(requestStream: CancellableStream) { + if (requestStream) { + requestStream.cancel && requestStream.cancel(); + + if (requestStream.destroy) { + requestStream.destroy(); + } else if (requestStream.end) { + requestStream.end(); + } + } + } + /** * Specifies the target stream. * @param {ApiCall} apiCall - the API function to be called. * @param {Object} argument - the argument to be passed to the apiCall. + * @param {RetryOptions} retry - Configures the exceptions upon which the + * function eshould retry, and the parameters to the exponential backoff retry + * algorithm. */ setStream( apiCall: SimpleCallbackFunction, argument: {}, - retryRequestOptions: RetryRequestOptions = {} + retryRequestOptions: RetryRequestOptions = {}, + retry: RetryOptions ) { + this.apiCall = apiCall; + this.argument = argument; + if (this.type === StreamType.SERVER_STREAMING) { if (this.rest) { const stream = apiCall(argument, this._callback) as CancellableStream; this.stream = stream; this.setReadable(stream); + } else if (this.new_retry) { + warn( + 'gax_server_streaming_retries', + 'You are using the new experimental gax native server streaming retry implementation', + 'ExperimentalWarning' + ); + const retryStream = streamingRetryRequest( + null, + { + objectMode: true, + request: () => { + if (this._isCancelCalled) { + if (this.stream) { + this.stream.cancel(); + } + return; + } + const stream = apiCall( + argument, + this._callback + ) as CancellableStream; + this.stream = stream; + this.stream = this.forwardEventsNewImplementation(stream, retry); + return this.stream; + }, + }, + null + ); + + this.setReadable(retryStream); } else { const retryStream = retryRequest(null, { objectMode: true, @@ -203,7 +547,12 @@ export class StreamProxy extends duplexify implements GRPCCallResult { const stream = apiCall(argument, this._callback) as CancellableStream; this.stream = stream; - this.forwardEvents(stream); + + if (this.new_retry) { + this.forwardEventsNewImplementation(stream, retry); + } else { + this.forwardEvents(stream); + } if (this.type === StreamType.CLIENT_STREAMING) { this.setWritable(stream); diff --git a/src/streamingCalls/streamingApiCaller.ts b/src/streamingCalls/streamingApiCaller.ts index 6f8dc3634..68ddb9855 100644 --- a/src/streamingCalls/streamingApiCaller.ts +++ b/src/streamingCalls/streamingApiCaller.ts @@ -47,7 +47,8 @@ export class StreamingApiCaller implements APICaller { return new StreamProxy( this.descriptor.type, callback, - this.descriptor.rest + this.descriptor.rest, + this.descriptor.gaxStreamingRetries ); } @@ -85,7 +86,12 @@ export class StreamingApiCaller implements APICaller { settings: CallSettings, stream: StreamProxy ) { - stream.setStream(apiCall, argument, settings.retryRequestOptions); + stream.setStream( + apiCall, + argument, + settings.retryRequestOptions, + settings.retry! + ); } fail(stream: CancellableStream, err: Error) { diff --git a/src/streamingRetryRequest.ts b/src/streamingRetryRequest.ts new file mode 100644 index 000000000..0e32ff70e --- /dev/null +++ b/src/streamingRetryRequest.ts @@ -0,0 +1,205 @@ +// Copyright 2023 Google LLC + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const {PassThrough} = require('stream'); +const extend = require('extend'); + +const DEFAULTS = { + /* + Max # of retries + */ + maxRetries: 2, + + /* + The maximum time to delay in seconds. If retryDelayMultiplier results in a + delay greater than maxRetryDelay, retries should delay by maxRetryDelay + seconds instead. + */ + maxRetryDelayMillis: 64000, + + /* + The multiplier by which to increase the delay time between the completion of + failed requests, and the initiation of the subsequent retrying request. + */ + retryDelayMultiplier: 2, + + /* + The length of time to keep retrying in seconds. The last sleep period will + be shortened as necessary, so that the last retry runs at deadline (and not + considerably beyond it). The total time starting from when the initial + request is sent, after which an error will be returned, regardless of the + retrying attempts made meanwhile. + */ + totalTimeoutMillis: 600000, + + /* + The initial delay time, in milliseconds, between the completion of the first + failed request and the initiation of the first retrying request. + */ + initialRetryDelayMillis: 60, + + /* + Initial timeout parameter to the request. + */ + initialRpcTimeoutMillis: 60, + + /* + Maximum timeout in milliseconds for a request. + When this value is reached, rpcTimeoutMulitplier will no + longer be used to increase the timeout. + */ + maxRpcTimeoutMillis: 60, + + /* + Multiplier by which to increase timeout parameter in + between failed requests. + */ + rpcTimeoutMultiplier: 2, + + /* + The number of retries that have occured. + */ + retries: 0, + + retryCodesOrShouldRetryFn: + [14] || + function (response: any) { + return undefined; + }, + + getResumptionRequestFn: function (response: any) { + return undefined; + }, +}; + +export function streamingRetryRequest( + requestOpts: any = null, + opts: any = null, + callback: any = null, + ...args: any +) { + const streamMode = typeof args[args.length - 1] !== 'function'; + + if (typeof opts === 'function') { + callback = opts; + } + + opts = extend({}, DEFAULTS, opts); + + if (typeof opts.request === 'undefined') { + try { + // eslint-disable-next-line node/no-unpublished-require + opts.request = require('request'); + } catch (e) { + throw new Error('A request library must be provided to retry-request.'); + } + } + + let numNoResponseAttempts = 0; + let streamResponseHandled = false; + + let retryStream: any; + let requestStream: any; + let delayStream: any; + + let activeRequest: {abort: () => void}; + const retryRequest = { + abort: function () { + if (activeRequest && activeRequest.abort) { + activeRequest.abort(); + } + }, + }; + + if (streamMode) { + retryStream = new PassThrough({objectMode: opts.objectMode}); + // retryStream.abort = resetStreams; + } + + makeRequest(); + + if (streamMode) { + return retryStream; + } else { + return retryRequest; + } + + function makeRequest() { + if (streamMode) { + streamResponseHandled = false; + + delayStream = new PassThrough({objectMode: opts.objectMode}); + requestStream = opts.request(requestOpts); + + setImmediate(() => { + retryStream.emit('request'); + }); + + requestStream + // gRPC via google-cloud-node can emit an `error` as well as a `response` + // Whichever it emits, we run with-- we can't run with both. That's what + // is up with the `streamResponseHandled` tracking. + .on('error', (err: any) => { + if (streamResponseHandled) { + return; + } + streamResponseHandled = true; + onResponse(err); + }) + .on('response', (resp: any, body: any) => { + if (streamResponseHandled) { + return; + } + + streamResponseHandled = true; + onResponse(null, resp, body); + }) + .on('complete', retryStream.emit.bind(retryStream, 'complete')); + + requestStream.pipe(delayStream); + } else { + activeRequest = opts.request(requestOpts, onResponse); + } + } + + function onResponse(err: any, response: any = null, body: any = null) { + // An error such as DNS resolution. + if (err) { + numNoResponseAttempts++; + + if (numNoResponseAttempts <= opts.maxRetries) { + makeRequest(); + } else { + if (streamMode) { + retryStream.emit('error', err); + } else { + callback(err, response, body); + } + } + + return; + } + + // No more attempts need to be made, just continue on. + if (streamMode) { + retryStream.emit('response', response); + delayStream.pipe(retryStream); + requestStream.on('error', (err: any) => { + retryStream.destroy(err); + }); + } else { + callback(err, response, body); + } + } +} diff --git a/test/browser-test/karma.conf.js b/test/browser-test/karma.conf.js index f34eacc0c..5754262b1 100644 --- a/test/browser-test/karma.conf.js +++ b/test/browser-test/karma.conf.js @@ -86,7 +86,9 @@ module.exports = function (config) { base: 'ChromeHeadless', // We must disable the Chrome sandbox when running Chrome inside Docker (Chrome's sandbox needs // more permissions than Docker allows by default) - flags: isDocker ? ['--no-sandbox'] : [], + flags: isDocker + ? ['--no-sandbox', '--disable-web-security'] + : ['--disable-web-security'], }, }, diff --git a/test/browser-test/package.json b/test/browser-test/package.json index f98e7d39b..5171769db 100644 --- a/test/browser-test/package.json +++ b/test/browser-test/package.json @@ -7,6 +7,9 @@ "files": [ "build/test" ], + "engines": { + "node": ">=14" + }, "license": "Apache-2.0", "keywords": [], "scripts": { diff --git a/test/browser-test/test/test.endtoend.ts b/test/browser-test/test/test.endtoend.ts index f07cb3213..1d9556dbc 100644 --- a/test/browser-test/test/test.endtoend.ts +++ b/test/browser-test/test/test.endtoend.ts @@ -41,7 +41,7 @@ describe('Run tests against gRPC server', () => { const opts = { auth: authStub as unknown as GoogleAuth, protocol: 'http', - port: 1337, + port: 7469, }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -87,8 +87,11 @@ describe('Run tests against gRPC server', () => { const words = ['nobody', 'ever', 'reads', 'test', 'input']; const request = { content: words.join(' '), + pageSize: 2, }; - assert.throws(() => client.expand(request)); + assert.throws(() => { + client.expand(request); + }); }); it('should be able to call paging calls', async () => { diff --git a/test/browser-test/test/test.grpc-fallback.ts b/test/browser-test/test/test.grpc-fallback.ts index d9c9133b3..6cf85e725 100644 --- a/test/browser-test/test/test.grpc-fallback.ts +++ b/test/browser-test/test/test.grpc-fallback.ts @@ -23,7 +23,6 @@ import {protobuf, GoogleAuth, fallback} from 'google-gax'; import {EchoClient} from 'showcase-echo-client'; import echoProtoJson = require('showcase-echo-client/build/protos/protos.json'); -import statusProtoJson = require('google-gax/build/protos/status.json'); const authStub = { getClient: async () => { @@ -193,12 +192,13 @@ describe('grpc-fallback', () => { it('should make a request', async () => { const client = new EchoClient(opts); const requestObject = {content: 'test-content'}; - const responseType = protos.lookupType('EchoResponse'); - const response = responseType.create(requestObject); // request === response for EchoService + const response = requestObject; // response == request for Echo const fakeFetch = sinon.fake.resolves({ ok: true, arrayBuffer: () => { - return Promise.resolve(responseType.encode(response).finish()); + return Promise.resolve( + new TextEncoder().encode(JSON.stringify(response)) + ); }, }); // eslint-disable-next-line no-undef @@ -233,8 +233,7 @@ describe('grpc-fallback', () => { fallback.routingHeader.fromParams({ abc: 'def', }); - const responseType = protos.lookupType('EchoResponse'); - const response = responseType.create(requestObject); + const response = requestObject; // eslint-disable-next-line no-undef const savedFetch = window.fetch; // @ts-ignore @@ -245,7 +244,9 @@ describe('grpc-fallback', () => { return Promise.resolve({ ok: true, arrayBuffer: () => { - return Promise.resolve(responseType.encode(response).finish()); + return Promise.resolve( + new TextEncoder().encode(JSON.stringify(response)) + ); }, }); }; @@ -257,20 +258,17 @@ describe('grpc-fallback', () => { it('should handle an error', done => { const requestObject = {content: 'test-content'}; - // example of an actual google.rpc.Status error message returned by Language - // API const expectedError = Object.assign(new Error('Error message'), { - code: 3, + code: 400, statusDetails: [], }); const fakeFetch = sinon.fake.resolves({ ok: false, arrayBuffer: () => { - const root = protobuf.Root.fromJSON(statusProtoJson); - const statusType = root.lookupType('google.rpc.Status'); - const statusMessage = statusType.fromObject(expectedError); - return Promise.resolve(statusType.encode(statusMessage).finish()); + return Promise.resolve( + new TextEncoder().encode(JSON.stringify(expectedError)) + ); }, }); // eslint-disable-next-line no-undef @@ -279,8 +277,6 @@ describe('grpc-fallback', () => { gaxGrpc.createStub(echoService, stubOptions).then(echoStub => { echoStub.echo(requestObject, {}, {}, (err?: Error) => { assert(err instanceof Error); - assert.strictEqual(err.message, '3 INVALID_ARGUMENT: Error message'); - assert.strictEqual(JSON.stringify(err), JSON.stringify(expectedError)); done(); }); }); diff --git a/test/showcase-echo-client/package.json b/test/showcase-echo-client/package.json index aab2afc7e..4e4a64b95 100644 --- a/test/showcase-echo-client/package.json +++ b/test/showcase-echo-client/package.json @@ -1,5 +1,5 @@ { - "name": "showcase-echo-client", + "name": "showcase-echo-client", "version": "0.1.0", "description": "Showcase client for Node.js", "repository": "googleapis/nodejs-showcase", @@ -21,46 +21,23 @@ "cloud", "google showcase", "showcase", - "compliance", "echo", - "identity", "sequence service" ], "scripts": { - "compile": "tsc -p . && cp -r protos build/", + "clean": "gts clean", + "compile": "tsc -p . && cp -r protos build/ && minifyProtoJson", "compile-protos": "compileProtos src", - "docs": "jsdoc -c .jsdoc.js", - "predocs-test": "npm run docs", - "docs-test": "linkinator docs", - "fix": "gts fix", - "lint": "gts check", - "prepare": "npm run compile-protos && npm run compile", - "prefetch": "rm -rf node_modules package-lock.json google-gax*.tgz && cd ../.. && npm pack && mv google-gax*.tgz test/showcase-echo-client/google-gax.tgz", - - "system-test": "c8 mocha build/system-test", - "test": "c8 mocha build/test" + "prefetch": "rm -rf node_modules package-lock.json google-gax*.tgz gapic-tools*.tgz && cd ../.. && npm pack && mv google-gax*.tgz test/showcase-echo-client/google-gax.tgz && cd tools && npm install && npm pack && mv gapic-tools*.tgz ../test/showcase-echo-client/gapic-tools.tgz", + "prepare": "npm run compile-protos && npm run compile" }, "dependencies": { "google-gax": "./google-gax.tgz" }, "devDependencies": { - "@types/mocha": "^10.0.1", - "@types/node": "^18.11.18", - "@types/sinon": "^10.0.13", - "c8": "^7.13.0", - "gts": "^3.1.1", - "jsdoc": "^4.0.2", - "jsdoc-fresh": "^2.0.1", - "jsdoc-region-tag": "^2.0.1", - "linkinator": "^4.1.2", - "mocha": "^10.2.0", - "null-loader": "^4.0.1", - "pack-n-play": "^1.0.0-2", - "sinon": "^15.0.1", - "ts-loader": "^8.4.0", - "typescript": "^4.8.4", - "webpack": "^4.46.0", - "webpack-cli": "^4.10.0" + "@types/node": "^16.0.0", + "gapic-tools": "./gapic-tools.tgz", + "typescript": "^4.5.5" }, "engines": { "node": ">=v14" diff --git a/test/showcase-echo-client/protos/google/showcase/v1beta1/echo.proto b/test/showcase-echo-client/protos/google/showcase/v1beta1/echo.proto index 6e9d78a37..3f79b4457 100644 --- a/test/showcase-echo-client/protos/google/showcase/v1beta1/echo.proto +++ b/test/showcase-echo-client/protos/google/showcase/v1beta1/echo.proto @@ -17,6 +17,7 @@ syntax = "proto3"; import "google/api/annotations.proto"; import "google/api/client.proto"; import "google/api/field_behavior.proto"; +import "google/api/routing.proto"; import "google/longrunning/operations.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; @@ -27,27 +28,63 @@ package google.showcase.v1beta1; option go_package = "github.com/googleapis/gapic-showcase/server/genproto"; option java_package = "com.google.showcase.v1beta1"; option java_multiple_files = true; +option ruby_package = "Google::Showcase::V1beta1"; // This service is used showcase the four main types of rpcs - unary, server // side streaming, client side streaming, and bidirectional streaming. This // service also exposes methods that explicitly implement server delay, and // paginated calls. Set the 'showcase-trailer' metadata key on any method -// to have the values echoed in the response trailers. +// to have the values echoed in the response trailers. Set the +// 'x-goog-request-params' metadata key on any method to have the values +// echoed in the response headers. service Echo { // This service is meant to only run locally on the port 7469 (keypad digits // for "show"). option (google.api.default_host) = "localhost:7469"; - // This method simply echos the request. This method is showcases unary rpcs. + // This method simply echoes the request. This method showcases unary RPCs. rpc Echo(EchoRequest) returns (EchoResponse) { option (google.api.http) = { post: "/v1beta1/echo:echo" body: "*" }; + option (google.api.routing) = { + routing_parameters{ + field: "header" + } + routing_parameters{ + field: "header" + path_template: "{routing_id=**}" + } + routing_parameters{ + field: "header" + path_template: "{table_name=regions/*/zones/*/**}" + } + routing_parameters{ + field: "header" + path_template: "{super_id=projects/*}/**" + } + routing_parameters{ + field: "header" + path_template: "{table_name=projects/*/instances/*/**}" + } + routing_parameters{ + field: "header" + path_template: "projects/*/{instance_id=instances/*}/**" + } + routing_parameters{ + field: "other_header" + path_template: "{baz=**}" + } + routing_parameters{ + field: "other_header" + path_template: "{qux=projects/*}/**" + } + }; } - // This method split the given content into words and will pass each word back - // through the stream. This method showcases server-side streaming rpcs. + // This method splits the given content into words and will pass each word back + // through the stream. This method showcases server-side streaming RPCs. rpc Expand(ExpandRequest) returns (stream EchoResponse) { option (google.api.http) = { post: "/v1beta1/echo:expand" @@ -60,7 +97,7 @@ service Echo { // This method will collect the words given to it. When the stream is closed // by the client, this method will return the a concatenation of the strings - // passed to it. This method showcases client-side streaming rpcs. + // passed to it. This method showcases client-side streaming RPCs. rpc Collect(stream EchoRequest) returns (EchoResponse) { option (google.api.http) = { post: "/v1beta1/echo:collect" @@ -68,9 +105,9 @@ service Echo { }; } - // This method, upon receiving a request on the stream, the same content will - // be passed back on the stream. This method showcases bidirectional - // streaming rpcs. + // This method, upon receiving a request on the stream, will pass the same + // content back on the stream. This method showcases bidirectional + // streaming RPCs. rpc Chat(stream EchoRequest) returns (stream EchoResponse); // This is similar to the Expand method but instead of returning a stream of @@ -82,8 +119,30 @@ service Echo { }; } - // This method will wait the requested amount of and then return. - // This method showcases how a client handles a request timing out. + // This is similar to the PagedExpand except that it uses + // max_results instead of page_size, as some legacy APIs still + // do. New APIs should NOT use this pattern. + rpc PagedExpandLegacy(PagedExpandLegacyRequest) returns (PagedExpandResponse) { + option (google.api.http) = { + post: "/v1beta1/echo:pagedExpandLegacy" + body: "*" + }; + } + + // This method returns a map containing lists of words that appear in the input, keyed by their + // initial character. The only words returned are the ones included in the current page, + // as determined by page_token and page_size, which both refer to the word indices in the + // input. This paging result consisting of a map of lists is a pattern used by some legacy + // APIs. New APIs should NOT use this pattern. + rpc PagedExpandLegacyMapped(PagedExpandRequest) returns (PagedExpandLegacyMappedResponse) { + option (google.api.http) = { + post: "/v1beta1/echo:pagedExpandLegacyMapped" + body: "*" + }; + } + + // This method will wait for the requested amount of time and then return. + // This method showcases how a client handles a request timeout. rpc Wait(WaitRequest) returns (google.longrunning.Operation) { option (google.api.http) = { post: "/v1beta1/echo:wait" @@ -95,7 +154,7 @@ service Echo { }; } - // This method will block (wait) for the requested amount of time + // This method will block (wait) for the requested amount of time // and then return the response or error. // This method showcases how a client handles delays or retries. rpc Block(BlockRequest) returns (BlockResponse) { @@ -106,9 +165,19 @@ service Echo { }; } -// The request message used for the Echo, Collect and Chat methods. If content -// is set in this message then the request will succeed. If status is set in -// this message then the status will be returned as an error. +// A severity enum used to test enum capabilities in GAPIC surfaces. +enum Severity { + UNNECESSARY = 0; + NECESSARY = 1; + URGENT = 2; + CRITICAL = 3; +} + + +// The request message used for the Echo, Collect and Chat methods. +// If content or opt are set in this message then the request will succeed. +// If status is set in this message then the status will be returned as an +// error. message EchoRequest { oneof response { // The content to be echoed by the server. @@ -117,12 +186,24 @@ message EchoRequest { // The error to be thrown by the server. google.rpc.Status error = 2; } + + // The severity to be echoed by the server. + Severity severity = 3; + + // Optional. This field can be set to test the routing annotation on the Echo method. + string header = 4; + + // Optional. This field can be set to test the routing annotation on the Echo method. + string other_header = 5; } // The response message for the Echo methods. message EchoResponse { // The content specified in the request. string content = 1; + + // The severity specified in the request. + Severity severity = 2; } // The request message for the Expand method. @@ -132,6 +213,9 @@ message ExpandRequest { // The error that is thrown after all words are sent on the stream. google.rpc.Status error = 2; + + //The wait time between each server streaming messages + google.protobuf.Duration stream_wait_time = 3; } // The request for the PagedExpand method. @@ -139,13 +223,29 @@ message PagedExpandRequest { // The string to expand. string content = 1 [(google.api.field_behavior) = REQUIRED]; - // The amount of words to returned in each page. + // The number of words to returned in each page. int32 page_size = 2; // The position of the page to be returned. string page_token = 3; } +// The request for the PagedExpandLegacy method. This is a pattern used by some legacy APIs. New +// APIs should NOT use this pattern, but rather something like PagedExpandRequest which conforms to +// aip.dev/158. +message PagedExpandLegacyRequest { + // The string to expand. + string content = 1 [(google.api.field_behavior) = REQUIRED]; + + // The number of words to returned in each page. + // (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that + // violates aip.dev/158. Ordinarily, this should be page_size. --) + int32 max_results = 2; + + // The position of the page to be returned. + string page_token = 3; +} + // The response for the PagedExpand method. message PagedExpandResponse { // The words that were expanded. @@ -155,6 +255,21 @@ message PagedExpandResponse { string next_page_token = 2; } +// A list of words. +message PagedExpandResponseList { + repeated string words = 1; +} + +message PagedExpandLegacyMappedResponse { + // The words that were expanded, indexed by their initial character. + // (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that violates + // aip.dev/158. Ordinarily, this should be a `repeated` field, as in PagedExpandResponse. --) + map alphabetized = 1; + + // The next page token. + string next_page_token = 2; +} + // The request for Wait method. message WaitRequest { oneof end { diff --git a/test/showcase-echo-client/protos/google/showcase/v1beta1/sequence.proto b/test/showcase-echo-client/protos/google/showcase/v1beta1/sequence.proto new file mode 100644 index 000000000..4c2a6bbe0 --- /dev/null +++ b/test/showcase-echo-client/protos/google/showcase/v1beta1/sequence.proto @@ -0,0 +1,258 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; +import "google/rpc/status.proto"; + +package google.showcase.v1beta1; + +option go_package = "github.com/googleapis/gapic-showcase/server/genproto"; +option java_package = "com.google.showcase.v1beta1"; +option java_multiple_files = true; +option ruby_package = "Google::Showcase::V1beta1"; + +service SequenceService { + // This service is meant to only run locally on the port 7469 (keypad digits + // for "show"). + option (google.api.default_host) = "localhost:7469"; + + // Creates a sequence. + rpc CreateSequence(CreateSequenceRequest) returns (Sequence) { + option (google.api.http) = { + post: "/v1beta1/sequences" + body: "sequence" + }; + option (google.api.method_signature) = "sequence"; + }; + + // Creates a sequence. + rpc CreateStreamingSequence(CreateStreamingSequenceRequest) returns (StreamingSequence) { + option (google.api.http) = { + post: "/v1beta1/streamingSequences" + body: "streaming_sequence" + }; + option (google.api.method_signature) = "streaming_sequence"; + }; + + // Retrieves a sequence. + rpc GetSequenceReport(GetSequenceReportRequest) returns (SequenceReport) { + option (google.api.http) = { + get: "/v1beta1/{name=sequences/*/sequenceReport}" + }; + option (google.api.method_signature) = "name"; + }; + + // Retrieves a sequence. + rpc GetStreamingSequenceReport(GetStreamingSequenceReportRequest) returns (StreamingSequenceReport) { + option (google.api.http) = { + get: "/v1beta1/{name=streamingSequences/*/streamingSequenceReport}" + }; + option (google.api.method_signature) = "name"; + }; + + // Attempts a sequence. + rpc AttemptSequence(AttemptSequenceRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/v1beta1/{name=sequences/*}" + body: "*" + }; + option (google.api.method_signature) = "name"; + }; + + // Attempts a streaming sequence. + rpc AttemptStreamingSequence(AttemptStreamingSequenceRequest) returns (stream AttemptStreamingSequenceResponse) { + option (google.api.http) = { + post: "/v1beta1/{name=streamingSequences/*}:stream" + body: "*" + }; + option (google.api.method_signature) = "name"; + }; +} + +message Sequence { + option (google.api.resource) = { + type: "showcase.googleapis.com/Sequence" + pattern: "sequences/{sequence}" + }; + + string name = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // A server response to an RPC Attempt in a sequence. + message Response { + // The status to return for an individual attempt. + google.rpc.Status status = 1; + + // The amount of time to delay sending the response. + google.protobuf.Duration delay = 2; + } + + // Sequence of responses to return in order for each attempt. If empty, the + // default response is an immediate OK. + repeated Response responses = 2; +} + +message StreamingSequence { + option (google.api.resource) = { + type: "showcase.googleapis.com/StreamingSequence" + pattern: "streamingSequences/{streaming_sequence}" + }; + + string name = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // The Content that the stream will send + string content = 2; + + // A server response to an RPC Attempt in a sequence. + message Response { + // The status to return for an individual attempt. + google.rpc.Status status = 1; + + // The amount of time to delay sending the response. + google.protobuf.Duration delay = 2; + + // The index that the status should be sent + int32 response_index = 3; + } + + // Sequence of responses to return in order for each attempt. If empty, the + // default response is an immediate OK. + repeated Response responses = 3; +} + + +message StreamingSequenceReport { + option (google.api.resource) = { + type: "showcase.googleapis.com/StreamingSequenceReport" + pattern: "streamingSequences/{streaming_sequence}/streamingSequenceReport" + }; + + string name = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Contains metrics on individual RPC Attempts in a sequence. + message Attempt { + // The attempt number - starting at 0. + int32 attempt_number = 1; + + // The deadline dictated by the attempt to the server. + google.protobuf.Timestamp attempt_deadline = 2; + + // The time that the server responded to the RPC attempt. Used for + // calculating attempt_delay. + google.protobuf.Timestamp response_time = 3; + + // The server perceived delay between sending the last response and + // receiving this attempt. Used for validating attempt delay backoff. + google.protobuf.Duration attempt_delay = 4; + + // The status returned to the attempt. + google.rpc.Status status = 5; + + } + + // The set of RPC attempts received by the server for a Sequence. + repeated Attempt attempts = 2; +} + +message SequenceReport { + option (google.api.resource) = { + type: "showcase.googleapis.com/SequenceReport" + pattern: "sequences/{sequence}/sequenceReport" + }; + + string name = 1 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Contains metrics on individual RPC Attempts in a sequence. + message Attempt { + // The attempt number - starting at 0. + int32 attempt_number = 1; + + // The deadline dictated by the attempt to the server. + google.protobuf.Timestamp attempt_deadline = 2; + + // The time that the server responded to the RPC attempt. Used for + // calculating attempt_delay. + google.protobuf.Timestamp response_time = 3; + + // The server perceived delay between sending the last response and + // receiving this attempt. Used for validating attempt delay backoff. + google.protobuf.Duration attempt_delay = 4; + + // The status returned to the attempt. + google.rpc.Status status = 5; + } + + // The set of RPC attempts received by the server for a Sequence. + repeated Attempt attempts = 2; +} + +message CreateSequenceRequest { + Sequence sequence = 1; +} + +message CreateStreamingSequenceRequest { + StreamingSequence streaming_sequence = 1; +} + +message AttemptSequenceRequest { + string name = 1 [ + (google.api.resource_reference).type = "showcase.googleapis.com/Sequence", + (google.api.field_behavior) = REQUIRED + ]; + +} + +message AttemptStreamingSequenceRequest { + string name = 1 [ + (google.api.resource_reference).type = "showcase.googleapis.com/StreamingSequence", + (google.api.field_behavior) = REQUIRED + ]; + + // used to send the index of the last failed message + // in the string "content" of an AttemptStreamingSequenceResponse + // needed for stream resumption logic testing + int32 last_fail_index = 2 [ + (google.api.field_behavior) = OPTIONAL + ]; +} + +// The response message for the Echo methods. +message AttemptStreamingSequenceResponse { + // The content specified in the request. + string content = 1; + +} + +message GetSequenceReportRequest { + string name = 1 [ + (google.api.resource_reference).type = + "showcase.googleapis.com/SequenceReport", + (google.api.field_behavior) = REQUIRED + ]; +} + +message GetStreamingSequenceReportRequest { + string name = 1 [ + (google.api.resource_reference).type = + "showcase.googleapis.com/StreamingSequenceReport", + (google.api.field_behavior) = REQUIRED + ]; +} diff --git a/test/showcase-echo-client/src/index.ts b/test/showcase-echo-client/src/index.ts index cfccbdf64..3e3fba4b1 100644 --- a/test/showcase-echo-client/src/index.ts +++ b/test/showcase-echo-client/src/index.ts @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,10 +19,9 @@ import * as v1beta1 from './v1beta1'; const EchoClient = v1beta1.EchoClient; type EchoClient = v1beta1.EchoClient; -export {v1beta1, EchoClient}; -export default { - v1beta1, - EchoClient, -}; +const SequenceServiceClient = v1beta1.SequenceServiceClient; +type SequenceServiceClient = v1beta1.SequenceServiceClient; +export {v1beta1, EchoClient, SequenceServiceClient}; +export default {v1beta1, EchoClient, SequenceServiceClient}; import * as protos from '../protos/protos'; export {protos}; diff --git a/test/showcase-echo-client/src/v1beta1/echo_client.ts b/test/showcase-echo-client/src/v1beta1/echo_client.ts index 898c84887..1a481d11d 100644 --- a/test/showcase-echo-client/src/v1beta1/echo_client.ts +++ b/test/showcase-echo-client/src/v1beta1/echo_client.ts @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ // ** All changes to this file may be overwritten. ** /* global window */ -import * as gax from 'google-gax'; -import { +import type * as gax from 'google-gax'; +import type { Callback, CallOptions, Descriptors, @@ -27,16 +27,12 @@ import { LROperation, PaginationCallback, GaxCall, - GoogleError, IamClient, IamProtos, LocationsClient, LocationProtos, } from 'google-gax'; - -import {Transform} from 'stream'; -import {RequestType} from 'google-gax/build/src/apitypes'; -import {PassThrough} from 'stream'; +import {Transform, PassThrough} from 'stream'; import * as protos from '../../protos/protos'; import jsonProtos = require('../../protos/protos.json'); /** @@ -45,7 +41,6 @@ import jsonProtos = require('../../protos/protos.json'); * This file defines retry strategy and timeouts for all API methods in this library. */ import * as gapicConfig from './echo_client_config.json'; -import {operationsProtos} from 'google-gax'; const version = require('../../../package.json').version; /** @@ -53,7 +48,9 @@ const version = require('../../../package.json').version; * side streaming, client side streaming, and bidirectional streaming. This * service also exposes methods that explicitly implement server delay, and * paginated calls. Set the 'showcase-trailer' metadata key on any method - * to have the values echoed in the response trailers. + * to have the values echoed in the response trailers. Set the + * 'x-goog-request-params' metadata key on any method to have the values + * echoed in the response headers. * @class * @memberof v1beta1 */ @@ -112,8 +109,18 @@ export class EchoClient { * Pass "rest" to use HTTP/1.1 REST API instead of gRPC. * For more information, please check the * {@link https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#http11-rest-api-mode documentation}. + * @param {gax} [gaxInstance]: loaded instance of `google-gax`. Useful if you + * need to avoid loading the default gRPC version and want to use the fallback + * HTTP implementation. Load only fallback version and pass it to the constructor: + * ``` + * const gax = require('google-gax/build/src/fallback'); // avoids loading google-gax with gRPC + * const client = new EchoClient({fallback: 'rest'}, gax); + * ``` */ - constructor(opts?: ClientOptions) { + constructor( + opts?: ClientOptions, + gaxInstance?: typeof gax | typeof gax.fallback + ) { // Ensure that options include all the required fields. const staticMembers = this.constructor as typeof EchoClient; const servicePath = @@ -133,8 +140,13 @@ export class EchoClient { opts['scopes'] = staticMembers.scopes; } + // Load google-gax module synchronously if needed + if (!gaxInstance) { + gaxInstance = require('google-gax') as typeof gax; + } + // Choose either gRPC or proto-over-HTTP implementation of google-gax. - this._gaxModule = opts.fallback ? gax.fallback : gax; + this._gaxModule = opts.fallback ? gaxInstance.fallback : gaxInstance; // Create a `gaxGrpc` object, with any grpc-specific options sent to the client. this._gaxGrpc = new this._gaxModule.GrpcClient(opts); @@ -155,9 +167,12 @@ export class EchoClient { if (servicePath === staticMembers.servicePath) { this.auth.defaultScopes = staticMembers.scopes; } - this.iamClient = new IamClient(this._gaxGrpc, opts); + this.iamClient = new this._gaxModule.IamClient(this._gaxGrpc, opts); - this.locationsClient = new LocationsClient(this._gaxGrpc, opts); + this.locationsClient = new this._gaxModule.LocationsClient( + this._gaxGrpc, + opts + ); // Determine the client header string. const clientHeader = [`gax/${this._gaxModule.version}`, `gapic/${version}`]; @@ -166,10 +181,10 @@ export class EchoClient { } else { clientHeader.push(`gl-web/${this._gaxModule.version}`); } - if (!opts.fallback) { - clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`); - } else if (opts.fallback === 'rest') { + if (opts.fallback) { clientHeader.push(`rest/${this._gaxGrpc.grpcVersion}`); + } else { + clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`); } if (opts.libName && opts.libVersion) { clientHeader.push(`${opts.libName}/${opts.libVersion}`); @@ -181,31 +196,18 @@ export class EchoClient { // identifiers to uniquely identify resources within the API. // Create useful helper objects for these. this.pathTemplates = { - blueprintPathTemplate: new this._gaxModule.PathTemplate( - 'sessions/{session}/tests/{test}/blueprints/{blueprint}' - ), - roomPathTemplate: new this._gaxModule.PathTemplate('rooms/{room_id}'), - roomIdBlurbIdPathTemplate: new this._gaxModule.PathTemplate( - 'rooms/{room_id}/blurbs/{blurb_id}' + sequencePathTemplate: new this._gaxModule.PathTemplate( + 'sequences/{sequence}' ), - roomIdBlurbsLegacyRoomIdBlurbIdPathTemplate: - new this._gaxModule.PathTemplate( - 'rooms/{room_id}/blurbs/legacy/{legacy_room_id}.{blurb_id}' - ), - sessionPathTemplate: new this._gaxModule.PathTemplate( - 'sessions/{session}' + sequenceReportPathTemplate: new this._gaxModule.PathTemplate( + 'sequences/{sequence}/sequenceReport' ), - testPathTemplate: new this._gaxModule.PathTemplate( - 'sessions/{session}/tests/{test}' + streamingSequencePathTemplate: new this._gaxModule.PathTemplate( + 'streamingSequences/{streaming_sequence}' ), - userPathTemplate: new this._gaxModule.PathTemplate('users/{user_id}'), - userIdProfileBlurbIdPathTemplate: new this._gaxModule.PathTemplate( - 'user/{user_id}/profile/blurbs/{blurb_id}' + streamingSequenceReportPathTemplate: new this._gaxModule.PathTemplate( + 'streamingSequences/{streaming_sequence}/streamingSequenceReport' ), - userIdProfileBlurbsLegacyUserIdBlurbIdPathTemplate: - new this._gaxModule.PathTemplate( - 'user/{user_id}/profile/blurbs/legacy/{legacy_user_id}~{blurb_id}' - ), }; // Some of the methods on this service return "paged" results, @@ -223,16 +225,19 @@ export class EchoClient { // Provide descriptors for these. this.descriptors.stream = { expand: new this._gaxModule.StreamDescriptor( - gax.StreamType.SERVER_STREAMING, - opts.fallback === 'rest' + this._gaxModule.StreamType.SERVER_STREAMING, + // legacy: opts.fallback can be a string or a boolean + opts.fallback ? true : false ), collect: new this._gaxModule.StreamDescriptor( - gax.StreamType.CLIENT_STREAMING, - opts.fallback === 'rest' + this._gaxModule.StreamType.CLIENT_STREAMING, + // legacy: opts.fallback can be a string or a boolean + opts.fallback ? true : false ), chat: new this._gaxModule.StreamDescriptor( - gax.StreamType.BIDI_STREAMING, - opts.fallback === 'rest' + this._gaxModule.StreamType.BIDI_STREAMING, + // legacy: opts.fallback can be a string or a boolean + opts.fallback ? true : false ), }; @@ -244,7 +249,7 @@ export class EchoClient { auth: this.auth, grpc: 'grpc' in this._gaxGrpc ? this._gaxGrpc.grpc : undefined, }; - if (opts.fallback === 'rest') { + if (opts.fallback) { lroOptions.protoJson = protoFilesRoot; lroOptions.httpRules = [ { @@ -343,7 +348,7 @@ export class EchoClient { this.innerApiCalls = {}; // Add a warn function to the client constructor so it can be easily tested. - this.warn = gax.warn; + this.warn = this._gaxModule.warn; } /** @@ -384,6 +389,7 @@ export class EchoClient { 'collect', 'chat', 'pagedExpand', + 'pagedExpandLegacy', 'wait', 'block', ]; @@ -397,7 +403,9 @@ export class EchoClient { setImmediate(() => { stream.emit( 'error', - new GoogleError('The client has already been closed.') + new this._gaxModule.GoogleError( + 'The client has already been closed.' + ) ); }); return stream; @@ -490,12 +498,17 @@ export class EchoClient { * The content to be echoed by the server. * @param {google.rpc.Status} request.error * The error to be thrown by the server. + * @param {google.showcase.v1beta1.Severity} request.severity + * The severity to be echoed by the server. + * @param {string} request.header + * Optional. This field can be set to test the routing annotation on the Echo method. + * @param {string} request.otherHeader + * Optional. This field can be set to test the routing annotation on the Echo method. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. - * The first element of the array is an object representing [EchoResponse]{@link google.showcase.v1beta1.EchoResponse}. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods) + * The first element of the array is an object representing {@link protos.google.showcase.v1beta1.EchoResponse|EchoResponse}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.echo.js * region_tag:localhost_v1beta1_generated_Echo_Echo_async @@ -559,9 +572,202 @@ export class EchoClient { options = options || {}; options.otherArgs = options.otherArgs || {}; options.otherArgs.headers = options.otherArgs.headers || {}; + const routingParameter = {}; + { + const fieldValue = request.header; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue.toString().match(RegExp('(?
.*)')); + if (match) { + const parameterValue = match.groups?.['header'] ?? fieldValue; + Object.assign(routingParameter, {header: parameterValue}); + } + } + } + { + const fieldValue = request.header; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue + .toString() + .match(RegExp('(?(?:.*)?)')); + if (match) { + const parameterValue = match.groups?.['routing_id'] ?? fieldValue; + Object.assign(routingParameter, {routing_id: parameterValue}); + } + } + } + { + const fieldValue = request.header; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue + .toString() + .match(RegExp('(?regions/[^/]+/zones/[^/]+(?:/.*)?)')); + if (match) { + const parameterValue = match.groups?.['table_name'] ?? fieldValue; + Object.assign(routingParameter, {table_name: parameterValue}); + } + } + } + { + const fieldValue = request.header; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue + .toString() + .match(RegExp('(?projects/[^/]+)(?:/.*)?')); + if (match) { + const parameterValue = match.groups?.['super_id'] ?? fieldValue; + Object.assign(routingParameter, {super_id: parameterValue}); + } + } + } + { + const fieldValue = request.header; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue + .toString() + .match( + RegExp('(?projects/[^/]+/instances/[^/]+(?:/.*)?)') + ); + if (match) { + const parameterValue = match.groups?.['table_name'] ?? fieldValue; + Object.assign(routingParameter, {table_name: parameterValue}); + } + } + } + { + const fieldValue = request.header; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue + .toString() + .match( + RegExp('projects/[^/]+/(?instances/[^/]+)(?:/.*)?') + ); + if (match) { + const parameterValue = match.groups?.['instance_id'] ?? fieldValue; + Object.assign(routingParameter, {instance_id: parameterValue}); + } + } + } + { + const fieldValue = request.otherHeader; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue.toString().match(RegExp('(?(?:.*)?)')); + if (match) { + const parameterValue = match.groups?.['baz'] ?? fieldValue; + Object.assign(routingParameter, {baz: parameterValue}); + } + } + } + { + const fieldValue = request.otherHeader; + if (fieldValue !== undefined && fieldValue !== null) { + const match = fieldValue + .toString() + .match(RegExp('(?projects/[^/]+)(?:/.*)?')); + if (match) { + const parameterValue = match.groups?.['qux'] ?? fieldValue; + Object.assign(routingParameter, {qux: parameterValue}); + } + } + } + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams(routingParameter); this.initialize(); return this.innerApiCalls.echo(request, options, callback); } + /** + * This is similar to the PagedExpand except that it uses + * max_results instead of page_size, as some legacy APIs still + * do. New APIs should NOT use this pattern. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.content + * The string to expand. + * @param {number} request.maxResults + * The number of words to returned in each page. + * (-- aip.dev/not-precedent: This is a legacy, non-standard pattern that + * violates aip.dev/158. Ordinarily, this should be page_size. --) + * @param {string} request.pageToken + * The position of the page to be returned. + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.showcase.v1beta1.PagedExpandResponse|PagedExpandResponse}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1beta1/echo.paged_expand_legacy.js + * region_tag:localhost_v1beta1_generated_Echo_PagedExpandLegacy_async + */ + pagedExpandLegacy( + request?: protos.google.showcase.v1beta1.IPagedExpandLegacyRequest, + options?: CallOptions + ): Promise< + [ + protos.google.showcase.v1beta1.IPagedExpandResponse, + protos.google.showcase.v1beta1.IPagedExpandLegacyRequest | undefined, + {} | undefined + ] + >; + pagedExpandLegacy( + request: protos.google.showcase.v1beta1.IPagedExpandLegacyRequest, + options: CallOptions, + callback: Callback< + protos.google.showcase.v1beta1.IPagedExpandResponse, + | protos.google.showcase.v1beta1.IPagedExpandLegacyRequest + | null + | undefined, + {} | null | undefined + > + ): void; + pagedExpandLegacy( + request: protos.google.showcase.v1beta1.IPagedExpandLegacyRequest, + callback: Callback< + protos.google.showcase.v1beta1.IPagedExpandResponse, + | protos.google.showcase.v1beta1.IPagedExpandLegacyRequest + | null + | undefined, + {} | null | undefined + > + ): void; + pagedExpandLegacy( + request?: protos.google.showcase.v1beta1.IPagedExpandLegacyRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.showcase.v1beta1.IPagedExpandResponse, + | protos.google.showcase.v1beta1.IPagedExpandLegacyRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.showcase.v1beta1.IPagedExpandResponse, + | protos.google.showcase.v1beta1.IPagedExpandLegacyRequest + | null + | undefined, + {} | null | undefined + > + ): Promise< + [ + protos.google.showcase.v1beta1.IPagedExpandResponse, + protos.google.showcase.v1beta1.IPagedExpandLegacyRequest | undefined, + {} | undefined + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + this.initialize(); + return this.innerApiCalls.pagedExpandLegacy(request, options, callback); + } /** * This method will block (wait) for the requested amount of time * and then return the response or error. @@ -579,9 +785,8 @@ export class EchoClient { * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. - * The first element of the array is an object representing [BlockResponse]{@link google.showcase.v1beta1.BlockResponse}. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods) + * The first element of the array is an object representing {@link protos.google.showcase.v1beta1.BlockResponse|BlockResponse}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.block.js * region_tag:localhost_v1beta1_generated_Echo_Block_async @@ -650,8 +855,8 @@ export class EchoClient { } /** - * This method split the given content into words and will pass each word back - * through the stream. This method showcases server-side streaming rpcs. + * This method splits the given content into words and will pass each word back + * through the stream. This method showcases server-side streaming RPCs. * * @param {Object} request * The request object that will be sent. @@ -659,12 +864,13 @@ export class EchoClient { * The content that will be split into words and returned on the stream. * @param {google.rpc.Status} request.error * The error that is thrown after all words are sent on the stream. + * @param {google.protobuf.Duration} request.streamWaitTime + * The wait time between each server streaming messages * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} - * An object stream which emits [EchoResponse]{@link google.showcase.v1beta1.EchoResponse} on 'data' event. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#server-streaming) + * An object stream which emits {@link protos.google.showcase.v1beta1.EchoResponse|EchoResponse} on 'data' event. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#server-streaming | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.expand.js * region_tag:localhost_v1beta1_generated_Echo_Expand_async @@ -684,14 +890,13 @@ export class EchoClient { /** * This method will collect the words given to it. When the stream is closed * by the client, this method will return the a concatenation of the strings - * passed to it. This method showcases client-side streaming rpcs. + * passed to it. This method showcases client-side streaming RPCs. * * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} - A writable stream which accepts objects representing - * [EchoRequest]{@link google.showcase.v1beta1.EchoRequest}. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#client-streaming) + * {@link protos.google.showcase.v1beta1.EchoRequest|EchoRequest}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#client-streaming | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.collect.js * region_tag:localhost_v1beta1_generated_Echo_Collect_async @@ -735,18 +940,17 @@ export class EchoClient { } /** - * This method, upon receiving a request on the stream, the same content will - * be passed back on the stream. This method showcases bidirectional - * streaming rpcs. + * This method, upon receiving a request on the stream, will pass the same + * content back on the stream. This method showcases bidirectional + * streaming RPCs. * * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} * An object stream which is both readable and writable. It accepts objects - * representing [EchoRequest]{@link google.showcase.v1beta1.EchoRequest} for write() method, and - * will emit objects representing [EchoResponse]{@link google.showcase.v1beta1.EchoResponse} on 'data' event asynchronously. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#bi-directional-streaming) + * representing {@link protos.google.showcase.v1beta1.EchoRequest|EchoRequest} for write() method, and + * will emit objects representing {@link protos.google.showcase.v1beta1.EchoResponse|EchoResponse} on 'data' event asynchronously. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#bi-directional-streaming | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.chat.js * region_tag:localhost_v1beta1_generated_Echo_Chat_async @@ -757,8 +961,8 @@ export class EchoClient { } /** - * This method will wait the requested amount of and then return. - * This method showcases how a client handles a request timing out. + * This method will wait for the requested amount of time and then return. + * This method showcases how a client handles a request timeout. * * @param {Object} request * The request object that will be sent. @@ -777,8 +981,7 @@ export class EchoClient { * The first element of the array is an object representing * a long running operation. Its `promise()` method returns a promise * you can `await` for. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#long-running-operations) + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#long-running-operations | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.wait.js * region_tag:localhost_v1beta1_generated_Echo_Wait_async @@ -869,8 +1072,7 @@ export class EchoClient { * The operation name that will be passed. * @returns {Promise} - The promise which resolves to an object. * The decoded operation object has result and metadata field to get information from. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#long-running-operations) + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#long-running-operations | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.wait.js * region_tag:localhost_v1beta1_generated_Echo_Wait_async @@ -883,11 +1085,12 @@ export class EchoClient { protos.google.showcase.v1beta1.WaitMetadata > > { - const request = new operationsProtos.google.longrunning.GetOperationRequest( - {name} - ); + const request = + new this._gaxModule.operationsProtos.google.longrunning.GetOperationRequest( + {name} + ); const [operation] = await this.operationsClient.getOperation(request); - const decodeOperation = new gax.Operation( + const decodeOperation = new this._gaxModule.Operation( operation, this.descriptors.longrunning.wait, this._gaxModule.createDefaultBackoffSettings() @@ -906,20 +1109,19 @@ export class EchoClient { * @param {string} request.content * The string to expand. * @param {number} request.pageSize - * The amount of words to returned in each page. + * The number of words to returned in each page. * @param {string} request.pageToken * The position of the page to be returned. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. - * The first element of the array is Array of [EchoResponse]{@link google.showcase.v1beta1.EchoResponse}. + * The first element of the array is Array of {@link protos.google.showcase.v1beta1.EchoResponse|EchoResponse}. * The client library will perform auto-pagination by default: it will call the API as many * times as needed and will merge results from all the pages into this array. * Note that it can affect your quota. * We recommend using `pagedExpandAsync()` * method described below for async iteration which you can stop as needed. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination) + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination | documentation } * for more details and examples. */ pagedExpand( @@ -994,19 +1196,18 @@ export class EchoClient { * @param {string} request.content * The string to expand. * @param {number} request.pageSize - * The amount of words to returned in each page. + * The number of words to returned in each page. * @param {string} request.pageToken * The position of the page to be returned. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Stream} - * An object stream which emits an object representing [EchoResponse]{@link google.showcase.v1beta1.EchoResponse} on 'data' event. + * An object stream which emits an object representing {@link protos.google.showcase.v1beta1.EchoResponse|EchoResponse} on 'data' event. * The client library will perform auto-pagination by default: it will call the API as many * times as needed. Note that it can affect your quota. * We recommend using `pagedExpandAsync()` * method described below for async iteration which you can stop as needed. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination) + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination | documentation } * for more details and examples. */ pagedExpandStream( @@ -1021,7 +1222,7 @@ export class EchoClient { const callSettings = defaultCallSettings.merge(options); this.initialize(); return this.descriptors.page.pagedExpand.createStream( - this.innerApiCalls.pagedExpand as gax.GaxCall, + this.innerApiCalls.pagedExpand as GaxCall, request, callSettings ); @@ -1036,18 +1237,17 @@ export class EchoClient { * @param {string} request.content * The string to expand. * @param {number} request.pageSize - * The amount of words to returned in each page. + * The number of words to returned in each page. * @param {string} request.pageToken * The position of the page to be returned. * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Object} - * An iterable Object that allows [async iteration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). + * An iterable Object that allows {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols | async iteration }. * When you iterate the returned iterable, each element will be an object representing - * [EchoResponse]{@link google.showcase.v1beta1.EchoResponse}. The API will be called under the hood as needed, once per the page, + * {@link protos.google.showcase.v1beta1.EchoResponse|EchoResponse}. The API will be called under the hood as needed, once per the page, * so you can stop the iteration when you don't need more results. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination) + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination | documentation } * for more details and examples. * @example include:samples/generated/v1beta1/echo.paged_expand.js * region_tag:localhost_v1beta1_generated_Echo_PagedExpand_async @@ -1065,7 +1265,7 @@ export class EchoClient { this.initialize(); return this.descriptors.page.pagedExpand.asyncIterate( this.innerApiCalls['pagedExpand'] as GaxCall, - request as unknown as RequestType, + request as {}, callSettings ) as AsyncIterable; } @@ -1082,16 +1282,16 @@ export class EchoClient { * OPTIONAL: A `GetPolicyOptions` object for specifying options to * `GetIamPolicy`. This field is only used by Cloud IAM. * - * This object should have the same structure as [GetPolicyOptions]{@link google.iam.v1.GetPolicyOptions} + * This object should have the same structure as {@link google.iam.v1.GetPolicyOptions | GetPolicyOptions}. * @param {Object} [options] * Optional parameters. You can override the default settings for this call, e.g, timeout, - * retries, paginations, etc. See [gax.CallOptions]{@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html} for the details. + * retries, paginations, etc. See {@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html | gax.CallOptions} for the details. * @param {function(?Error, ?Object)} [callback] * The function which will be called with the result of the API call. * - * The second parameter to the callback is an object representing [Policy]{@link google.iam.v1.Policy}. + * The second parameter to the callback is an object representing {@link google.iam.v1.Policy | Policy}. * @returns {Promise} - The promise which resolves to an array. - * The first element of the array is an object representing [Policy]{@link google.iam.v1.Policy}. + * The first element of the array is an object representing {@link google.iam.v1.Policy | Policy}. * The promise has a method named "cancel" which cancels the ongoing API call. */ getIamPolicy( @@ -1108,7 +1308,7 @@ export class EchoClient { IamProtos.google.iam.v1.GetIamPolicyRequest | null | undefined, {} | null | undefined > - ): Promise { + ): Promise<[IamProtos.google.iam.v1.Policy]> { return this.iamClient.getIamPolicy(request, options, callback); } @@ -1129,17 +1329,16 @@ export class EchoClient { * @param {string[]} request.permissions * The set of permissions to check for the `resource`. Permissions with * wildcards (such as '*' or 'storage.*') are not allowed. For more - * information see - * [IAM Overview](https://cloud.google.com/iam/docs/overview#permissions). + * information see {@link https://cloud.google.com/iam/docs/overview#permissions | IAM Overview }. * @param {Object} [options] * Optional parameters. You can override the default settings for this call, e.g, timeout, - * retries, paginations, etc. See [gax.CallOptions]{@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html} for the details. + * retries, paginations, etc. See {@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html | gax.CallOptions} for the details. * @param {function(?Error, ?Object)} [callback] * The function which will be called with the result of the API call. * - * The second parameter to the callback is an object representing [TestIamPermissionsResponse]{@link google.iam.v1.TestIamPermissionsResponse}. + * The second parameter to the callback is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. * @returns {Promise} - The promise which resolves to an array. - * The first element of the array is an object representing [TestIamPermissionsResponse]{@link google.iam.v1.TestIamPermissionsResponse}. + * The first element of the array is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. * The promise has a method named "cancel" which cancels the ongoing API call. */ setIamPolicy( @@ -1156,7 +1355,7 @@ export class EchoClient { IamProtos.google.iam.v1.SetIamPolicyRequest | null | undefined, {} | null | undefined > - ): Promise { + ): Promise<[IamProtos.google.iam.v1.Policy]> { return this.iamClient.setIamPolicy(request, options, callback); } @@ -1177,17 +1376,16 @@ export class EchoClient { * @param {string[]} request.permissions * The set of permissions to check for the `resource`. Permissions with * wildcards (such as '*' or 'storage.*') are not allowed. For more - * information see - * [IAM Overview](https://cloud.google.com/iam/docs/overview#permissions). + * information see {@link https://cloud.google.com/iam/docs/overview#permissions | IAM Overview }. * @param {Object} [options] * Optional parameters. You can override the default settings for this call, e.g, timeout, - * retries, paginations, etc. See [gax.CallOptions]{@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html} for the details. + * retries, paginations, etc. See {@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html | gax.CallOptions} for the details. * @param {function(?Error, ?Object)} [callback] * The function which will be called with the result of the API call. * - * The second parameter to the callback is an object representing [TestIamPermissionsResponse]{@link google.iam.v1.TestIamPermissionsResponse}. + * The second parameter to the callback is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. * @returns {Promise} - The promise which resolves to an array. - * The first element of the array is an object representing [TestIamPermissionsResponse]{@link google.iam.v1.TestIamPermissionsResponse}. + * The first element of the array is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. * The promise has a method named "cancel" which cancels the ongoing API call. * */ @@ -1205,7 +1403,7 @@ export class EchoClient { IamProtos.google.iam.v1.TestIamPermissionsRequest | null | undefined, {} | null | undefined > - ): Promise { + ): Promise<[IamProtos.google.iam.v1.TestIamPermissionsResponse]> { return this.iamClient.testIamPermissions(request, options, callback); } @@ -1217,11 +1415,10 @@ export class EchoClient { * @param {string} request.name * Resource name for the location. * @param {object} [options] - * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html | CallOptions} for more details. * @returns {Promise} - The promise which resolves to an array. - * The first element of the array is an object representing [Location]{@link google.cloud.location.Location}. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods) + * The first element of the array is an object representing {@link google.cloud.location.Location | Location}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } * for more details and examples. * @example * ``` @@ -1267,12 +1464,11 @@ export class EchoClient { * @param {object} [options] * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. * @returns {Object} - * An iterable Object that allows [async iteration](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). + * An iterable Object that allows {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols | async iteration }. * When you iterate the returned iterable, each element will be an object representing - * [Location]{@link google.cloud.location.Location}. The API will be called under the hood as needed, once per the page, + * {@link google.cloud.location.Location | Location}. The API will be called under the hood as needed, once per the page, * so you can stop the iteration when you don't need more results. - * Please see the - * [documentation](https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination) + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination | documentation } * for more details and examples. * @example * ``` @@ -1298,20 +1494,18 @@ export class EchoClient { * @param {string} request.name - The name of the operation resource. * @param {Object=} options * Optional parameters. You can override the default settings for this call, - * e.g, timeout, retries, paginations, etc. See [gax.CallOptions]{@link - * https://googleapis.github.io/gax-nodejs/global.html#CallOptions} for the - * details. + * e.g, timeout, retries, paginations, etc. See {@link + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions | gax.CallOptions} + * for the details. * @param {function(?Error, ?Object)=} callback * The function which will be called with the result of the API call. * * The second parameter to the callback is an object representing - * [google.longrunning.Operation]{@link - * external:"google.longrunning.Operation"}. + * {@link google.longrunning.Operation | google.longrunning.Operation}. * @return {Promise} - The promise which resolves to an array. * The first element of the array is an object representing - * [google.longrunning.Operation]{@link - * external:"google.longrunning.Operation"}. The promise has a method named - * "cancel" which cancels the ongoing API call. + * {@link google.longrunning.Operation | google.longrunning.Operation}. + * The promise has a method named "cancel" which cancels the ongoing API call. * * @example * ``` @@ -1355,11 +1549,11 @@ export class EchoClient { * resources in a page. * @param {Object=} options * Optional parameters. You can override the default settings for this call, - * e.g, timeout, retries, paginations, etc. See [gax.CallOptions]{@link - * https://googleapis.github.io/gax-nodejs/global.html#CallOptions} for the + * e.g, timeout, retries, paginations, etc. See {@link + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions | gax.CallOptions} for the * details. * @returns {Object} - * An iterable Object that conforms to @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols. + * An iterable Object that conforms to {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols | iteration protocols}. * * @example * ``` @@ -1390,8 +1584,8 @@ export class EchoClient { * @param {string} request.name - The name of the operation resource to be cancelled. * @param {Object=} options * Optional parameters. You can override the default settings for this call, - * e.g, timeout, retries, paginations, etc. See [gax.CallOptions]{@link - * https://googleapis.github.io/gax-nodejs/global.html#CallOptions} for the + * e.g, timeout, retries, paginations, etc. See {@link + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions | gax.CallOptions} for the * details. * @param {function(?Error)=} callback * The function which will be called with the result of the API call. @@ -1433,9 +1627,9 @@ export class EchoClient { * @param {string} request.name - The name of the operation resource to be deleted. * @param {Object=} options * Optional parameters. You can override the default settings for this call, - * e.g, timeout, retries, paginations, etc. See [gax.CallOptions]{@link - * https://googleapis.github.io/gax-nodejs/global.html#CallOptions} for the - * details. + * e.g, timeout, retries, paginations, etc. See {@link + * https://googleapis.github.io/gax-nodejs/global.html#CallOptions | gax.CallOptions} + * for the details. * @param {function(?Error)=} callback * The function which will be called with the result of the API call. * @return {Promise} - The promise which resolves when API call finishes. @@ -1471,371 +1665,105 @@ export class EchoClient { // -------------------- /** - * Return a fully-qualified blueprint resource name string. + * Return a fully-qualified sequence resource name string. * - * @param {string} session - * @param {string} test - * @param {string} blueprint + * @param {string} sequence * @returns {string} Resource name string. */ - blueprintPath(session: string, test: string, blueprint: string) { - return this.pathTemplates.blueprintPathTemplate.render({ - session: session, - test: test, - blueprint: blueprint, + sequencePath(sequence: string) { + return this.pathTemplates.sequencePathTemplate.render({ + sequence: sequence, }); } /** - * Parse the session from Blueprint resource. + * Parse the sequence from Sequence resource. * - * @param {string} blueprintName - * A fully-qualified path representing Blueprint resource. - * @returns {string} A string representing the session. + * @param {string} sequenceName + * A fully-qualified path representing Sequence resource. + * @returns {string} A string representing the sequence. */ - matchSessionFromBlueprintName(blueprintName: string) { - return this.pathTemplates.blueprintPathTemplate.match(blueprintName) - .session; + matchSequenceFromSequenceName(sequenceName: string) { + return this.pathTemplates.sequencePathTemplate.match(sequenceName).sequence; } /** - * Parse the test from Blueprint resource. + * Return a fully-qualified sequenceReport resource name string. * - * @param {string} blueprintName - * A fully-qualified path representing Blueprint resource. - * @returns {string} A string representing the test. - */ - matchTestFromBlueprintName(blueprintName: string) { - return this.pathTemplates.blueprintPathTemplate.match(blueprintName).test; - } - - /** - * Parse the blueprint from Blueprint resource. - * - * @param {string} blueprintName - * A fully-qualified path representing Blueprint resource. - * @returns {string} A string representing the blueprint. - */ - matchBlueprintFromBlueprintName(blueprintName: string) { - return this.pathTemplates.blueprintPathTemplate.match(blueprintName) - .blueprint; - } - - /** - * Return a fully-qualified room resource name string. - * - * @param {string} room_id + * @param {string} sequence * @returns {string} Resource name string. */ - roomPath(roomId: string) { - return this.pathTemplates.roomPathTemplate.render({ - room_id: roomId, + sequenceReportPath(sequence: string) { + return this.pathTemplates.sequenceReportPathTemplate.render({ + sequence: sequence, }); } /** - * Parse the room_id from Room resource. + * Parse the sequence from SequenceReport resource. * - * @param {string} roomName - * A fully-qualified path representing Room resource. - * @returns {string} A string representing the room_id. + * @param {string} sequenceReportName + * A fully-qualified path representing SequenceReport resource. + * @returns {string} A string representing the sequence. */ - matchRoomIdFromRoomName(roomName: string) { - return this.pathTemplates.roomPathTemplate.match(roomName).room_id; + matchSequenceFromSequenceReportName(sequenceReportName: string) { + return this.pathTemplates.sequenceReportPathTemplate.match( + sequenceReportName + ).sequence; } /** - * Return a fully-qualified roomIdBlurbId resource name string. + * Return a fully-qualified streamingSequence resource name string. * - * @param {string} room_id - * @param {string} blurb_id + * @param {string} streaming_sequence * @returns {string} Resource name string. */ - roomIdBlurbIdPath(roomId: string, blurbId: string) { - return this.pathTemplates.roomIdBlurbIdPathTemplate.render({ - room_id: roomId, - blurb_id: blurbId, + streamingSequencePath(streamingSequence: string) { + return this.pathTemplates.streamingSequencePathTemplate.render({ + streaming_sequence: streamingSequence, }); } /** - * Parse the room_id from RoomIdBlurbId resource. - * - * @param {string} roomIdBlurbIdName - * A fully-qualified path representing room_id_blurb_id resource. - * @returns {string} A string representing the room_id. - */ - matchRoomIdFromRoomIdBlurbIdName(roomIdBlurbIdName: string) { - return this.pathTemplates.roomIdBlurbIdPathTemplate.match(roomIdBlurbIdName) - .room_id; - } - - /** - * Parse the blurb_id from RoomIdBlurbId resource. + * Parse the streaming_sequence from StreamingSequence resource. * - * @param {string} roomIdBlurbIdName - * A fully-qualified path representing room_id_blurb_id resource. - * @returns {string} A string representing the blurb_id. - */ - matchBlurbIdFromRoomIdBlurbIdName(roomIdBlurbIdName: string) { - return this.pathTemplates.roomIdBlurbIdPathTemplate.match(roomIdBlurbIdName) - .blurb_id; - } - - /** - * Return a fully-qualified roomIdBlurbsLegacyRoomIdBlurbId resource name string. - * - * @param {string} room_id - * @param {string} legacy_room_id - * @param {string} blurb_id - * @returns {string} Resource name string. + * @param {string} streamingSequenceName + * A fully-qualified path representing StreamingSequence resource. + * @returns {string} A string representing the streaming_sequence. */ - roomIdBlurbsLegacyRoomIdBlurbIdPath( - roomId: string, - legacyRoomId: string, - blurbId: string + matchStreamingSequenceFromStreamingSequenceName( + streamingSequenceName: string ) { - return this.pathTemplates.roomIdBlurbsLegacyRoomIdBlurbIdPathTemplate.render( - { - room_id: roomId, - legacy_room_id: legacyRoomId, - blurb_id: blurbId, - } - ); - } - - /** - * Parse the room_id from RoomIdBlurbsLegacyRoomIdBlurbId resource. - * - * @param {string} roomIdBlurbsLegacyRoomIdBlurbIdName - * A fully-qualified path representing room_id_blurbs_legacy_room_id_blurb_id resource. - * @returns {string} A string representing the room_id. - */ - matchRoomIdFromRoomIdBlurbsLegacyRoomIdBlurbIdName( - roomIdBlurbsLegacyRoomIdBlurbIdName: string - ) { - return this.pathTemplates.roomIdBlurbsLegacyRoomIdBlurbIdPathTemplate.match( - roomIdBlurbsLegacyRoomIdBlurbIdName - ).room_id; - } - - /** - * Parse the legacy_room_id from RoomIdBlurbsLegacyRoomIdBlurbId resource. - * - * @param {string} roomIdBlurbsLegacyRoomIdBlurbIdName - * A fully-qualified path representing room_id_blurbs_legacy_room_id_blurb_id resource. - * @returns {string} A string representing the legacy_room_id. - */ - matchLegacyRoomIdFromRoomIdBlurbsLegacyRoomIdBlurbIdName( - roomIdBlurbsLegacyRoomIdBlurbIdName: string - ) { - return this.pathTemplates.roomIdBlurbsLegacyRoomIdBlurbIdPathTemplate.match( - roomIdBlurbsLegacyRoomIdBlurbIdName - ).legacy_room_id; - } - - /** - * Parse the blurb_id from RoomIdBlurbsLegacyRoomIdBlurbId resource. - * - * @param {string} roomIdBlurbsLegacyRoomIdBlurbIdName - * A fully-qualified path representing room_id_blurbs_legacy_room_id_blurb_id resource. - * @returns {string} A string representing the blurb_id. - */ - matchBlurbIdFromRoomIdBlurbsLegacyRoomIdBlurbIdName( - roomIdBlurbsLegacyRoomIdBlurbIdName: string - ) { - return this.pathTemplates.roomIdBlurbsLegacyRoomIdBlurbIdPathTemplate.match( - roomIdBlurbsLegacyRoomIdBlurbIdName - ).blurb_id; - } - - /** - * Return a fully-qualified session resource name string. - * - * @param {string} session - * @returns {string} Resource name string. - */ - sessionPath(session: string) { - return this.pathTemplates.sessionPathTemplate.render({ - session: session, - }); - } - - /** - * Parse the session from Session resource. - * - * @param {string} sessionName - * A fully-qualified path representing Session resource. - * @returns {string} A string representing the session. - */ - matchSessionFromSessionName(sessionName: string) { - return this.pathTemplates.sessionPathTemplate.match(sessionName).session; - } - - /** - * Return a fully-qualified test resource name string. - * - * @param {string} session - * @param {string} test - * @returns {string} Resource name string. - */ - testPath(session: string, test: string) { - return this.pathTemplates.testPathTemplate.render({ - session: session, - test: test, - }); + return this.pathTemplates.streamingSequencePathTemplate.match( + streamingSequenceName + ).streaming_sequence; } /** - * Parse the session from Test resource. + * Return a fully-qualified streamingSequenceReport resource name string. * - * @param {string} testName - * A fully-qualified path representing Test resource. - * @returns {string} A string representing the session. - */ - matchSessionFromTestName(testName: string) { - return this.pathTemplates.testPathTemplate.match(testName).session; - } - - /** - * Parse the test from Test resource. - * - * @param {string} testName - * A fully-qualified path representing Test resource. - * @returns {string} A string representing the test. - */ - matchTestFromTestName(testName: string) { - return this.pathTemplates.testPathTemplate.match(testName).test; - } - - /** - * Return a fully-qualified user resource name string. - * - * @param {string} user_id + * @param {string} streaming_sequence * @returns {string} Resource name string. */ - userPath(userId: string) { - return this.pathTemplates.userPathTemplate.render({ - user_id: userId, + streamingSequenceReportPath(streamingSequence: string) { + return this.pathTemplates.streamingSequenceReportPathTemplate.render({ + streaming_sequence: streamingSequence, }); } /** - * Parse the user_id from User resource. - * - * @param {string} userName - * A fully-qualified path representing User resource. - * @returns {string} A string representing the user_id. - */ - matchUserIdFromUserName(userName: string) { - return this.pathTemplates.userPathTemplate.match(userName).user_id; - } - - /** - * Return a fully-qualified userIdProfileBlurbId resource name string. - * - * @param {string} user_id - * @param {string} blurb_id - * @returns {string} Resource name string. - */ - userIdProfileBlurbIdPath(userId: string, blurbId: string) { - return this.pathTemplates.userIdProfileBlurbIdPathTemplate.render({ - user_id: userId, - blurb_id: blurbId, - }); - } - - /** - * Parse the user_id from UserIdProfileBlurbId resource. - * - * @param {string} userIdProfileBlurbIdName - * A fully-qualified path representing user_id_profile_blurb_id resource. - * @returns {string} A string representing the user_id. - */ - matchUserIdFromUserIdProfileBlurbIdName(userIdProfileBlurbIdName: string) { - return this.pathTemplates.userIdProfileBlurbIdPathTemplate.match( - userIdProfileBlurbIdName - ).user_id; - } - - /** - * Parse the blurb_id from UserIdProfileBlurbId resource. - * - * @param {string} userIdProfileBlurbIdName - * A fully-qualified path representing user_id_profile_blurb_id resource. - * @returns {string} A string representing the blurb_id. - */ - matchBlurbIdFromUserIdProfileBlurbIdName(userIdProfileBlurbIdName: string) { - return this.pathTemplates.userIdProfileBlurbIdPathTemplate.match( - userIdProfileBlurbIdName - ).blurb_id; - } - - /** - * Return a fully-qualified userIdProfileBlurbsLegacyUserIdBlurbId resource name string. - * - * @param {string} user_id - * @param {string} legacy_user_id - * @param {string} blurb_id - * @returns {string} Resource name string. - */ - userIdProfileBlurbsLegacyUserIdBlurbIdPath( - userId: string, - legacyUserId: string, - blurbId: string - ) { - return this.pathTemplates.userIdProfileBlurbsLegacyUserIdBlurbIdPathTemplate.render( - { - user_id: userId, - legacy_user_id: legacyUserId, - blurb_id: blurbId, - } - ); - } - - /** - * Parse the user_id from UserIdProfileBlurbsLegacyUserIdBlurbId resource. - * - * @param {string} userIdProfileBlurbsLegacyUserIdBlurbIdName - * A fully-qualified path representing user_id_profile_blurbs_legacy_user_id_blurb_id resource. - * @returns {string} A string representing the user_id. - */ - matchUserIdFromUserIdProfileBlurbsLegacyUserIdBlurbIdName( - userIdProfileBlurbsLegacyUserIdBlurbIdName: string - ) { - return this.pathTemplates.userIdProfileBlurbsLegacyUserIdBlurbIdPathTemplate.match( - userIdProfileBlurbsLegacyUserIdBlurbIdName - ).user_id; - } - - /** - * Parse the legacy_user_id from UserIdProfileBlurbsLegacyUserIdBlurbId resource. - * - * @param {string} userIdProfileBlurbsLegacyUserIdBlurbIdName - * A fully-qualified path representing user_id_profile_blurbs_legacy_user_id_blurb_id resource. - * @returns {string} A string representing the legacy_user_id. - */ - matchLegacyUserIdFromUserIdProfileBlurbsLegacyUserIdBlurbIdName( - userIdProfileBlurbsLegacyUserIdBlurbIdName: string - ) { - return this.pathTemplates.userIdProfileBlurbsLegacyUserIdBlurbIdPathTemplate.match( - userIdProfileBlurbsLegacyUserIdBlurbIdName - ).legacy_user_id; - } - - /** - * Parse the blurb_id from UserIdProfileBlurbsLegacyUserIdBlurbId resource. + * Parse the streaming_sequence from StreamingSequenceReport resource. * - * @param {string} userIdProfileBlurbsLegacyUserIdBlurbIdName - * A fully-qualified path representing user_id_profile_blurbs_legacy_user_id_blurb_id resource. - * @returns {string} A string representing the blurb_id. + * @param {string} streamingSequenceReportName + * A fully-qualified path representing StreamingSequenceReport resource. + * @returns {string} A string representing the streaming_sequence. */ - matchBlurbIdFromUserIdProfileBlurbsLegacyUserIdBlurbIdName( - userIdProfileBlurbsLegacyUserIdBlurbIdName: string + matchStreamingSequenceFromStreamingSequenceReportName( + streamingSequenceReportName: string ) { - return this.pathTemplates.userIdProfileBlurbsLegacyUserIdBlurbIdPathTemplate.match( - userIdProfileBlurbsLegacyUserIdBlurbIdName - ).blurb_id; + return this.pathTemplates.streamingSequenceReportPathTemplate.match( + streamingSequenceReportName + ).streaming_sequence; } /** diff --git a/test/showcase-echo-client/src/v1beta1/echo_client_config.json b/test/showcase-echo-client/src/v1beta1/echo_client_config.json index c30d10733..57bd509e9 100644 --- a/test/showcase-echo-client/src/v1beta1/echo_client_config.json +++ b/test/showcase-echo-client/src/v1beta1/echo_client_config.json @@ -40,6 +40,14 @@ "retry_codes_name": "non_idempotent", "retry_params_name": "default" }, + "PagedExpandLegacy": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "PagedExpandLegacyMapped": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, "Wait": { "retry_codes_name": "non_idempotent", "retry_params_name": "default" diff --git a/test/showcase-echo-client/src/v1beta1/echo_proto_list.json b/test/showcase-echo-client/src/v1beta1/echo_proto_list.json index 4d911013e..76b3da61a 100644 --- a/test/showcase-echo-client/src/v1beta1/echo_proto_list.json +++ b/test/showcase-echo-client/src/v1beta1/echo_proto_list.json @@ -1,3 +1,4 @@ [ - "../../protos/google/showcase/v1beta1/echo.proto" + "../../protos/google/showcase/v1beta1/echo.proto", + "../../protos/google/showcase/v1beta1/sequence.proto" ] diff --git a/test/showcase-echo-client/src/v1beta1/index.ts b/test/showcase-echo-client/src/v1beta1/index.ts index eb1faebc8..be6bb2377 100644 --- a/test/showcase-echo-client/src/v1beta1/index.ts +++ b/test/showcase-echo-client/src/v1beta1/index.ts @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,3 +17,4 @@ // ** All changes to this file may be overwritten. ** export {EchoClient} from './echo_client'; +export {SequenceServiceClient} from './sequence_service_client'; diff --git a/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts new file mode 100644 index 000000000..1149d230a --- /dev/null +++ b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts @@ -0,0 +1,1182 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ** This file is automatically generated by gapic-generator-typescript. ** +// ** https://github.com/googleapis/gapic-generator-typescript ** +// ** All changes to this file may be overwritten. ** + +/* global window */ +import type * as gax from 'google-gax'; +import type { + Callback, + CallOptions, + Descriptors, + ClientOptions, + IamClient, + IamProtos, + LocationsClient, + LocationProtos, +} from 'google-gax'; +import {PassThrough} from 'stream'; +import * as protos from '../../protos/protos'; +import jsonProtos = require('../../protos/protos.json'); +/** + * Client JSON configuration object, loaded from + * `src/v1beta1/sequence_service_client_config.json`. + * This file defines retry strategy and timeouts for all API methods in this library. + */ +import * as gapicConfig from './sequence_service_client_config.json'; +const version = require('../../../package.json').version; + +/** + * @class + * @memberof v1beta1 + */ +export class SequenceServiceClient { + private _terminated = false; + private _opts: ClientOptions; + private _providedCustomServicePath: boolean; + private _gaxModule: typeof gax | typeof gax.fallback; + private _gaxGrpc: gax.GrpcClient | gax.fallback.GrpcClient; + private _protos: {}; + private _defaults: {[method: string]: gax.CallSettings}; + auth: gax.GoogleAuth; + descriptors: Descriptors = { + page: {}, + stream: {}, + longrunning: {}, + batching: {}, + }; + warn: (code: string, message: string, warnType?: string) => void; + innerApiCalls: {[name: string]: Function}; + iamClient: IamClient; + locationsClient: LocationsClient; + pathTemplates: {[name: string]: gax.PathTemplate}; + sequenceServiceStub?: Promise<{[name: string]: Function}>; + + /** + * Construct an instance of SequenceServiceClient. + * + * @param {object} [options] - The configuration object. + * The options accepted by the constructor are described in detail + * in [this document](https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#creating-the-client-instance). + * The common options are: + * @param {object} [options.credentials] - Credentials object. + * @param {string} [options.credentials.client_email] + * @param {string} [options.credentials.private_key] + * @param {string} [options.email] - Account email address. Required when + * using a .pem or .p12 keyFilename. + * @param {string} [options.keyFilename] - Full path to the a .json, .pem, or + * .p12 key downloaded from the Google Developers Console. If you provide + * a path to a JSON file, the projectId option below is not necessary. + * NOTE: .pem and .p12 require you to specify options.email as well. + * @param {number} [options.port] - The port on which to connect to + * the remote host. + * @param {string} [options.projectId] - The project ID from the Google + * Developer's Console, e.g. 'grape-spaceship-123'. We will also check + * the environment variable GCLOUD_PROJECT for your project ID. If your + * app is running in an environment which supports + * {@link https://developers.google.com/identity/protocols/application-default-credentials Application Default Credentials}, + * your project ID will be detected automatically. + * @param {string} [options.apiEndpoint] - The domain name of the + * API remote host. + * @param {gax.ClientConfig} [options.clientConfig] - Client configuration override. + * Follows the structure of {@link gapicConfig}. + * @param {boolean | "rest"} [options.fallback] - Use HTTP fallback mode. + * Pass "rest" to use HTTP/1.1 REST API instead of gRPC. + * For more information, please check the + * {@link https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#http11-rest-api-mode documentation}. + * @param {gax} [gaxInstance]: loaded instance of `google-gax`. Useful if you + * need to avoid loading the default gRPC version and want to use the fallback + * HTTP implementation. Load only fallback version and pass it to the constructor: + * ``` + * const gax = require('google-gax/build/src/fallback'); // avoids loading google-gax with gRPC + * const client = new SequenceServiceClient({fallback: 'rest'}, gax); + * ``` + */ + constructor( + opts?: ClientOptions, + gaxInstance?: typeof gax | typeof gax.fallback + ) { + // Ensure that options include all the required fields. + const staticMembers = this.constructor as typeof SequenceServiceClient; + const servicePath = + opts?.servicePath || opts?.apiEndpoint || staticMembers.servicePath; + this._providedCustomServicePath = !!( + opts?.servicePath || opts?.apiEndpoint + ); + const port = opts?.port || staticMembers.port; + const clientConfig = opts?.clientConfig ?? {}; + const fallback = + opts?.fallback ?? + (typeof window !== 'undefined' && typeof window?.fetch === 'function'); + opts = Object.assign({servicePath, port, clientConfig, fallback}, opts); + + // If scopes are unset in options and we're connecting to a non-default endpoint, set scopes just in case. + if (servicePath !== staticMembers.servicePath && !('scopes' in opts)) { + opts['scopes'] = staticMembers.scopes; + } + + // Load google-gax module synchronously if needed + if (!gaxInstance) { + gaxInstance = require('google-gax') as typeof gax; + } + + // Choose either gRPC or proto-over-HTTP implementation of google-gax. + this._gaxModule = opts.fallback ? gaxInstance.fallback : gaxInstance; + + // Create a `gaxGrpc` object, with any grpc-specific options sent to the client. + this._gaxGrpc = new this._gaxModule.GrpcClient(opts); + + // Save options to use in initialize() method. + this._opts = opts; + + // Save the auth object to the client, for use by other methods. + this.auth = this._gaxGrpc.auth as gax.GoogleAuth; + + // Set useJWTAccessWithScope on the auth object. + this.auth.useJWTAccessWithScope = true; + + // Set defaultServicePath on the auth object. + this.auth.defaultServicePath = staticMembers.servicePath; + + // Set the default scopes in auth client if needed. + if (servicePath === staticMembers.servicePath) { + this.auth.defaultScopes = staticMembers.scopes; + } + this.iamClient = new this._gaxModule.IamClient(this._gaxGrpc, opts); + + this.locationsClient = new this._gaxModule.LocationsClient( + this._gaxGrpc, + opts + ); + + // Determine the client header string. + const clientHeader = [`gax/${this._gaxModule.version}`, `gapic/${version}`]; + if (typeof process !== 'undefined' && 'versions' in process) { + clientHeader.push(`gl-node/${process.versions.node}`); + } else { + clientHeader.push(`gl-web/${this._gaxModule.version}`); + } + if (!opts.fallback) { + clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`); + } else if (opts.fallback === 'rest') { + clientHeader.push(`rest/${this._gaxGrpc.grpcVersion}`); + } + if (opts.libName && opts.libVersion) { + clientHeader.push(`${opts.libName}/${opts.libVersion}`); + } + // Load the applicable protos. + this._protos = this._gaxGrpc.loadProtoJSON(jsonProtos); + + // This API contains "path templates"; forward-slash-separated + // identifiers to uniquely identify resources within the API. + // Create useful helper objects for these. + this.pathTemplates = { + sequencePathTemplate: new this._gaxModule.PathTemplate( + 'sequences/{sequence}' + ), + sequenceReportPathTemplate: new this._gaxModule.PathTemplate( + 'sequences/{sequence}/sequenceReport' + ), + streamingSequencePathTemplate: new this._gaxModule.PathTemplate( + 'streamingSequences/{streaming_sequence}' + ), + streamingSequenceReportPathTemplate: new this._gaxModule.PathTemplate( + 'streamingSequences/{streaming_sequence}/streamingSequenceReport' + ), + }; + + // Some of the methods on this service provide streaming responses. + // Provide descriptors for these. + this.descriptors.stream = { + attemptStreamingSequence: new this._gaxModule.StreamDescriptor( + this._gaxModule.StreamType.SERVER_STREAMING, + opts.fallback === 'rest', + this._opts.newRetry + ), + }; + + // Put together the default options sent with requests. + this._defaults = this._gaxGrpc.constructSettings( + 'google.showcase.v1beta1.SequenceService', + gapicConfig as gax.ClientConfig, + opts.clientConfig || {}, + {'x-goog-api-client': clientHeader.join(' ')} + ); + + // Set up a dictionary of "inner API calls"; the core implementation + // of calling the API is handled in `google-gax`, with this code + // merely providing the destination and request information. + this.innerApiCalls = {}; + + // Add a warn function to the client constructor so it can be easily tested. + this.warn = this._gaxModule.warn; + } + + /** + * Initialize the client. + * Performs asynchronous operations (such as authentication) and prepares the client. + * This function will be called automatically when any class method is called for the + * first time, but if you need to initialize it before calling an actual method, + * feel free to call initialize() directly. + * + * You can await on this method if you want to make sure the client is initialized. + * + * @returns {Promise} A promise that resolves to an authenticated service stub. + */ + initialize() { + // If the client stub promise is already initialized, return immediately. + if (this.sequenceServiceStub) { + return this.sequenceServiceStub; + } + + // Put together the "service stub" for + // google.showcase.v1beta1.SequenceService. + this.sequenceServiceStub = this._gaxGrpc.createStub( + this._opts.fallback + ? (this._protos as protobuf.Root).lookupService( + 'google.showcase.v1beta1.SequenceService' + ) + : // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this._protos as any).google.showcase.v1beta1.SequenceService, + this._opts, + this._providedCustomServicePath + ) as Promise<{[method: string]: Function}>; + + // Iterate over each of the methods that the service provides + // and create an API call method for each. + const sequenceServiceStubMethods = [ + 'createSequence', + 'createStreamingSequence', + 'getSequenceReport', + 'getStreamingSequenceReport', + 'attemptSequence', + 'attemptStreamingSequence', + ]; + for (const methodName of sequenceServiceStubMethods) { + const callPromise = this.sequenceServiceStub.then( + stub => + (...args: Array<{}>) => { + if (this._terminated) { + if (methodName in this.descriptors.stream) { + const stream = new PassThrough(); + setImmediate(() => { + stream.emit( + 'error', + new this._gaxModule.GoogleError( + 'The client has already been closed.' + ) + ); + }); + return stream; + } + return Promise.reject('The client has already been closed.'); + } + const func = stub[methodName]; + return func.apply(stub, args); + }, + (err: Error | null | undefined) => () => { + throw err; + } + ); + + const descriptor = this.descriptors.stream[methodName] || undefined; + const apiCall = this._gaxModule.createApiCall( + callPromise, + this._defaults[methodName], + descriptor, + this._opts.fallback + ); + + this.innerApiCalls[methodName] = apiCall; + } + + return this.sequenceServiceStub; + } + + /** + * The DNS address for this API service. + * @returns {string} The DNS address for this service. + */ + static get servicePath() { + return 'localhost'; + } + + /** + * The DNS address for this API service - same as servicePath(), + * exists for compatibility reasons. + * @returns {string} The DNS address for this service. + */ + static get apiEndpoint() { + return 'localhost'; + } + + /** + * The port for this API service. + * @returns {number} The default port for this service. + */ + static get port() { + return 7469; + } + + /** + * The scopes needed to make gRPC calls for every method defined + * in this service. + * @returns {string[]} List of default scopes. + */ + static get scopes() { + return []; + } + + getProjectId(): Promise; + getProjectId(callback: Callback): void; + /** + * Return the project ID used by this class. + * @returns {Promise} A promise that resolves to string containing the project ID. + */ + getProjectId( + callback?: Callback + ): Promise | void { + if (callback) { + this.auth.getProjectId(callback); + return; + } + return this.auth.getProjectId(); + } + + // ------------------- + // -- Service calls -- + // ------------------- + /** + * Creates a sequence. + * + * @param {Object} request + * The request object that will be sent. + * @param {google.showcase.v1beta1.Sequence} request.sequence + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.showcase.v1beta1.Sequence|Sequence}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1beta1/sequence_service.create_sequence.js + * region_tag:localhost_v1beta1_generated_SequenceService_CreateSequence_async + */ + createSequence( + request?: protos.google.showcase.v1beta1.ICreateSequenceRequest, + options?: CallOptions + ): Promise< + [ + protos.google.showcase.v1beta1.ISequence, + protos.google.showcase.v1beta1.ICreateSequenceRequest | undefined, + {} | undefined + ] + >; + createSequence( + request: protos.google.showcase.v1beta1.ICreateSequenceRequest, + options: CallOptions, + callback: Callback< + protos.google.showcase.v1beta1.ISequence, + protos.google.showcase.v1beta1.ICreateSequenceRequest | null | undefined, + {} | null | undefined + > + ): void; + createSequence( + request: protos.google.showcase.v1beta1.ICreateSequenceRequest, + callback: Callback< + protos.google.showcase.v1beta1.ISequence, + protos.google.showcase.v1beta1.ICreateSequenceRequest | null | undefined, + {} | null | undefined + > + ): void; + createSequence( + request?: protos.google.showcase.v1beta1.ICreateSequenceRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.showcase.v1beta1.ISequence, + | protos.google.showcase.v1beta1.ICreateSequenceRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.showcase.v1beta1.ISequence, + protos.google.showcase.v1beta1.ICreateSequenceRequest | null | undefined, + {} | null | undefined + > + ): Promise< + [ + protos.google.showcase.v1beta1.ISequence, + protos.google.showcase.v1beta1.ICreateSequenceRequest | undefined, + {} | undefined + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + this.initialize(); + return this.innerApiCalls.createSequence(request, options, callback); + } + /** + * Creates a sequence. + * + * @param {Object} request + * The request object that will be sent. + * @param {google.showcase.v1beta1.StreamingSequence} request.streamingSequence + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.showcase.v1beta1.StreamingSequence|StreamingSequence}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1beta1/sequence_service.create_streaming_sequence.js + * region_tag:localhost_v1beta1_generated_SequenceService_CreateStreamingSequence_async + */ + createStreamingSequence( + request?: protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest, + options?: CallOptions + ): Promise< + [ + protos.google.showcase.v1beta1.IStreamingSequence, + ( + | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest + | undefined + ), + {} | undefined + ] + >; + createStreamingSequence( + request: protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest, + options: CallOptions, + callback: Callback< + protos.google.showcase.v1beta1.IStreamingSequence, + | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest + | null + | undefined, + {} | null | undefined + > + ): void; + createStreamingSequence( + request: protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest, + callback: Callback< + protos.google.showcase.v1beta1.IStreamingSequence, + | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest + | null + | undefined, + {} | null | undefined + > + ): void; + createStreamingSequence( + request?: protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.showcase.v1beta1.IStreamingSequence, + | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.showcase.v1beta1.IStreamingSequence, + | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest + | null + | undefined, + {} | null | undefined + > + ): Promise< + [ + protos.google.showcase.v1beta1.IStreamingSequence, + ( + | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest + | undefined + ), + {} | undefined + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + this.initialize(); + return this.innerApiCalls.createStreamingSequence( + request, + options, + callback + ); + } + /** + * Retrieves a sequence. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.name + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.showcase.v1beta1.SequenceReport|SequenceReport}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1beta1/sequence_service.get_sequence_report.js + * region_tag:localhost_v1beta1_generated_SequenceService_GetSequenceReport_async + */ + getSequenceReport( + request?: protos.google.showcase.v1beta1.IGetSequenceReportRequest, + options?: CallOptions + ): Promise< + [ + protos.google.showcase.v1beta1.ISequenceReport, + protos.google.showcase.v1beta1.IGetSequenceReportRequest | undefined, + {} | undefined + ] + >; + getSequenceReport( + request: protos.google.showcase.v1beta1.IGetSequenceReportRequest, + options: CallOptions, + callback: Callback< + protos.google.showcase.v1beta1.ISequenceReport, + | protos.google.showcase.v1beta1.IGetSequenceReportRequest + | null + | undefined, + {} | null | undefined + > + ): void; + getSequenceReport( + request: protos.google.showcase.v1beta1.IGetSequenceReportRequest, + callback: Callback< + protos.google.showcase.v1beta1.ISequenceReport, + | protos.google.showcase.v1beta1.IGetSequenceReportRequest + | null + | undefined, + {} | null | undefined + > + ): void; + getSequenceReport( + request?: protos.google.showcase.v1beta1.IGetSequenceReportRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.showcase.v1beta1.ISequenceReport, + | protos.google.showcase.v1beta1.IGetSequenceReportRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.showcase.v1beta1.ISequenceReport, + | protos.google.showcase.v1beta1.IGetSequenceReportRequest + | null + | undefined, + {} | null | undefined + > + ): Promise< + [ + protos.google.showcase.v1beta1.ISequenceReport, + protos.google.showcase.v1beta1.IGetSequenceReportRequest | undefined, + {} | undefined + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams({ + name: request.name ?? '', + }); + this.initialize(); + return this.innerApiCalls.getSequenceReport(request, options, callback); + } + /** + * Retrieves a sequence. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.name + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.showcase.v1beta1.StreamingSequenceReport|StreamingSequenceReport}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1beta1/sequence_service.get_streaming_sequence_report.js + * region_tag:localhost_v1beta1_generated_SequenceService_GetStreamingSequenceReport_async + */ + getStreamingSequenceReport( + request?: protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest, + options?: CallOptions + ): Promise< + [ + protos.google.showcase.v1beta1.IStreamingSequenceReport, + ( + | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest + | undefined + ), + {} | undefined + ] + >; + getStreamingSequenceReport( + request: protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest, + options: CallOptions, + callback: Callback< + protos.google.showcase.v1beta1.IStreamingSequenceReport, + | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest + | null + | undefined, + {} | null | undefined + > + ): void; + getStreamingSequenceReport( + request: protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest, + callback: Callback< + protos.google.showcase.v1beta1.IStreamingSequenceReport, + | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest + | null + | undefined, + {} | null | undefined + > + ): void; + getStreamingSequenceReport( + request?: protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.showcase.v1beta1.IStreamingSequenceReport, + | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.showcase.v1beta1.IStreamingSequenceReport, + | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest + | null + | undefined, + {} | null | undefined + > + ): Promise< + [ + protos.google.showcase.v1beta1.IStreamingSequenceReport, + ( + | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest + | undefined + ), + {} | undefined + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams({ + name: request.name ?? '', + }); + this.initialize(); + return this.innerApiCalls.getStreamingSequenceReport( + request, + options, + callback + ); + } + /** + * Attempts a sequence. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.name + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link protos.google.protobuf.Empty|Empty}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example include:samples/generated/v1beta1/sequence_service.attempt_sequence.js + * region_tag:localhost_v1beta1_generated_SequenceService_AttemptSequence_async + */ + attemptSequence( + request?: protos.google.showcase.v1beta1.IAttemptSequenceRequest, + options?: CallOptions + ): Promise< + [ + protos.google.protobuf.IEmpty, + protos.google.showcase.v1beta1.IAttemptSequenceRequest | undefined, + {} | undefined + ] + >; + attemptSequence( + request: protos.google.showcase.v1beta1.IAttemptSequenceRequest, + options: CallOptions, + callback: Callback< + protos.google.protobuf.IEmpty, + protos.google.showcase.v1beta1.IAttemptSequenceRequest | null | undefined, + {} | null | undefined + > + ): void; + attemptSequence( + request: protos.google.showcase.v1beta1.IAttemptSequenceRequest, + callback: Callback< + protos.google.protobuf.IEmpty, + protos.google.showcase.v1beta1.IAttemptSequenceRequest | null | undefined, + {} | null | undefined + > + ): void; + attemptSequence( + request?: protos.google.showcase.v1beta1.IAttemptSequenceRequest, + optionsOrCallback?: + | CallOptions + | Callback< + protos.google.protobuf.IEmpty, + | protos.google.showcase.v1beta1.IAttemptSequenceRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + protos.google.protobuf.IEmpty, + protos.google.showcase.v1beta1.IAttemptSequenceRequest | null | undefined, + {} | null | undefined + > + ): Promise< + [ + protos.google.protobuf.IEmpty, + protos.google.showcase.v1beta1.IAttemptSequenceRequest | undefined, + {} | undefined + ] + > | void { + request = request || {}; + let options: CallOptions; + if (typeof optionsOrCallback === 'function' && callback === undefined) { + callback = optionsOrCallback; + options = {}; + } else { + options = optionsOrCallback as CallOptions; + } + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams({ + name: request.name ?? '', + }); + this.initialize(); + return this.innerApiCalls.attemptSequence(request, options, callback); + } + + /** + * Attempts a streaming sequence. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.name + * @param {number} [request.lastFailIndex] + * used to send the index of the last failed message + * in the string "content" of an AttemptStreamingSequenceResponse + * needed for stream resumption logic testing + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Stream} + * An object stream which emits {@link protos.google.showcase.v1beta1.AttemptStreamingSequenceResponse|AttemptStreamingSequenceResponse} on 'data' event. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#server-streaming | documentation } + * for more details and examples. + * @example include:samples/generated/v1beta1/sequence_service.attempt_streaming_sequence.js + * region_tag:localhost_v1beta1_generated_SequenceService_AttemptStreamingSequence_async + */ + attemptStreamingSequence( + request?: protos.google.showcase.v1beta1.IAttemptStreamingSequenceRequest, + options?: CallOptions + ): gax.CancellableStream { + request = request || {}; + options = options || {}; + options.otherArgs = options.otherArgs || {}; + options.otherArgs.headers = options.otherArgs.headers || {}; + options.otherArgs.headers['x-goog-request-params'] = + this._gaxModule.routingHeader.fromParams({ + name: request.name ?? '', + }); + this.initialize(); + return this.innerApiCalls.attemptStreamingSequence(request, options); + } + + /** + * Gets the access control policy for a resource. Returns an empty policy + * if the resource exists and does not have a policy set. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.resource + * REQUIRED: The resource for which the policy is being requested. + * See the operation documentation for the appropriate value for this field. + * @param {Object} [request.options] + * OPTIONAL: A `GetPolicyOptions` object for specifying options to + * `GetIamPolicy`. This field is only used by Cloud IAM. + * + * This object should have the same structure as {@link google.iam.v1.GetPolicyOptions | GetPolicyOptions}. + * @param {Object} [options] + * Optional parameters. You can override the default settings for this call, e.g, timeout, + * retries, paginations, etc. See {@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html | gax.CallOptions} for the details. + * @param {function(?Error, ?Object)} [callback] + * The function which will be called with the result of the API call. + * + * The second parameter to the callback is an object representing {@link google.iam.v1.Policy | Policy}. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link google.iam.v1.Policy | Policy}. + * The promise has a method named "cancel" which cancels the ongoing API call. + */ + getIamPolicy( + request: IamProtos.google.iam.v1.GetIamPolicyRequest, + options?: + | gax.CallOptions + | Callback< + IamProtos.google.iam.v1.Policy, + IamProtos.google.iam.v1.GetIamPolicyRequest | null | undefined, + {} | null | undefined + >, + callback?: Callback< + IamProtos.google.iam.v1.Policy, + IamProtos.google.iam.v1.GetIamPolicyRequest | null | undefined, + {} | null | undefined + > + ): Promise<[IamProtos.google.iam.v1.Policy]> { + return this.iamClient.getIamPolicy(request, options, callback); + } + + /** + * Returns permissions that a caller has on the specified resource. If the + * resource does not exist, this will return an empty set of + * permissions, not a NOT_FOUND error. + * + * Note: This operation is designed to be used for building + * permission-aware UIs and command-line tools, not for authorization + * checking. This operation may "fail open" without warning. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.resource + * REQUIRED: The resource for which the policy detail is being requested. + * See the operation documentation for the appropriate value for this field. + * @param {string[]} request.permissions + * The set of permissions to check for the `resource`. Permissions with + * wildcards (such as '*' or 'storage.*') are not allowed. For more + * information see {@link https://cloud.google.com/iam/docs/overview#permissions | IAM Overview }. + * @param {Object} [options] + * Optional parameters. You can override the default settings for this call, e.g, timeout, + * retries, paginations, etc. See {@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html | gax.CallOptions} for the details. + * @param {function(?Error, ?Object)} [callback] + * The function which will be called with the result of the API call. + * + * The second parameter to the callback is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. + * The promise has a method named "cancel" which cancels the ongoing API call. + */ + setIamPolicy( + request: IamProtos.google.iam.v1.SetIamPolicyRequest, + options?: + | gax.CallOptions + | Callback< + IamProtos.google.iam.v1.Policy, + IamProtos.google.iam.v1.SetIamPolicyRequest | null | undefined, + {} | null | undefined + >, + callback?: Callback< + IamProtos.google.iam.v1.Policy, + IamProtos.google.iam.v1.SetIamPolicyRequest | null | undefined, + {} | null | undefined + > + ): Promise<[IamProtos.google.iam.v1.Policy]> { + return this.iamClient.setIamPolicy(request, options, callback); + } + + /** + * Returns permissions that a caller has on the specified resource. If the + * resource does not exist, this will return an empty set of + * permissions, not a NOT_FOUND error. + * + * Note: This operation is designed to be used for building + * permission-aware UIs and command-line tools, not for authorization + * checking. This operation may "fail open" without warning. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.resource + * REQUIRED: The resource for which the policy detail is being requested. + * See the operation documentation for the appropriate value for this field. + * @param {string[]} request.permissions + * The set of permissions to check for the `resource`. Permissions with + * wildcards (such as '*' or 'storage.*') are not allowed. For more + * information see {@link https://cloud.google.com/iam/docs/overview#permissions | IAM Overview }. + * @param {Object} [options] + * Optional parameters. You can override the default settings for this call, e.g, timeout, + * retries, paginations, etc. See {@link https://googleapis.github.io/gax-nodejs/interfaces/CallOptions.html | gax.CallOptions} for the details. + * @param {function(?Error, ?Object)} [callback] + * The function which will be called with the result of the API call. + * + * The second parameter to the callback is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link google.iam.v1.TestIamPermissionsResponse | TestIamPermissionsResponse}. + * The promise has a method named "cancel" which cancels the ongoing API call. + * + */ + testIamPermissions( + request: IamProtos.google.iam.v1.TestIamPermissionsRequest, + options?: + | gax.CallOptions + | Callback< + IamProtos.google.iam.v1.TestIamPermissionsResponse, + IamProtos.google.iam.v1.TestIamPermissionsRequest | null | undefined, + {} | null | undefined + >, + callback?: Callback< + IamProtos.google.iam.v1.TestIamPermissionsResponse, + IamProtos.google.iam.v1.TestIamPermissionsRequest | null | undefined, + {} | null | undefined + > + ): Promise<[IamProtos.google.iam.v1.TestIamPermissionsResponse]> { + return this.iamClient.testIamPermissions(request, options, callback); + } + + /** + * Gets information about a location. + * + * @param {Object} request + * The request object that will be sent. + * @param {string} request.name + * Resource name for the location. + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html | CallOptions} for more details. + * @returns {Promise} - The promise which resolves to an array. + * The first element of the array is an object representing {@link google.cloud.location.Location | Location}. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#regular-methods | documentation } + * for more details and examples. + * @example + * ``` + * const [response] = await client.getLocation(request); + * ``` + */ + getLocation( + request: LocationProtos.google.cloud.location.IGetLocationRequest, + options?: + | gax.CallOptions + | Callback< + LocationProtos.google.cloud.location.ILocation, + | LocationProtos.google.cloud.location.IGetLocationRequest + | null + | undefined, + {} | null | undefined + >, + callback?: Callback< + LocationProtos.google.cloud.location.ILocation, + | LocationProtos.google.cloud.location.IGetLocationRequest + | null + | undefined, + {} | null | undefined + > + ): Promise { + return this.locationsClient.getLocation(request, options, callback); + } + + /** + * Lists information about the supported locations for this service. Returns an iterable object. + * + * `for`-`await`-`of` syntax is used with the iterable to get response elements on-demand. + * @param {Object} request + * The request object that will be sent. + * @param {string} request.name + * The resource that owns the locations collection, if applicable. + * @param {string} request.filter + * The standard list filter. + * @param {number} request.pageSize + * The standard list page size. + * @param {string} request.pageToken + * The standard list page token. + * @param {object} [options] + * Call options. See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} for more details. + * @returns {Object} + * An iterable Object that allows {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols | async iteration }. + * When you iterate the returned iterable, each element will be an object representing + * {@link google.cloud.location.Location | Location}. The API will be called under the hood as needed, once per the page, + * so you can stop the iteration when you don't need more results. + * Please see the {@link https://github.com/googleapis/gax-nodejs/blob/master/client-libraries.md#auto-pagination | documentation } + * for more details and examples. + * @example + * ``` + * const iterable = client.listLocationsAsync(request); + * for await (const response of iterable) { + * // process response + * } + * ``` + */ + listLocationsAsync( + request: LocationProtos.google.cloud.location.IListLocationsRequest, + options?: CallOptions + ): AsyncIterable { + return this.locationsClient.listLocationsAsync(request, options); + } + + // -------------------- + // -- Path templates -- + // -------------------- + + /** + * Return a fully-qualified sequence resource name string. + * + * @param {string} sequence + * @returns {string} Resource name string. + */ + sequencePath(sequence: string) { + return this.pathTemplates.sequencePathTemplate.render({ + sequence: sequence, + }); + } + + /** + * Parse the sequence from Sequence resource. + * + * @param {string} sequenceName + * A fully-qualified path representing Sequence resource. + * @returns {string} A string representing the sequence. + */ + matchSequenceFromSequenceName(sequenceName: string) { + return this.pathTemplates.sequencePathTemplate.match(sequenceName).sequence; + } + + /** + * Return a fully-qualified sequenceReport resource name string. + * + * @param {string} sequence + * @returns {string} Resource name string. + */ + sequenceReportPath(sequence: string) { + return this.pathTemplates.sequenceReportPathTemplate.render({ + sequence: sequence, + }); + } + + /** + * Parse the sequence from SequenceReport resource. + * + * @param {string} sequenceReportName + * A fully-qualified path representing SequenceReport resource. + * @returns {string} A string representing the sequence. + */ + matchSequenceFromSequenceReportName(sequenceReportName: string) { + return this.pathTemplates.sequenceReportPathTemplate.match( + sequenceReportName + ).sequence; + } + + /** + * Return a fully-qualified streamingSequence resource name string. + * + * @param {string} streaming_sequence + * @returns {string} Resource name string. + */ + streamingSequencePath(streamingSequence: string) { + return this.pathTemplates.streamingSequencePathTemplate.render({ + streaming_sequence: streamingSequence, + }); + } + + /** + * Parse the streaming_sequence from StreamingSequence resource. + * + * @param {string} streamingSequenceName + * A fully-qualified path representing StreamingSequence resource. + * @returns {string} A string representing the streaming_sequence. + */ + matchStreamingSequenceFromStreamingSequenceName( + streamingSequenceName: string + ) { + return this.pathTemplates.streamingSequencePathTemplate.match( + streamingSequenceName + ).streaming_sequence; + } + + /** + * Return a fully-qualified streamingSequenceReport resource name string. + * + * @param {string} streaming_sequence + * @returns {string} Resource name string. + */ + streamingSequenceReportPath(streamingSequence: string) { + return this.pathTemplates.streamingSequenceReportPathTemplate.render({ + streaming_sequence: streamingSequence, + }); + } + + /** + * Parse the streaming_sequence from StreamingSequenceReport resource. + * + * @param {string} streamingSequenceReportName + * A fully-qualified path representing StreamingSequenceReport resource. + * @returns {string} A string representing the streaming_sequence. + */ + matchStreamingSequenceFromStreamingSequenceReportName( + streamingSequenceReportName: string + ) { + return this.pathTemplates.streamingSequenceReportPathTemplate.match( + streamingSequenceReportName + ).streaming_sequence; + } + + /** + * Terminate the gRPC channel and close the client. + * + * The client will no longer be usable and all future behavior is undefined. + * @returns {Promise} A promise that resolves when the client is closed. + */ + close(): Promise { + if (this.sequenceServiceStub && !this._terminated) { + return this.sequenceServiceStub.then(stub => { + this._terminated = true; + stub.close(); + this.iamClient.close(); + this.locationsClient.close(); + }); + } + return Promise.resolve(); + } +} diff --git a/test/showcase-echo-client/src/v1beta1/sequence_service_client_config.json b/test/showcase-echo-client/src/v1beta1/sequence_service_client_config.json new file mode 100644 index 000000000..955c6fcec --- /dev/null +++ b/test/showcase-echo-client/src/v1beta1/sequence_service_client_config.json @@ -0,0 +1,50 @@ +{ + "interfaces": { + "google.showcase.v1beta1.SequenceService": { + "retry_codes": { + "non_idempotent": [], + "idempotent": [ + "DEADLINE_EXCEEDED", + "UNAVAILABLE" + ] + }, + "retry_params": { + "default": { + "initial_retry_delay_millis": 100, + "retry_delay_multiplier": 1.3, + "max_retry_delay_millis": 60000, + "initial_rpc_timeout_millis": 60000, + "rpc_timeout_multiplier": 1, + "max_rpc_timeout_millis": 60000, + "total_timeout_millis": 600000 + } + }, + "methods": { + "CreateSequence": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "CreateStreamingSequence": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "GetSequenceReport": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "GetStreamingSequenceReport": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "AttemptSequence": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + }, + "AttemptStreamingSequence": { + "retry_codes_name": "non_idempotent", + "retry_params_name": "default" + } + } + } + } +} diff --git a/test/showcase-echo-client/src/v1beta1/sequence_service_proto_list.json b/test/showcase-echo-client/src/v1beta1/sequence_service_proto_list.json new file mode 100644 index 000000000..76b3da61a --- /dev/null +++ b/test/showcase-echo-client/src/v1beta1/sequence_service_proto_list.json @@ -0,0 +1,4 @@ +[ + "../../protos/google/showcase/v1beta1/echo.proto", + "../../protos/google/showcase/v1beta1/sequence.proto" +] diff --git a/test/showcase-server/package.json b/test/showcase-server/package.json index 0568a8651..f8d4dbfe5 100644 --- a/test/showcase-server/package.json +++ b/test/showcase-server/package.json @@ -19,7 +19,7 @@ "dependencies": { "download": "^8.0.0", "execa": "^5.0.0", - "rimraf": "^3.0.0" + "rimraf": "^5.0.1" }, "devDependencies": { "@types/download": "^8.0.1", diff --git a/test/showcase-server/src/index.ts b/test/showcase-server/src/index.ts index c67405814..bf1ff6ab7 100644 --- a/test/showcase-server/src/index.ts +++ b/test/showcase-server/src/index.ts @@ -18,11 +18,11 @@ import * as execa from 'execa'; import * as download from 'download'; import * as fs from 'fs'; import * as path from 'path'; -import * as rimraf from 'rimraf'; +import {rimraf} from 'rimraf'; import * as util from 'util'; const mkdir = util.promisify(fs.mkdir); -const rmrf = util.promisify(rimraf); + const timeout = 5000; // wait after the server launches function sleep(timeoutMs: number) { @@ -36,12 +36,12 @@ export class ShowcaseServer { const testDir = path.join(process.cwd(), '.showcase-server-dir'); const platform = process.platform; const arch = process.arch === 'x64' ? 'amd64' : process.arch; - const showcaseVersion = process.env['SHOWCASE_VERSION'] || '0.23.0'; + const showcaseVersion = process.env['SHOWCASE_VERSION'] || '0.28.4'; const tarballFilename = `gapic-showcase-${showcaseVersion}-${platform}-${arch}.tar.gz`; const fallbackServerUrl = `https://github.com/googleapis/gapic-showcase/releases/download/v${showcaseVersion}/${tarballFilename}`; const binaryName = './gapic-showcase'; - await rmrf(testDir); + await rimraf(testDir); await mkdir(testDir); process.chdir(testDir); console.log(`Server will be run from ${testDir}.`); diff --git a/test/system-test/test.clientlibs.ts b/test/system-test/test.clientlibs.ts index efe822b80..9391df900 100644 --- a/test/system-test/test.clientlibs.ts +++ b/test/system-test/test.clientlibs.ts @@ -17,11 +17,10 @@ import * as execa from 'execa'; import * as fs from 'fs'; import * as path from 'path'; -import * as rimraf from 'rimraf'; +import {rimraf} from 'rimraf'; import * as util from 'util'; import {describe, it, before} from 'mocha'; -const rmrf = util.promisify(rimraf); const mkdir = util.promisify(fs.mkdir); const readFile = util.promisify(fs.readFile); const writeFile = util.promisify(fs.writeFile); @@ -38,6 +37,9 @@ const gaxDir = path.resolve(__dirname, '..', '..', '..'); // eslint-disable-next-line @typescript-eslint/no-var-requires const pkg = require('../../../package.json'); const gaxTarball = path.join(gaxDir, `${pkg.name}-${pkg.version}.tgz`); +const toolsPkg = require('../../../tools/package.json'); +const toolsBasename = `${toolsPkg.name}-${toolsPkg.version}.tgz`; +const toolsTarball = path.join(gaxDir, toolsBasename); async function latestRelease( cwd: string, @@ -121,8 +123,7 @@ async function preparePackage( const packageJsonStr = (await readFile(packageJson)).toString(); const packageJsonObj = JSON.parse(packageJsonStr); packageJsonObj['dependencies']['google-gax'] = `file:${gaxTarball}`; - await writeFile(packageJson, JSON.stringify(packageJsonObj, null, ' ')); - packageJsonObj['dependencies']['google-gax'] = `file:${gaxTarball}`; + packageJsonObj['devDependencies']['gapic-tools'] = `file:${toolsTarball}`; await writeFile(packageJson, JSON.stringify(packageJsonObj, null, ' ')); await execa('npm', ['install'], { cwd: inMonorepo ? packagePath : packageName, @@ -176,25 +177,40 @@ describe('Run system tests for some libraries', () => { before(async () => { console.log('Packing google-gax...'); await execa('npm', ['pack'], {cwd: gaxDir, stdio: 'inherit'}); - if (!fs.existsSync(gaxTarball)) { throw new Error(`npm pack tarball ${gaxTarball} does not exist`); } + console.log('Packing gapic-tools...'); + await execa('npm', ['install'], { + cwd: path.join(gaxDir, 'tools'), + stdio: 'inherit', + }); + await execa('npm', ['pack'], { + cwd: path.join(gaxDir, 'tools'), + stdio: 'inherit', + }); + await fs.promises.rename( + path.join(gaxDir, 'tools', toolsBasename), + toolsTarball + ); + if (!fs.existsSync(toolsTarball)) { + throw new Error(`npm pack tarball ${toolsTarball} does not exist`); + } - await rmrf(testDir); + await rimraf(testDir); await mkdir(testDir); process.chdir(testDir); console.log(`Running tests in ${testDir}.`); }); // Speech has unary, LRO, and streaming - // Speech is not in the monorepo + // Speech is in the google-cloud-node monorepo describe('speech', () => { before(async () => { - await preparePackage('nodejs-speech', false); + await preparePackage('speech', true); }); it('should pass system tests', async function () { - const result = await runSystemTest('nodejs-speech', false); + const result = await runSystemTest('speech', true); if (result === TestResult.SKIP) { this.skip(); } else if (result === TestResult.FAIL) { @@ -205,7 +221,8 @@ describe('Run system tests for some libraries', () => { // KMS api has IAM service injected from gax. All its IAM related test are in samples-test. // KMS is in the google-cloud-node monorepo - describe('kms', () => { + // Temporarily skipped to avoid circular dependency issue. + /*describe('kms', () => { before(async () => { await preparePackage('kms', true); }); @@ -217,5 +234,5 @@ describe('Run system tests for some libraries', () => { throw new Error('Test failed'); } }); - }); + });*/ }); diff --git a/test/test-application/package.json b/test/test-application/package.json index c2ad41876..3fc47060c 100644 --- a/test/test-application/package.json +++ b/test/test-application/package.json @@ -20,8 +20,8 @@ "start": "node build/src/index.js" }, "devDependencies": { - "mocha": "^9.1.4", - "typescript": "^4.5.5" + "mocha": "^10.0.1", + "typescript": "^5.1.6" }, "dependencies": { "@grpc/grpc-js": "~1.6.0", diff --git a/test/test-application/src/index.ts b/test/test-application/src/index.ts index 3f77d5ea3..a11abd838 100644 --- a/test/test-application/src/index.ts +++ b/test/test-application/src/index.ts @@ -15,13 +15,21 @@ */ 'use strict'; -import {EchoClient} from 'showcase-echo-client'; +import {EchoClient, SequenceServiceClient, protos} from 'showcase-echo-client'; import {ShowcaseServer} from 'showcase-server'; import * as assert from 'assert'; import {promises as fsp} from 'fs'; import * as path from 'path'; -import {protobuf, grpc, GoogleError, GoogleAuth} from 'google-gax'; +import { + protobuf, + grpc, + GoogleError, + GoogleAuth, + Status, + createBackoffSettings, + RetryOptions, +} from 'google-gax'; async function testShowcase() { const grpcClientOpts = { @@ -29,6 +37,12 @@ async function testShowcase() { sslCreds: grpc.credentials.createInsecure(), }; + const grpcClientOptsWithNewRetry = { + grpc, + sslCreds: grpc.credentials.createInsecure(), + newRetry: true, + }; + const fakeGoogleAuth = { getClient: async () => { return { @@ -41,14 +55,14 @@ async function testShowcase() { }, } as unknown as GoogleAuth; - const fallbackClientOpts = { + const restClientOpts = { fallback: true, protocol: 'http', - port: 1337, + port: 7469, auth: fakeGoogleAuth, }; - const restClientOpts = { + const restClientOptsCompat = { fallback: 'rest' as const, protocol: 'http', port: 7469, @@ -56,8 +70,14 @@ async function testShowcase() { }; const grpcClient = new EchoClient(grpcClientOpts); - const fallbackClient = new EchoClient(fallbackClientOpts); + + const grpcClientWithNewRetry = new EchoClient(grpcClientOptsWithNewRetry); + const grpcSequenceClientWithNewRetry = new SequenceServiceClient( + grpcClientOptsWithNewRetry + ); + const restClient = new EchoClient(restClientOpts); + const restClientCompat = new EchoClient(restClientOptsCompat); // assuming gRPC server is started locally await testEcho(grpcClient); @@ -69,85 +89,98 @@ async function testShowcase() { await testChat(grpcClient); await testWait(grpcClient); - await testEcho(fallbackClient); - await testEchoError(fallbackClient); - await testExpandThrows(fallbackClient); // fallback does not support server streaming - await testPagedExpand(fallbackClient); - await testPagedExpandAsync(fallbackClient); - await testCollectThrows(fallbackClient); // fallback does not support client streaming - await testChatThrows(fallbackClient); // fallback does not support bidi streaming - await testWait(fallbackClient); - - // await testEcho(fallbackClient); - // await testEchoError(fallbackClient); - // await testExpandThrows(fallbackClient); // fallback does not support server streaming - // await testPagedExpand(fallbackClient); - // await testPagedExpandAsync(fallbackClient); - // await testCollectThrows(fallbackClient); // fallback does not support client streaming - // await testChatThrows(fallbackClient); // fallback does not support bidi streaming - // await testWait(fallbackClient); - - // await testEcho(restClient); - // await testExpand(restClient); // REGAPIC supports server streaming - // await testPagedExpand(restClient); - // await testPagedExpandAsync(restClient); - // await testCollectThrows(restClient); // REGAPIC does not support client streaming - // await testChatThrows(restClient); // REGAPIC does not support bidi streaming - // await testWait(restClient); -} + await testEcho(restClient); + await testExpand(restClient); // REGAPIC supports server streaming + await testPagedExpand(restClient); + await testPagedExpandAsync(restClient); + await testCollectThrows(restClient); // REGAPIC does not support client streaming + await testChatThrows(restClient); // REGAPIC does not support bidi streaming + await testWait(restClient); + + await testEcho(restClientCompat); + await testExpand(restClientCompat); // REGAPIC supports server streaming + await testPagedExpand(restClientCompat); + await testPagedExpandAsync(restClientCompat); + await testCollectThrows(restClientCompat); // REGAPIC does not support client streaming + await testChatThrows(restClientCompat); // REGAPIC does not support bidi streaming + await testWait(restClientCompat); + // Testing with newRetry being true + await testServerStreamingRetryOptions(grpcSequenceClientWithNewRetry); + + await testServerStreamingRetriesWithShouldRetryFn( + grpcSequenceClientWithNewRetry + ); -function getStreamingSequenceRequest(){ - const request = new protos.google.showcase.v1beta1.CreateStreamingSequenceRequest() + await testServerStreamingRetrieswithRetryOptions( + grpcSequenceClientWithNewRetry + ); - let firstDelay = new protos.google.protobuf.Duration(); - firstDelay.nanos=150; + await testServerStreamingRetrieswithRetryRequestOptions( + grpcSequenceClientWithNewRetry + ); - let firstStatus = new protos.google.rpc.Status(); - firstStatus.code=14; - firstStatus.message="UNAVAILABLE"; + await testServerStreamingRetrieswithRetryRequestOptionsResumptionStrategy( + grpcSequenceClientWithNewRetry + ); - let firstResponse = new protos.google.showcase.v1beta1.StreamingSequence.Response(); - firstResponse.delay=firstDelay; - firstResponse.status=firstStatus; + await testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResumptionStrategy( + grpcSequenceClientWithNewRetry + ); - // The Index you want the stream to fail or send the status - // This should be index + 1 so if you want to send status at index 0 - // you would provide firstResponse.sendStatusAtIndex=1 + await testServerStreamingThrowsClassifiedTransientError( + grpcSequenceClientWithNewRetry + ); - firstResponse.sendStatusAtIndex=1; - - let secondDelay = new protos.google.protobuf.Duration(); - secondDelay.nanos=150; + await testServerStreamingRetriesAndThrowsClassifiedTransientError( + grpcSequenceClientWithNewRetry + ); - let secondStatus = new protos.google.rpc.Status(); - secondStatus.code= 4; - secondStatus.message="DEADLINE_EXCEEDED"; + await testServerStreamingThrowsCannotSetTotalTimeoutMillisMaxRetries( + grpcSequenceClientWithNewRetry + ); + + await testEcho(grpcClientWithNewRetry); + await testEchoError(grpcClientWithNewRetry); + await testExpand(grpcClientWithNewRetry); + await testPagedExpand(grpcClientWithNewRetry); + await testPagedExpandAsync(grpcClientWithNewRetry); + await testCollect(grpcClientWithNewRetry); + await testChat(grpcClientWithNewRetry); + await testWait(grpcClientWithNewRetry); +} - let secondResponse = new protos.google.showcase.v1beta1.StreamingSequence.Response(); - secondResponse.delay=secondDelay; - secondResponse.status=secondStatus; - secondResponse.sendStatusAtIndex=2 +function createStreamingSequenceRequestFactory( + statusCodeList: Status[], + delayList: number[], + failIndexs: number[], + content: string +) { + const request = + new protos.google.showcase.v1beta1.CreateStreamingSequenceRequest(); + const streamingSequence = + new protos.google.showcase.v1beta1.StreamingSequence(); - let thirdDelay = new protos.google.protobuf.Duration(); - thirdDelay.nanos=500000; + for (let i = 0; i < statusCodeList.length; i++) { + const delay = new protos.google.protobuf.Duration(); + delay.seconds = delayList[i]; - let thirdStatus = new protos.google.rpc.Status(); - thirdStatus.code=0; - thirdStatus.message="OK"; + const status = new protos.google.rpc.Status(); + status.code = statusCodeList[i]; + status.message = statusCodeList[i].toString(); - let thirdResponse = new protos.google.showcase.v1beta1.StreamingSequence.Response(); - thirdResponse.delay=thirdDelay; - thirdResponse.status=thirdStatus; - thirdResponse.sendStatusAtIndex=11; + const response = + new protos.google.showcase.v1beta1.StreamingSequence.Response(); + response.delay = delay; + response.status = status; + response.responseIndex = failIndexs[i]; - let streamingSequence = new protos.google.showcase.v1beta1.StreamingSequence() - streamingSequence.responses = [firstResponse,secondResponse,thirdResponse]; - // streamingSequence.responses = []; + streamingSequence.responses.push(response); + streamingSequence.content = content; + } - streamingSequence.content = "This is testing the brand new and shiny StreamingSequence server 3"; - request.streamingsequence = streamingSequence + request.streamingSequence = streamingSequence; - return request + return request; } async function testEcho(client: EchoClient) { @@ -245,23 +278,6 @@ async function testExpand(client: EchoClient) { }); } -async function testExpandThrows(client: EchoClient) { - const words = ['nobody', 'ever', 'reads', 'test', 'input']; - const request = { - content: words.join(' '), - }; - assert.throws(() => { - const stream = client.expand(request); - const result: string[] = []; - stream.on('data', (response: {content: string}) => { - result.push(response.content); - }); - stream.on('end', () => { - assert.deepStrictEqual(words, result); - }); - }, /currently does not support/); -} - async function testPagedExpand(client: EchoClient) { const words = ['nobody', 'ever', 'reads', 'test', 'input']; const request = { @@ -308,7 +324,6 @@ async function testCollect(client: EchoClient) { resolve(result.content ?? ''); } }); -<<<<<<< Updated upstream for (const word of words) { const request = {content: word}; stream.write(request); @@ -323,22 +338,6 @@ async function testCollect(client: EchoClient) { }); const result = await promise; assert.strictEqual(result, words.join(' ')); -======= - return attemptStream - } - let attemptStream; - //TODO(coleleah): handle this more elegantly - if (sequence.responses){ - const numResponses = sequence.responses.length - console.log(numResponses); - - attemptStream = await multipleSequenceAttempts(numResponses) - }else{ - const numResponses = 3 - attemptStream = await multipleSequenceAttempts(numResponses) - - } ->>>>>>> Stashed changes } async function testCollectThrows(client: EchoClient) { @@ -465,6 +464,557 @@ async function testWait(client: EchoClient) { assert.deepStrictEqual(response.content, request.success.content); } +async function testServerStreamingRetryOptions(client: SequenceServiceClient) { + const finalData: string[] = []; + const backoffSettings = createBackoffSettings( + 100, + 1.2, + 1000, + null, + 1.5, + 3000, + 10000 + ); + + const retryOptions = new RetryOptions([], backoffSettings); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.OK], + [0.1], + [11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + + const response = await client.createStreamingSequence(request); + await new Promise((resolve, _) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('data', (response: {content: string}) => { + finalData.push(response.content); + }); + attemptStream.on('error', () => { + //Do Nothing + }); + attemptStream.on('end', () => { + attemptStream.end(); + resolve(); + }); + }).then(() => { + assert.equal( + finalData.join(' '), + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + }); +} + +async function testServerStreamingRetrieswithRetryOptions( + client: SequenceServiceClient +) { + const finalData: string[] = []; + const backoffSettings = createBackoffSettings( + 100, + 1.2, + 1000, + null, + 1.5, + 3000, + 10000 + ); + + const retryOptions = new RetryOptions([14, 4], backoffSettings); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.DEADLINE_EXCEEDED, Status.OK], + [0.1, 0.1, 0.1], + [1, 2, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + + const response = await client.createStreamingSequence(request); + await new Promise((resolve, _) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('data', (response: {content: string}) => { + finalData.push(response.content); + }); + attemptStream.on('error', () => { + //Do Nothing + }); + attemptStream.on('end', () => { + attemptStream.end(); + resolve(); + }); + }).then(() => { + assert.equal( + finalData.join(' '), + 'This This is This is testing the brand new and shiny StreamingSequence server 3' + ); + }); +} + +async function testServerStreamingRetriesWithShouldRetryFn( + client: SequenceServiceClient +) { + const finalData: string[] = []; + const shouldRetryFn = function checkRetry(error: GoogleError) { + return [14, 4].includes(error.code!); + }; + + const backoffSettings = createBackoffSettings( + 100, + 1.2, + 1000, + null, + 1.5, + 3000, + 10000 + ); + + const retryOptions = new RetryOptions(shouldRetryFn, backoffSettings); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.DEADLINE_EXCEEDED, Status.OK], + [0.1, 0.1, 0.1], + [1, 2, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + + const response = await client.createStreamingSequence(request); + await new Promise((resolve, _) => { + const sequence = response[0]; + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('data', (response: {content: string}) => { + finalData.push(response.content); + }); + attemptStream.on('error', () => { + // Do nothing + }); + attemptStream.on('end', () => { + attemptStream.end(); + resolve(); + }); + }).then(() => { + assert.equal( + finalData.join(' '), + 'This This is This is testing the brand new and shiny StreamingSequence server 3' + ); + }); +} + +async function testServerStreamingRetrieswithRetryRequestOptions( + client: SequenceServiceClient +) { + const finalData: string[] = []; + const retryRequestOptions = { + objectMode: true, + retries: 1, + maxRetryDelay: 70, + retryDelayMultiplier: 3, + totalTimeout: 650, + noResponseRetries: 3, + currentRetryAttempt: 0, + shouldRetryFn: function checkRetry(error: GoogleError) { + return [14, 4].includes(error.code!); + }, + }; + + const settings = { + retryRequestOptions: retryRequestOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.DEADLINE_EXCEEDED, Status.OK], + [0.1, 0.1, 0.1], + [1, 2, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + + const response = await client.createStreamingSequence(request); + await new Promise((resolve, _) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('data', (response: {content: string}) => { + finalData.push(response.content); + }); + attemptStream.on('error', () => { + // Do Nothing + }); + attemptStream.on('end', () => { + attemptStream.end(); + resolve(); + }); + }).then(() => { + assert.equal( + finalData.join(' '), + 'This This is This is testing the brand new and shiny StreamingSequence server 3' + ); + }); +} +async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrategy( + client: SequenceServiceClient +) { + const finalData: string[] = []; + const shouldRetryFn = (error: GoogleError) => { + return [4, 14].includes(error.code!); + }; + const backoffSettings = createBackoffSettings( + 10000, + 2.5, + 1000, + null, + 1.5, + 3000, + 600000 + ); + const getResumptionRequestFn = ( + originalRequest: protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest + ) => { + const newRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + newRequest.name = originalRequest.name; + newRequest.lastFailIndex = 5; + return newRequest; + }; + + const retryOptions = new RetryOptions( + shouldRetryFn, + backoffSettings, + getResumptionRequestFn + ); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.DEADLINE_EXCEEDED, Status.OK], + [0.1, 0.1, 0.1], + [1, 2, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + const response = await client.createStreamingSequence(request); + await new Promise((resolve, _) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('data', (response: {content: string}) => { + finalData.push(response.content); + }); + attemptStream.on('error', () => { + // do nothing + }); + attemptStream.on('end', () => { + attemptStream.end(); + resolve(); + }); + }).then(() => { + assert.deepStrictEqual( + finalData.join(' '), + 'This new and new and shiny StreamingSequence server 3' + ); + }); +} + +async function testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResumptionStrategy( + client: SequenceServiceClient +) { + const shouldRetryFn = (error: GoogleError) => { + return [4, 14].includes(error.code!); + }; + const backoffSettings = createBackoffSettings( + 10000, + 2.5, + 1000, + null, + 1.5, + 3000, + 600000 + ); + const getResumptionRequestFn = () => { + // return a bad resumption strategy + return {}; + }; + + const retryOptions = new RetryOptions( + shouldRetryFn, + backoffSettings, + getResumptionRequestFn + ); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.DEADLINE_EXCEEDED, Status.OK], + [0.1, 0.1, 0.1], + [1, 2, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + const allowedCodes = [4, 14]; + const response = await client.createStreamingSequence(request); + await new Promise((_, reject) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + + attemptStream.on('error', (e: any) => { + if (!allowedCodes.includes(e.code!)) { + reject(e); + } + }); + }).then( + () => { + assert(false); + }, + (err: GoogleError) => { + assert.strictEqual(err.code, 3); + assert.match(err.message, /not classified as transient/); + } + ); +} + +async function testServerStreamingThrowsClassifiedTransientError( + client: SequenceServiceClient +) { + const backoffSettings = createBackoffSettings( + 100, + 1.2, + 1000, + null, + 1.5, + 3000, + 10000 + ); + const allowedCodes = [4]; + const retryOptions = new RetryOptions(allowedCodes, backoffSettings); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.DEADLINE_EXCEEDED, Status.OK], + [0.1, 0.1, 0.1], + [1, 2, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + + const response = await client.createStreamingSequence(request); + await new Promise((_, reject) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('error', (e: GoogleError) => { + if (!allowedCodes.includes(e.code!)) { + reject(e); + } + }); + }).then( + () => { + assert(false); + }, + (err: GoogleError) => { + assert.strictEqual(err.code, 3); + assert.match(err.message, /not classified as transient/); + } + ); +} + +async function testServerStreamingRetriesAndThrowsClassifiedTransientError( + client: SequenceServiceClient +) { + const backoffSettings = createBackoffSettings( + 100, + 1.2, + 1000, + null, + 1.5, + 3000, + 10000 + ); + const allowedCodes = [14]; + const retryOptions = new RetryOptions(allowedCodes, backoffSettings); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.DEADLINE_EXCEEDED, Status.OK], + [0.1, 0.1, 0.1], + [1, 2, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + + const response = await client.createStreamingSequence(request); + await new Promise((_, reject) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('error', (e: GoogleError) => { + if (!allowedCodes.includes(e.code!)) { + reject(e); + } + }); + }).then( + () => { + assert(false); + }, + (err: GoogleError) => { + assert.strictEqual(err.code, 3); + assert.match(err.message, /not classified as transient/); + } + ); +} + +async function testServerStreamingThrowsCannotSetTotalTimeoutMillisMaxRetries( + client: SequenceServiceClient +) { + const backoffSettings = createBackoffSettings( + 100, + 1.2, + 1000, + null, + 1.5, + 3000, + 10000 + ); + const allowedCodes = [14]; + backoffSettings.maxRetries = 1; + const retryOptions = new RetryOptions(allowedCodes, backoffSettings); + + const settings = { + retry: retryOptions, + }; + + client.initialize(); + + const request = createStreamingSequenceRequestFactory( + [Status.UNAVAILABLE, Status.OK], + [0.1, 0.1], + [1, 11], + 'This is testing the brand new and shiny StreamingSequence server 3' + ); + + const response = await client.createStreamingSequence(request); + await new Promise((_, reject) => { + const sequence = response[0]; + + const attemptRequest = + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); + attemptRequest.name = sequence.name!; + + const attemptStream = client.attemptStreamingSequence( + attemptRequest, + settings + ); + attemptStream.on('error', (e: GoogleError) => { + if (!allowedCodes.includes(e.code!)) { + reject(e); + } + }); + }).then( + () => { + assert(false); + }, + (err: GoogleError) => { + assert.strictEqual(err.code, 3); + assert.match( + err.message, + /Cannot set both totalTimeoutMillis and maxRetries/ + ); + } + ); +} + async function main() { const showcaseServer = new ShowcaseServer(); try { diff --git a/test/unit/apiCallable.ts b/test/unit/apiCallable.ts index 988380ff4..628c6cf36 100644 --- a/test/unit/apiCallable.ts +++ b/test/unit/apiCallable.ts @@ -19,6 +19,7 @@ import {status} from '@grpc/grpc-js'; import {afterEach, describe, it} from 'mocha'; import * as sinon from 'sinon'; +import {RequestType} from '../../src/apitypes'; import * as gax from '../../src/gax'; import {GoogleError} from '../../src/googleError'; import * as utils from './utils'; @@ -66,8 +67,8 @@ describe('createApiCall', () => { const now = new Date(); const originalDeadline = now.getTime() + 100; const expectedDeadline = now.getTime() + 200; - assert(resp! > originalDeadline); - assert(resp! <= expectedDeadline); + assert((resp as any)! > originalDeadline); + assert((resp as any)! <= expectedDeadline); done(); }); }); @@ -139,20 +140,23 @@ describe('createApiCall', () => { done(); }); }); - - it('override just custom retry.retrycodes', done => { + it('override just custom retry.retryCodesOrShouldRetryFn with retry codes', done => { const initialRetryCodes = [1]; const overrideRetryCodes = [1, 2, 3]; // eslint-disable-next-line @typescript-eslint/no-explicit-any sinon.stub(retries, 'retryable').callsFake((func, retry): any => { - assert.strictEqual(retry.retryCodes, overrideRetryCodes); + try { + assert.strictEqual(retry.retryCodesOrShouldRetryFn, overrideRetryCodes); + return func; + } catch (err) { + done(err); + } return func; }); function func() { done(); } - const apiCall = createApiCall(func, { settings: { retry: gax.createRetryOptions(initialRetryCodes, { @@ -170,11 +174,50 @@ describe('createApiCall', () => { {}, { retry: { - retryCodes: overrideRetryCodes, + retryCodesOrShouldRetryFn: overrideRetryCodes, }, } ); }); + it('errors when you override just custom retry.retryCodesOrShouldRetryFn with a function on a non streaming call', async () => { + function neverRetry() { + return false; + } + const initialRetryCodes = [1]; + const overrideRetryCodes = neverRetry; + + function func() { + return Promise.resolve(); + } + + const apiCall = createApiCall(func, { + settings: { + retry: gax.createRetryOptions(initialRetryCodes, { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + }), + }, + }); + try { + await apiCall( + {}, + { + retry: { + retryCodesOrShouldRetryFn: overrideRetryCodes, + }, + } + ); + } catch (err: any) { + assert.strictEqual( + err.message, + 'Using a function to determine retry eligibility is only supported with server streaming calls' + ); + } + }); it('override just custom retry.backoffSettings', done => { const initialBackoffSettings = gax.createDefaultBackoffSettings(); @@ -212,6 +255,53 @@ describe('createApiCall', () => { } ); }); + + it('errors when a resumption strategy is passed for a non streaming call', async () => { + const initialBackoffSettings = gax.createDefaultBackoffSettings(); + const overriBackoffSettings = gax.createBackoffSettings( + 100, + 1.2, + 1000, + null, + 1.5, + 3000, + 4500 + ); + // "resumption" strategy is to just return the original request + const getResumptionRequestFn = (originalRequest: RequestType) => { + return originalRequest; + }; + + function func() { + Promise.resolve(); + } + const apiCall = createApiCall(func, { + settings: { + retry: gax.createRetryOptions( + [1], + initialBackoffSettings, + getResumptionRequestFn + ), + }, + }); + + try { + await apiCall( + {}, + { + retry: { + backoffSettings: overriBackoffSettings, + }, + } + ); + } catch (err) { + assert(err instanceof Error); + assert.strictEqual( + err.message, + 'Resumption strategy can only be used with server streaming retries' + ); + } + }); }); describe('Promise', () => { @@ -500,7 +590,8 @@ describe('retryable', () => { }); }); - // maxRetries is unsupported, and intended for internal use only. + // maxRetries is unsupported, and intended for internal use only or + // use with retry-request backwards compatibility it('errors when totalTimeoutMillis and maxRetries set', done => { const maxRetries = 5; const backoff = gax.createMaxRetriesBackoffSettings( diff --git a/test/unit/gax.ts b/test/unit/gax.ts index d7eb4a99d..62ff7cfea 100644 --- a/test/unit/gax.ts +++ b/test/unit/gax.ts @@ -74,11 +74,14 @@ const RETRY_DICT = { function expectRetryOptions(obj: gax.RetryOptions) { assert.ok(obj instanceof Object); - ['retryCodes', 'backoffSettings'].forEach(k => + ['retryCodesOrShouldRetryFn', 'backoffSettings'].forEach(k => // eslint-disable-next-line no-prototype-builtins assert.ok(obj.hasOwnProperty(k)) ); - assert.ok(obj.retryCodes instanceof Array); + assert.ok( + obj.retryCodesOrShouldRetryFn instanceof Array || + obj.retryCodesOrShouldRetryFn instanceof Function + ); expectBackoffSettings(obj.backoffSettings); } @@ -112,13 +115,13 @@ describe('gax construct settings', () => { assert.strictEqual(settings.timeout, 40000); assert.strictEqual(settings.apiName, SERVICE_NAME); expectRetryOptions(settings.retry); - assert.deepStrictEqual(settings.retry.retryCodes, [1, 2]); + assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [1, 2]); assert.strictEqual(settings.otherArgs, otherArgs); settings = defaults.pageStreamingMethod; assert.strictEqual(settings.timeout, 30000); expectRetryOptions(settings.retry); - assert.deepStrictEqual(settings.retry.retryCodes, [3]); + assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [3]); assert.strictEqual(settings.otherArgs, otherArgs); }); @@ -185,7 +188,9 @@ describe('gax construct settings', () => { let settings = defaults.bundlingMethod; let backoff = settings.retry.backoffSettings; assert.strictEqual(backoff.initialRetryDelayMillis, 1000); - assert.deepStrictEqual(settings.retry.retryCodes, [RETRY_DICT.code_a]); + assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [ + RETRY_DICT.code_a, + ]); assert.strictEqual(settings.timeout, 50000); /* page_streaming_method is unaffected because it's not specified in @@ -196,6 +201,8 @@ describe('gax construct settings', () => { assert.strictEqual(backoff.initialRetryDelayMillis, 100); assert.strictEqual(backoff.retryDelayMultiplier, 1.2); assert.strictEqual(backoff.maxRetryDelayMillis, 1000); - assert.deepStrictEqual(settings.retry.retryCodes, [RETRY_DICT.code_c]); + assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [ + RETRY_DICT.code_c, + ]); }); }); diff --git a/test/unit/grpc-fallback.ts b/test/unit/grpc-fallback.ts index e386c5eb6..53abfdb99 100644 --- a/test/unit/grpc-fallback.ts +++ b/test/unit/grpc-fallback.ts @@ -19,8 +19,6 @@ import * as assert from 'assert'; import {describe, it, beforeEach, afterEach, before, after} from 'mocha'; -import * as path from 'path'; -import * as fs from 'fs'; import * as nodeFetch from 'node-fetch'; import * as abortController from 'abort-controller'; import * as protobuf from 'protobufjs'; @@ -267,7 +265,7 @@ describe('grpc-fallback', () => { Promise.resolve({ ok: true, arrayBuffer: () => { - return Promise.resolve(responseType.encode(response).finish()); + return Promise.resolve(Buffer.from(JSON.stringify(response))); }, }) ); @@ -287,10 +285,23 @@ describe('grpc-fallback', () => { it('should handle an API error', done => { const requestObject = {content: 'test-content'}; // example of an actual google.rpc.Status error message returned by Language API - const fixtureName = path.resolve(__dirname, '..', 'fixtures', 'error.bin'); - const errorBin = fs.readFileSync(fixtureName); const expectedMessage = '3 INVALID_ARGUMENT: One of content, or gcs_content_uri must be set.'; + const jsonError = { + code: 400, // Bad request + message: expectedMessage, + details: [ + { + '@type': 'type.googleapis.com/google.rpc.BadRequest', + fieldViolations: [ + { + field: 'document.content', + description: 'Must have some text content to annotate.', + }, + ], + }, + ], + }; const expectedError = { code: 3, details: [ @@ -309,7 +320,7 @@ describe('grpc-fallback', () => { Promise.resolve({ ok: false, arrayBuffer: () => { - return Promise.resolve(errorBin); + return Promise.resolve(Buffer.from(JSON.stringify(jsonError))); }, }) ); diff --git a/test/unit/streaming.ts b/test/unit/streaming.ts index 5feab8dee..38efc7d34 100644 --- a/test/unit/streaming.ts +++ b/test/unit/streaming.ts @@ -21,12 +21,14 @@ import * as sinon from 'sinon'; import {afterEach, describe, it} from 'mocha'; import {PassThrough} from 'stream'; -import {GaxCallStream, GRPCCall} from '../../src/apitypes'; +import {GaxCallStream, GRPCCall, RequestType} from '../../src/apitypes'; import {createApiCall} from '../../src/createApiCall'; +import {StreamingApiCaller} from '../../src/streamingCalls/streamingApiCaller'; import * as gax from '../../src/gax'; import {StreamDescriptor} from '../../src/streamingCalls/streamDescriptor'; import * as streaming from '../../src/streamingCalls/streaming'; import {APICallback} from '../../src/apitypes'; +import * as warnings from '../../src/warnings'; import internal = require('stream'); import {StreamArrayParser} from '../../src/streamArrayParser'; import path = require('path'); @@ -35,6 +37,23 @@ import {GoogleError} from '../../src'; import {Metadata} from '@grpc/grpc-js'; function createApiCallStreaming( + func: + | Promise + | sinon.SinonSpy, internal.Transform | StreamArrayParser>, + type: streaming.StreamType, + rest?: boolean, + gaxStreamingRetries?: boolean +) { + const settings = new gax.CallSettings(); + return createApiCall( + //@ts-ignore + Promise.resolve(func), + settings, + new StreamDescriptor(type, rest, gaxStreamingRetries) + ) as GaxCallStream; +} + +function createApiCallStreamingWithNewLogic( func: | Promise | sinon.SinonSpy, internal.Transform | StreamArrayParser>, @@ -46,7 +65,7 @@ function createApiCallStreaming( //@ts-ignore Promise.resolve(func), settings, - new StreamDescriptor(type, rest) + new StreamDescriptor(type, rest, true) ) as GaxCallStream; } @@ -159,41 +178,6 @@ describe('streaming', () => { s.end(); }); - it('allows custome CallOptions.retry settings', done => { - sinon - .stub(streaming.StreamProxy.prototype, 'forwardEvents') - .callsFake(stream => { - assert(stream instanceof internal.Stream); - done(); - }); - const spy = sinon.spy((...args: Array<{}>) => { - assert.strictEqual(args.length, 3); - const s = new PassThrough({ - objectMode: true, - }); - return s; - }); - - const apiCall = createApiCallStreaming( - spy, - streaming.StreamType.SERVER_STREAMING - ); - - apiCall( - {}, - { - retry: gax.createRetryOptions([1], { - initialRetryDelayMillis: 100, - retryDelayMultiplier: 1.2, - maxRetryDelayMillis: 1000, - rpcTimeoutMultiplier: 1.5, - maxRpcTimeoutMillis: 3000, - totalTimeoutMillis: 4500, - }), - } - ); - }); - it('forwards metadata and status', done => { const responseMetadata = {metadata: true}; const status = {code: 0, metadata: responseMetadata}; @@ -264,8 +248,9 @@ describe('streaming', () => { s.end(s); }, 50); }); - it('cancels in the middle', done => { + const warnStub = sinon.stub(warnings, 'warn'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any function schedulePush(s: any, c: number) { const intervalId = setInterval(() => { @@ -297,7 +282,19 @@ describe('streaming', () => { func, streaming.StreamType.SERVER_STREAMING ); - const s = apiCall({}, undefined); + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 0, + }), + } + ); let counter = 0; const expectedCount = 5; s.on('data', data => { @@ -313,6 +310,14 @@ describe('streaming', () => { assert.strictEqual(err, cancelError); done(); }); + assert.strictEqual(warnStub.callCount, 1); + assert( + warnStub.calledWith( + 'legacy_streaming_retry_behavior', + 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', + 'DeprecationWarning' + ) + ); }); it('emit response when stream received metadata event', done => { @@ -409,7 +414,6 @@ describe('streaming', () => { s.push(null); s.on('end', () => { setTimeout(() => { - console.log('emit status event'); s.emit('status', expectedStatus); }, 10); }); @@ -462,6 +466,8 @@ describe('streaming', () => { }); it('emit parsed GoogleError', done => { + const warnStub = sinon.stub(warnings, 'warn'); + const errorInfoObj = { reason: 'SERVICE_DISABLED', domain: 'googleapis.com', @@ -488,6 +494,7 @@ describe('streaming', () => { details: 'Failed to read', metadata: metadata, }); + const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); const s = new PassThrough({ @@ -497,14 +504,33 @@ describe('streaming', () => { setImmediate(() => { s.emit('error', error); }); + setImmediate(() => { + s.emit('end'); + }); return s; }); const apiCall = createApiCallStreaming( spy, streaming.StreamType.SERVER_STREAMING ); - const s = apiCall({}, undefined); + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 0, + }), + } + ); + s.on('error', err => { + s.pause(); + s.destroy(); assert(err instanceof GoogleError); assert.deepStrictEqual(err.message, 'test error'); assert.strictEqual(err.domain, errorInfoObj.domain); @@ -515,6 +541,704 @@ describe('streaming', () => { ); done(); }); + s.on('end', () => { + done(); + }); + assert.strictEqual(warnStub.callCount, 1); + assert( + warnStub.calledWith( + 'legacy_streaming_retry_behavior', + 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', + 'DeprecationWarning' + ) + ); + }); + + it('emit error and retry once', done => { + const firstError = Object.assign(new GoogleError('UNAVAILABLE'), { + code: 14, + details: 'UNAVAILABLE', + }); + let counter = 0; + const expectedStatus = {code: 0}; + const receivedData: string[] = []; + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + setImmediate(() => { + s.push('Hello'); + s.push('World'); + switch (counter) { + case 0: + s.emit('error', firstError); + counter++; + break; + case 1: + s.push('testing'); + s.push('retries'); + s.emit('status', expectedStatus); + counter++; + assert.deepStrictEqual( + receivedData.join(' '), + 'Hello World testing retries' + ); + done(); + break; + default: + break; + } + }); + return s; + }); + + const apiCall = createApiCallStreamingWithNewLogic( + spy, + streaming.StreamType.SERVER_STREAMING + ); + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([14], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 1, + }), + } + ); + let errorCount = 0; + s.on('data', data => { + receivedData.push(data); + }); + s.on('error', err => { + assert(err instanceof GoogleError); + switch (errorCount) { + case 0: + assert.deepStrictEqual(err.message, 'UNAVAILABLE'); + assert.strictEqual(err.code, 14); + break; + default: + break; + } + errorCount++; + }); + }); + + it('emit error and retry once with shouldRetryFn', done => { + const firstError = Object.assign(new GoogleError('UNAVAILABLE'), { + code: 14, + details: 'UNAVAILABLE', + }); + let counter = 0; + const expectedStatus = {code: 0}; + const receivedData: string[] = []; + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + setImmediate(() => { + s.push('Hello'); + s.push('World'); + switch (counter) { + case 0: + s.emit('error', firstError); + counter++; + break; + case 1: + s.push('testing'); + s.push('retries'); + s.emit('status', expectedStatus); + counter++; + assert.deepStrictEqual( + receivedData.join(' '), + 'Hello World testing retries' + ); + done(); + break; + default: + break; + } + }); + return s; + }); + + const apiCall = createApiCallStreamingWithNewLogic( + spy, + streaming.StreamType.SERVER_STREAMING + ); + + function retryCodesOrShouldRetryFn(error: GoogleError) { + return [14].includes(error.code!); + } + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions(retryCodesOrShouldRetryFn, { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 1, + }), + } + ); + let errorCount = 0; + s.on('data', data => { + receivedData.push(data); + }); + s.on('error', err => { + assert(err instanceof GoogleError); + switch (errorCount) { + case 0: + assert.deepStrictEqual(err.message, 'UNAVAILABLE'); + assert.strictEqual(err.code, 14); + break; + default: + break; + } + errorCount++; + }); + }); +}); + +describe('handles server streaming retries in gax when gaxStreamingRetries is enabled', () => { + afterEach(() => { + sinon.restore(); + }); + it('allows custom CallOptions.retry settings with shouldRetryFn instead of retryCodes and new retry behavior', done => { + sinon + .stub(streaming.StreamProxy.prototype, 'forwardEventsNewImplementation') + .callsFake((stream): any => { + assert(stream instanceof internal.Stream); + done(); + }); + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true //gaxStreamingRetries + ); + + apiCall( + {}, + { + retry: gax.createRetryOptions( + () => { + return true; + }, + { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + } + ), + } + ); + }); + it('allows custom CallOptions.retry settings with retryCodes and new retry behavior', done => { + sinon + .stub(streaming.StreamProxy.prototype, 'forwardEventsNewImplementation') + .callsFake((stream): any => { + assert(stream instanceof internal.Stream); + done(); + }); + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true //gaxStreamingRetries + ); + + apiCall( + {}, + { + retry: gax.createRetryOptions([1, 2, 3], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + }), + } + ); + }); + it('allows the user to pass a custom resumption strategy', done => { + sinon + .stub(streaming.StreamProxy.prototype, 'forwardEventsNewImplementation') + .callsFake((stream, retry): any => { + console.log('hello i am here', retry); + assert(stream instanceof internal.Stream); + assert(retry.getResumptionRequestFn instanceof Function); + done(); + }); + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true //gaxStreamingRetries + ); + + // "resumption" strategy is to just return the original request + const getResumptionRequestFn = (originalRequest: RequestType) => { + return originalRequest; + }; + + apiCall( + {}, + { + retry: gax.createRetryOptions( + [1, 2, 3], + { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + }, + getResumptionRequestFn + ), + } + ); + }); + + it('throws an error when both retryRequestoptions and retryOptions are passed at call time when new retry behavior is enabled', done => { + //if this is reached, it means the settings merge in createAPICall did not fail properly + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + throw new Error("This shouldn't be happening"); + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // ensure we're doing the new retries + ); + + const passedRetryRequestOptions = { + objectMode: false, + retries: 1, + maxRetryDelay: 70, + retryDelayMultiplier: 3, + totalTimeout: 650, + noResponseRetries: 3, + currentRetryAttempt: 0, + shouldRetryFn: function alwaysRetry() { + return true; + }, + }; + // make the call with both options passed at call time + try { + apiCall( + {}, + { + retryRequestOptions: passedRetryRequestOptions, + retry: gax.createRetryOptions([1], { + initialRetryDelayMillis: 300, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + }), + } + ); + } catch (err) { + assert(err instanceof Error); + assert.strictEqual( + err.toString(), + 'Error: Only one of retry or retryRequestOptions may be set' + ); + done(); + } + }); + it('throws a warning and converts retryRequestOptions for new retry behavior', done => { + const warnStub = sinon.stub(warnings, 'warn'); + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + try { + // Retry settings + // TODO: retries - one to one with maxRetries - this should be undefined if timeout is defined, I think + // //Backoff settings + assert(settings.retry); + assert(typeof settings.retryRequestOptions === 'undefined'); + assert.strictEqual( + settings.retry?.backoffSettings.maxRetryDelayMillis, + 70000 + ); + assert.strictEqual( + settings.retry?.backoffSettings.retryDelayMultiplier, + 3 + ); + // totalTimeout is undefined because maxRetries is passed + assert( + typeof settings.retry?.backoffSettings.totalTimeoutMillis === + 'undefined' + ); + + assert.strictEqual(settings.retry?.backoffSettings.maxRetries, 1); + assert( + typeof settings.retry.retryCodesOrShouldRetryFn === 'function' + ); + assert(settings.retry !== new gax.CallSettings().retry); + done(); + } catch (err) { + done(err); + } + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // gaxStreamingRetries + ); + const passedRetryRequestOptions = { + objectMode: false, + retries: 1, + maxRetryDelay: 70, + retryDelayMultiplier: 3, + totalTimeout: 650, + noResponseRetries: 3, + currentRetryAttempt: 0, + shouldRetryFn: function alwaysRetry() { + return true; + }, + }; + // make the call with both options passed at call time + apiCall( + {}, + { + retryRequestOptions: passedRetryRequestOptions, + } + ); + + assert.strictEqual(warnStub.callCount, 1); + assert( + warnStub.calledWith( + 'retry_request_options', + 'retryRequestOptions will be deprecated in a future release. Please use retryOptions to pass retry options at call time', + 'DeprecationWarning' + ) + ); + }); + it('throws a warning and converts retryRequestOptions for new retry behavior - no maxRetries', done => { + const warnStub = sinon.stub(warnings, 'warn'); + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + try { + assert(settings.retry); + assert(typeof settings.retryRequestOptions === 'undefined'); + assert.strictEqual( + settings.retry?.backoffSettings.maxRetryDelayMillis, + 70000 + ); + assert.strictEqual( + settings.retry?.backoffSettings.retryDelayMultiplier, + 3 + ); + assert.strictEqual( + settings.retry?.backoffSettings.totalTimeoutMillis, + 650000 + ); + assert( + typeof settings.retry?.backoffSettings.maxRetries === 'undefined' + ); + assert( + typeof settings.retry.retryCodesOrShouldRetryFn === 'function' + ); + assert(settings.retry !== new gax.CallSettings().retry); + done(); + } catch (err) { + done(err); + } + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // gaxStreamingRetries + ); + const passedRetryRequestOptions = { + objectMode: false, + maxRetryDelay: 70, + retryDelayMultiplier: 3, + totalTimeout: 650, + noResponseRetries: 3, + currentRetryAttempt: 0, + shouldRetryFn: function alwaysRetry() { + return true; + }, + }; + apiCall( + {}, + { + retryRequestOptions: passedRetryRequestOptions, + } + ); + + assert.strictEqual(warnStub.callCount, 1); + assert( + warnStub.calledWith( + 'retry_request_options', + 'retryRequestOptions will be deprecated in a future release. Please use retryOptions to pass retry options at call time', + 'DeprecationWarning' + ) + ); + }); +}); +describe('warns/errors about server streaming retry behavior when gaxStreamingRetries is disabled', () => { + afterEach(() => { + // restore 'call' stubs and 'warn' stubs + sinon.restore(); + }); + + // NO RETRY BEHAVIOR ENABLED + it('throws a warning when retryRequestOptions are passed', done => { + const warnStub = sinon.stub(warnings, 'warn'); + + // this exists to help resolve createApiCall + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + done(); + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + false // ensure we are NOT opted into the new retry behavior + ); + const passedRetryRequestOptions = { + objectMode: false, + retries: 1, + maxRetryDelay: 70, + retryDelayMultiplier: 3, + totalTimeout: 650, + noResponseRetries: 3, + currentRetryAttempt: 0, + shouldRetryFn: function alwaysRetry() { + return true; + }, + }; + // make the call with both options passed at call time + apiCall( + {}, + { + retryRequestOptions: passedRetryRequestOptions, + } + ); + assert.strictEqual(warnStub.callCount, 1); + assert( + warnStub.calledWith( + 'legacy_streaming_retry_request_behavior', + 'Legacy streaming retry behavior will not honor retryRequestOptions passed at call time. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will convert retryRequestOptions to retry parameters by default in future releases.', + 'DeprecationWarning' + ) + ); + }); + it('throws a warning when retry options are passed', done => { + const warnStub = sinon.stub(warnings, 'warn'); + // this exists to help resolve createApiCall + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + done(); + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + false // ensure we are NOT opted into the new retry behavior + ); + + // make the call with both options passed at call time + apiCall( + {}, + { + retry: gax.createRetryOptions([1], { + initialRetryDelayMillis: 300, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + }), + } + ); + assert.strictEqual(warnStub.callCount, 1); + assert( + warnStub.calledWith( + 'legacy_streaming_retry_behavior', + 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', + 'DeprecationWarning' + ) + ); + }); + it('throws no warnings when when no retry options are passed', done => { + const warnStub = sinon.stub(warnings, 'warn'); + // this exists to help resolve createApiCall + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + done(); + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + false // ensure we are NOT opted into the new retry behavior + ); + + // make the call with neither retry option passed at call time + apiCall({}, {}); + assert.strictEqual(warnStub.callCount, 0); + }); + it('throws two warnings when when retry and retryRequestoptions are passed', done => { + const warnStub = sinon.stub(warnings, 'warn'); + // this exists to help resolve createApiCall + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + done(); + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + false // ensure we are NOT opted into the new retry behavior + ); + const passedRetryRequestOptions = { + objectMode: false, + retries: 1, + maxRetryDelay: 70, + retryDelayMultiplier: 3, + totalTimeout: 650, + noResponseRetries: 3, + currentRetryAttempt: 0, + shouldRetryFn: function alwaysRetry() { + return true; + }, + }; + // make the call with both retry options passed at call time + apiCall( + {}, + { + retryRequestOptions: passedRetryRequestOptions, + retry: gax.createRetryOptions([1], { + initialRetryDelayMillis: 300, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + }), + } + ); + assert.strictEqual(warnStub.callCount, 2); }); }); @@ -576,42 +1300,6 @@ describe('REST streaming apiCall return StreamArrayParser', () => { assert.deepStrictEqual(err.message, 'test error'); done(); }); - }); - - it('cancels StreamArrayParser in the middle', done => { - function schedulePush(s: StreamArrayParser, c: number) { - const intervalId = setInterval(() => { - s.push(c); - c++; - }, 10); - s.on('finish', () => { - clearInterval(intervalId); - }); - } - const spy = sinon.spy((...args: Array<{}>) => { - assert.strictEqual(args.length, 3); - const s = new StreamArrayParser(streamMethod); - schedulePush(s, 0); - return s; - }); - const apiCall = createApiCallStreaming( - //@ts-ignore - spy, - streaming.StreamType.SERVER_STREAMING, - true - ); - const s = apiCall({}, undefined); - let counter = 0; - const expectedCount = 5; - s.on('data', data => { - assert.strictEqual(data, counter); - counter++; - if (counter === expectedCount) { - s.cancel(); - } else if (counter > expectedCount) { - done(new Error('should not reach')); - } - }); s.on('end', () => { done(); }); diff --git a/test/unit/streamingRetryRequest.ts b/test/unit/streamingRetryRequest.ts new file mode 100644 index 000000000..dbdb204b6 --- /dev/null +++ b/test/unit/streamingRetryRequest.ts @@ -0,0 +1,122 @@ +// Copyright 2023 Google LLC + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// https://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +/* eslint-disable @typescript-eslint/ban-ts-comment */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import {describe, it} from 'mocha'; +import {PassThrough} from 'stream'; + +import {CancellableStream, GaxCallStream, GRPCCall} from '../../src/apitypes'; +import {createApiCall} from '../../src/createApiCall'; +import * as gax from '../../src/gax'; +import {StreamDescriptor} from '../../src/streamingCalls/streamDescriptor'; +import * as streaming from '../../src/streamingCalls/streaming'; +import internal = require('stream'); +import {StreamArrayParser} from '../../src/streamArrayParser'; +import {streamingRetryRequest} from '../../src/streamingRetryRequest'; + +function createApiCallStreaming( + func: + | Promise + | sinon.SinonSpy, internal.Transform | StreamArrayParser>, + type: streaming.StreamType, + rest?: boolean, + gaxStreamingRetries?: boolean +) { + const settings = new gax.CallSettings(); + return createApiCall( + //@ts-ignore + Promise.resolve(func), + settings, + new StreamDescriptor(type, rest, gaxStreamingRetries) + ) as GaxCallStream; +} + +describe('retry-request', () => { + describe('streams', () => { + it('works with defaults in a stream', done => { + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + s.push({resources: [1, 2]}); + s.push({resources: [3, 4, 5]}); + s.push(null); + setImmediate(() => { + s.emit('metadata'); + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true + ); + + const retryStream = streamingRetryRequest( + null, + { + objectMode: true, + request: () => { + const stream = apiCall( + {}, + { + retry: gax.createRetryOptions([5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 0, + }), + } + ) as CancellableStream; + return stream; + }, + }, + null + ) + .on('end', done()) + .on('data', (data: any) => { + console.log(data); + }); + assert.strictEqual(retryStream._readableState.objectMode, true); + }); + + it('throws request error', done => { + try { + streamingRetryRequest(null, null, null); + } catch (err: any) { + assert.match(err.message, /A request library must be provided/); + done(); + } + }); + + it('opts is a function and throws request error', done => { + try { + const opts = () => { + return true; + }; + streamingRetryRequest(null, opts, null); + } catch (err: any) { + assert.match(err.message, /A request library must be provided/); + done(); + } + }); + }); +}); diff --git a/tools/package.json b/tools/package.json index 53b34d894..08b137295 100644 --- a/tools/package.json +++ b/tools/package.json @@ -1,10 +1,10 @@ { "name": "gapic-tools", - "version": "0.1.7", + "version": "0.1.8", "description": "compiles, updates, and minifies protos", "main": "build/src/compileProtos.js", "files": [ - "build/tools/src", + "build/src", "!build/src/**/*.map" ], "scripts": { @@ -28,24 +28,24 @@ "author": "Google API Authors", "license": "Apache-2.0", "dependencies": { - "@types/rimraf": "^3.0.2", - "google-proto-files": "^3.0.0", - "protobufjs-cli": "1.1.1", + "google-gax": "^4.0.2", + "google-proto-files": "^4.0.0", + "protobufjs-cli": "1.1.2", + "rimraf": "^5.0.1", "uglify-js": "^3.17.0", - "walkdir": "^0.4.0", - "rimraf": "^3.0.2" + "walkdir": "^0.4.0" }, "repository": "googleapis/gax-nodejs", "devDependencies": { "@types/mocha": "^9.0.0", "@types/ncp": "^2.0.1", "@types/uglify-js": "^3.17.0", - "c8": "^7.0.0", - "gts": "^3.1.1", + "c8": "^8.0.0", + "gts": "^5.0.0", "mocha": "^9.0.0", "ncp": "^2.0.0", - "protobufjs": "7.2.3", - "typescript": "^4.6.4" + "protobufjs": "7.2.5", + "typescript": "^5.1.6" }, "engines": { "node": ">=14" diff --git a/tools/src/compileProtos.ts b/tools/src/compileProtos.ts index d6245d41e..10d5e24d7 100644 --- a/tools/src/compileProtos.ts +++ b/tools/src/compileProtos.ts @@ -22,6 +22,12 @@ import * as util from 'util'; import * as pbjs from 'protobufjs-cli/pbjs'; import * as pbts from 'protobufjs-cli/pbts'; +export const gaxProtos = path.join( + require.resolve('google-gax'), + '..', + '..', + 'protos' +); const readdir = util.promisify(fs.readdir); const readFile = util.promisify(fs.readFile); const writeFile = util.promisify(fs.writeFile); @@ -246,7 +252,7 @@ async function compileProtos( '-p', 'protos', '-p', - path.join(__dirname, '..', '..', '..', 'google-gax', 'build', 'protos'), + gaxProtos, '-o', jsonOutput, ]; @@ -264,7 +270,7 @@ async function compileProtos( '-p', 'protos', '-p', - path.join(__dirname, '..', '..', '..', 'google-gax', 'build', 'protos'), + gaxProtos, '-o', jsOutput, ]; diff --git a/tools/test/compileProtos.ts b/tools/test/compileProtos.ts index 87020b606..d9d68b49a 100644 --- a/tools/test/compileProtos.ts +++ b/tools/test/compileProtos.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import {describe, it, beforeEach, afterEach} from 'mocha'; import * as fs from 'fs'; -import * as rimraf from 'rimraf'; +import {rimraf} from 'rimraf'; import * as util from 'util'; import * as path from 'path'; import * as protobuf from 'protobufjs'; @@ -25,7 +25,6 @@ import * as compileProtos from '../src/compileProtos'; const readFile = util.promisify(fs.readFile); const mkdir = util.promisify(fs.mkdir); -const rmrf = util.promisify(rimraf); const testDir = path.join(process.cwd(), '.compileProtos-test'); const resultDir = path.join(testDir, 'protos'); @@ -38,7 +37,7 @@ const expectedTSResultFile = path.join(resultDir, 'protos.d.ts'); describe('compileProtos tool', () => { beforeEach(async () => { if (fs.existsSync(testDir)) { - await rmrf(testDir); + await rimraf(testDir); } await mkdir(testDir); await mkdir(resultDir); @@ -50,6 +49,26 @@ describe('compileProtos tool', () => { process.chdir(cwd); }); + it('fetches gax from the appropriate place', async () => { + assert.deepStrictEqual(fs.readdirSync(compileProtos.gaxProtos), [ + 'compute_operations.d.ts', + 'compute_operations.js', + 'compute_operations.json', + 'google', + 'http.d.ts', + 'http.js', + 'iam_service.d.ts', + 'iam_service.js', + 'iam_service.json', + 'locations.d.ts', + 'locations.js', + 'locations.json', + 'operations.d.ts', + 'operations.js', + 'operations.json', + 'status.json', + ]); + }); it('compiles protos to JSON, JS, TS', async function () { this.timeout(20000); await compileProtos.main([ diff --git a/tools/test/fixtures/echo.js b/tools/test/fixtures/echo.js index 76c665675..e2db51487 100644 --- a/tools/test/fixtures/echo.js +++ b/tools/test/fixtures/echo.js @@ -16,13 +16,12 @@ // Lots of comments we can delete in uglify async function main() { - // Showcases auto-pagination functionality. - - // Let's say we have an API call that returns results grouped into pages. - // It accepts 4 parameters (just like gRPC stub calls do): - return 'SUCCESS'; - } - - main().catch(console.error); - // More comments - + // Showcases auto-pagination functionality. + + // Let's say we have an API call that returns results grouped into pages. + // It accepts 4 parameters (just like gRPC stub calls do): + return 'SUCCESS'; +} + +main().catch(console.error); +// More comments diff --git a/tools/test/minify.ts b/tools/test/minify.ts index 14d86eebf..19f814b0f 100644 --- a/tools/test/minify.ts +++ b/tools/test/minify.ts @@ -16,19 +16,18 @@ import * as assert from 'assert'; import {describe, it, beforeEach} from 'mocha'; import * as fs from 'fs'; import {promises as fsp} from 'fs'; -import * as rimraf from 'rimraf'; +import {rimraf} from 'rimraf'; import * as path from 'path'; import * as minify from '../src/minify'; import {promisify} from 'util'; -const rmrf = promisify(rimraf); const testDir = path.join(process.cwd(), '.minify-test'); const fixturesDir = path.join(__dirname, '..', 'test', 'fixtures'); describe('minify tool', () => { beforeEach(async () => { if (fs.existsSync(testDir)) { - await rmrf(testDir); + await rimraf(testDir); } await fsp.mkdir(testDir); }); diff --git a/tsconfig.json b/tsconfig.json index 0e984ded2..0921b23c8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,9 @@ }, "include": [ "src/*.ts", + "tools/src/listProtos.ts", + "tools/src/minify.ts", + "tools/src/prepublish.ts", "src/*/*.ts", "test/system-test/*.ts", "test/unit/*.ts", From 25f10468cae5c5823f85128c91fbfa1d67c372a3 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 23 Aug 2023 10:03:33 -0400 Subject: [PATCH 03/35] rerun npx gts fix --- .../src/v1beta1/echo_client.ts | 4 ++-- .../src/v1beta1/sequence_service_client.ts | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/test/showcase-echo-client/src/v1beta1/echo_client.ts b/test/showcase-echo-client/src/v1beta1/echo_client.ts index 293ee7e95..f87bd04ca 100644 --- a/test/showcase-echo-client/src/v1beta1/echo_client.ts +++ b/test/showcase-echo-client/src/v1beta1/echo_client.ts @@ -705,7 +705,7 @@ export class EchoClient { [ protos.google.showcase.v1beta1.IPagedExpandResponse, protos.google.showcase.v1beta1.IPagedExpandLegacyRequest | undefined, - {} | undefined + {} | undefined, ] >; pagedExpandLegacy( @@ -751,7 +751,7 @@ export class EchoClient { [ protos.google.showcase.v1beta1.IPagedExpandResponse, protos.google.showcase.v1beta1.IPagedExpandLegacyRequest | undefined, - {} | undefined + {} | undefined, ] > | void { request = request || {}; diff --git a/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts index 1149d230a..770f180d1 100644 --- a/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts +++ b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts @@ -381,7 +381,7 @@ export class SequenceServiceClient { [ protos.google.showcase.v1beta1.ISequence, protos.google.showcase.v1beta1.ICreateSequenceRequest | undefined, - {} | undefined + {} | undefined, ] >; createSequence( @@ -421,7 +421,7 @@ export class SequenceServiceClient { [ protos.google.showcase.v1beta1.ISequence, protos.google.showcase.v1beta1.ICreateSequenceRequest | undefined, - {} | undefined + {} | undefined, ] > | void { request = request || {}; @@ -463,7 +463,7 @@ export class SequenceServiceClient { | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest | undefined ), - {} | undefined + {} | undefined, ] >; createStreamingSequence( @@ -512,7 +512,7 @@ export class SequenceServiceClient { | protos.google.showcase.v1beta1.ICreateStreamingSequenceRequest | undefined ), - {} | undefined + {} | undefined, ] > | void { request = request || {}; @@ -555,7 +555,7 @@ export class SequenceServiceClient { [ protos.google.showcase.v1beta1.ISequenceReport, protos.google.showcase.v1beta1.IGetSequenceReportRequest | undefined, - {} | undefined + {} | undefined, ] >; getSequenceReport( @@ -601,7 +601,7 @@ export class SequenceServiceClient { [ protos.google.showcase.v1beta1.ISequenceReport, protos.google.showcase.v1beta1.IGetSequenceReportRequest | undefined, - {} | undefined + {} | undefined, ] > | void { request = request || {}; @@ -647,7 +647,7 @@ export class SequenceServiceClient { | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest | undefined ), - {} | undefined + {} | undefined, ] >; getStreamingSequenceReport( @@ -696,7 +696,7 @@ export class SequenceServiceClient { | protos.google.showcase.v1beta1.IGetStreamingSequenceReportRequest | undefined ), - {} | undefined + {} | undefined, ] > | void { request = request || {}; @@ -743,7 +743,7 @@ export class SequenceServiceClient { [ protos.google.protobuf.IEmpty, protos.google.showcase.v1beta1.IAttemptSequenceRequest | undefined, - {} | undefined + {} | undefined, ] >; attemptSequence( @@ -783,7 +783,7 @@ export class SequenceServiceClient { [ protos.google.protobuf.IEmpty, protos.google.showcase.v1beta1.IAttemptSequenceRequest | undefined, - {} | undefined + {} | undefined, ] > | void { request = request || {}; From 78b9e5b26061b9247f09d550a929eb8e09a61deb Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 23 Aug 2023 13:35:44 -0400 Subject: [PATCH 04/35] fix two of three failing browser tests --- test/browser-test/test/test.grpc-fallback.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/browser-test/test/test.grpc-fallback.ts b/test/browser-test/test/test.grpc-fallback.ts index 6cf85e725..49b93c3cd 100644 --- a/test/browser-test/test/test.grpc-fallback.ts +++ b/test/browser-test/test/test.grpc-fallback.ts @@ -93,8 +93,8 @@ describe('createStub', () => { assert(echoStub.pagedExpand instanceof Function); assert(echoStub.wait instanceof Function); - // There should be 8 methods for the echo service - assert.strictEqual(Object.keys(echoStub).length, 8); + // There should be 10 methods for the echo service + assert.strictEqual(Object.keys(echoStub).length, 10); // Each of the service methods should take 4 arguments (so that it works // with createApiCall) @@ -109,8 +109,8 @@ describe('createStub', () => { assert(echoStub.collect instanceof Function); assert(echoStub.chat instanceof Function); - // There should be 8 methods for the echo service - assert.strictEqual(Object.keys(echoStub).length, 8); + // There should be 10 methods for the echo service + assert.strictEqual(Object.keys(echoStub).length, 10); // Each of the service methods should take 4 arguments (so that it works // with createApiCall) From f258a1c3f7797e72fc8d14df6c0a5041b09f8343 Mon Sep 17 00:00:00 2001 From: "Leah E. Cole" <6719667+leahecole@users.noreply.github.com> Date: Thu, 24 Aug 2023 13:53:49 -0400 Subject: [PATCH 05/35] Regenerate showcase (#12) * regenerate client * npx gts fix * add new retry * remove tsconfig changes --- test/showcase-echo-client/package.json | 2 +- .../src/v1beta1/echo_client.ts | 22 +++++++++---------- .../src/v1beta1/sequence_service_client.ts | 9 ++++---- 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/test/showcase-echo-client/package.json b/test/showcase-echo-client/package.json index 4e4a64b95..42f642985 100644 --- a/test/showcase-echo-client/package.json +++ b/test/showcase-echo-client/package.json @@ -1,5 +1,5 @@ { - "name": "showcase-echo-client", + "name": "showcase-echo-client", "version": "0.1.0", "description": "Showcase client for Node.js", "repository": "googleapis/nodejs-showcase", diff --git a/test/showcase-echo-client/src/v1beta1/echo_client.ts b/test/showcase-echo-client/src/v1beta1/echo_client.ts index f87bd04ca..a1a61ab90 100644 --- a/test/showcase-echo-client/src/v1beta1/echo_client.ts +++ b/test/showcase-echo-client/src/v1beta1/echo_client.ts @@ -105,8 +105,7 @@ export class EchoClient { * API remote host. * @param {gax.ClientConfig} [options.clientConfig] - Client configuration override. * Follows the structure of {@link gapicConfig}. - * @param {boolean | "rest"} [options.fallback] - Use HTTP fallback mode. - * Pass "rest" to use HTTP/1.1 REST API instead of gRPC. + * @param {boolean} [options.fallback] - Use HTTP/1.1 REST mode. * For more information, please check the * {@link https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#http11-rest-api-mode documentation}. * @param {gax} [gaxInstance]: loaded instance of `google-gax`. Useful if you @@ -114,7 +113,7 @@ export class EchoClient { * HTTP implementation. Load only fallback version and pass it to the constructor: * ``` * const gax = require('google-gax/build/src/fallback'); // avoids loading google-gax with gRPC - * const client = new EchoClient({fallback: 'rest'}, gax); + * const client = new EchoClient({fallback: true}, gax); * ``` */ constructor( @@ -181,10 +180,10 @@ export class EchoClient { } else { clientHeader.push(`gl-web/${this._gaxModule.version}`); } - if (opts.fallback) { - clientHeader.push(`rest/${this._gaxGrpc.grpcVersion}`); - } else { + if (!opts.fallback) { clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`); + } else { + clientHeader.push(`rest/${this._gaxGrpc.grpcVersion}`); } if (opts.libName && opts.libVersion) { clientHeader.push(`${opts.libName}/${opts.libVersion}`); @@ -226,18 +225,17 @@ export class EchoClient { this.descriptors.stream = { expand: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.SERVER_STREAMING, - // legacy: opts.fallback can be a string or a boolean - opts.fallback ? true : false + !!opts.fallback, + this._opts.newRetry + ), collect: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.CLIENT_STREAMING, - // legacy: opts.fallback can be a string or a boolean - opts.fallback ? true : false + !!opts.fallback ), chat: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.BIDI_STREAMING, - // legacy: opts.fallback can be a string or a boolean - opts.fallback ? true : false + !!opts.fallback ), }; diff --git a/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts index 770f180d1..dbc82a9b6 100644 --- a/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts +++ b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts @@ -93,8 +93,7 @@ export class SequenceServiceClient { * API remote host. * @param {gax.ClientConfig} [options.clientConfig] - Client configuration override. * Follows the structure of {@link gapicConfig}. - * @param {boolean | "rest"} [options.fallback] - Use HTTP fallback mode. - * Pass "rest" to use HTTP/1.1 REST API instead of gRPC. + * @param {boolean} [options.fallback] - Use HTTP/1.1 REST mode. * For more information, please check the * {@link https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md#http11-rest-api-mode documentation}. * @param {gax} [gaxInstance]: loaded instance of `google-gax`. Useful if you @@ -102,7 +101,7 @@ export class SequenceServiceClient { * HTTP implementation. Load only fallback version and pass it to the constructor: * ``` * const gax = require('google-gax/build/src/fallback'); // avoids loading google-gax with gRPC - * const client = new SequenceServiceClient({fallback: 'rest'}, gax); + * const client = new SequenceServiceClient({fallback: true}, gax); * ``` */ constructor( @@ -171,7 +170,7 @@ export class SequenceServiceClient { } if (!opts.fallback) { clientHeader.push(`grpc/${this._gaxGrpc.grpcVersion}`); - } else if (opts.fallback === 'rest') { + } else { clientHeader.push(`rest/${this._gaxGrpc.grpcVersion}`); } if (opts.libName && opts.libVersion) { @@ -203,7 +202,7 @@ export class SequenceServiceClient { this.descriptors.stream = { attemptStreamingSequence: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.SERVER_STREAMING, - opts.fallback === 'rest', + !!opts.fallback, this._opts.newRetry ), }; From 038ad48fb3a0521821c1857beb49bab25b5d73fb Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Thu, 24 Aug 2023 14:11:52 -0400 Subject: [PATCH 06/35] fix lint --- test/showcase-echo-client/src/v1beta1/echo_client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/showcase-echo-client/src/v1beta1/echo_client.ts b/test/showcase-echo-client/src/v1beta1/echo_client.ts index a1a61ab90..d567351ce 100644 --- a/test/showcase-echo-client/src/v1beta1/echo_client.ts +++ b/test/showcase-echo-client/src/v1beta1/echo_client.ts @@ -227,7 +227,6 @@ export class EchoClient { this._gaxModule.StreamType.SERVER_STREAMING, !!opts.fallback, this._opts.newRetry - ), collect: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.CLIENT_STREAMING, From 5c7fc4e8e46040d0ca8e842bd4ea7f949b7a546d Mon Sep 17 00:00:00 2001 From: Alexander Fenster Date: Thu, 24 Aug 2023 20:35:51 +0000 Subject: [PATCH 07/35] test: use a custom header for testing headers --- test/browser-test/test/test.grpc-fallback.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/browser-test/test/test.grpc-fallback.ts b/test/browser-test/test/test.grpc-fallback.ts index 49b93c3cd..030cbcac6 100644 --- a/test/browser-test/test/test.grpc-fallback.ts +++ b/test/browser-test/test/test.grpc-fallback.ts @@ -229,10 +229,7 @@ describe('grpc-fallback', () => { const options: any = {}; options.otherArgs = {}; options.otherArgs.headers = {}; - options.otherArgs.headers['x-goog-request-params'] = - fallback.routingHeader.fromParams({ - abc: 'def', - }); + options.otherArgs.headers['x-test-header'] = 'value'; const response = requestObject; // eslint-disable-next-line no-undef const savedFetch = window.fetch; @@ -240,7 +237,7 @@ describe('grpc-fallback', () => { // eslint-disable-next-line no-undef window.fetch = (url, options) => { // @ts-ignore - assert.strictEqual(options.headers['x-goog-request-params'], 'abc=def'); + assert.strictEqual(options.headers['x-test-header'], 'value'); return Promise.resolve({ ok: true, arrayBuffer: () => { From 5a4bd71cdcdffd1231588a236388f983c9a6e121 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 30 Aug 2023 11:27:32 -0400 Subject: [PATCH 08/35] rename "newRetry" parameter --- src/clientInterface.ts | 2 +- .../src/v1beta1/echo_client.ts | 2 +- .../src/v1beta1/sequence_service_client.ts | 2 +- test/test-application/src/index.ts | 49 ++++++++++--------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/clientInterface.ts b/src/clientInterface.ts index 2f9edcda2..94ae67701 100644 --- a/src/clientInterface.ts +++ b/src/clientInterface.ts @@ -37,7 +37,7 @@ export interface ClientOptions clientConfig?: gax.ClientConfig; fallback?: boolean | 'rest' | 'proto'; apiEndpoint?: string; - newRetry?: boolean; + gaxServerStreamingRetries?: boolean; } export interface Descriptors { diff --git a/test/showcase-echo-client/src/v1beta1/echo_client.ts b/test/showcase-echo-client/src/v1beta1/echo_client.ts index d567351ce..856d308a2 100644 --- a/test/showcase-echo-client/src/v1beta1/echo_client.ts +++ b/test/showcase-echo-client/src/v1beta1/echo_client.ts @@ -226,7 +226,7 @@ export class EchoClient { expand: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.SERVER_STREAMING, !!opts.fallback, - this._opts.newRetry + this._opts.gaxServerStreamingRetries ), collect: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.CLIENT_STREAMING, diff --git a/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts index dbc82a9b6..a07bc75f6 100644 --- a/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts +++ b/test/showcase-echo-client/src/v1beta1/sequence_service_client.ts @@ -203,7 +203,7 @@ export class SequenceServiceClient { attemptStreamingSequence: new this._gaxModule.StreamDescriptor( this._gaxModule.StreamType.SERVER_STREAMING, !!opts.fallback, - this._opts.newRetry + this._opts.gaxServerStreamingRetries ), }; diff --git a/test/test-application/src/index.ts b/test/test-application/src/index.ts index cee6e0072..dd92fb59e 100644 --- a/test/test-application/src/index.ts +++ b/test/test-application/src/index.ts @@ -37,10 +37,10 @@ async function testShowcase() { sslCreds: grpc.credentials.createInsecure(), }; - const grpcClientOptsWithNewRetry = { + const grpcClientOptsWithServerStreamingRetries = { grpc, sslCreds: grpc.credentials.createInsecure(), - newRetry: true, + gaxServerStreamingRetries: true, }; const fakeGoogleAuth = { @@ -70,10 +70,11 @@ async function testShowcase() { }; const grpcClient = new EchoClient(grpcClientOpts); - const grpcClientWithNewRetry = new EchoClient(grpcClientOptsWithNewRetry); - const grpcSequenceClientWithNewRetry = new SequenceServiceClient( - grpcClientOptsWithNewRetry + const grpcClientWithServerStreamingRetries = new EchoClient( + grpcClientOptsWithServerStreamingRetries ); + const grpcSequenceClientWithServerStreamingRetries = + new SequenceServiceClient(grpcClientOptsWithServerStreamingRetries); const restClient = new EchoClient(restClientOpts); const restClientCompat = new EchoClient(restClientOptsCompat); @@ -103,49 +104,51 @@ async function testShowcase() { await testCollectThrows(restClientCompat); // REGAPIC does not support client streaming await testChatThrows(restClientCompat); // REGAPIC does not support bidi streaming await testWait(restClientCompat); - // Testing with newRetry being true - await testServerStreamingRetryOptions(grpcSequenceClientWithNewRetry); + // Testing with gaxServerStreamingRetries being true + await testServerStreamingRetryOptions( + grpcSequenceClientWithServerStreamingRetries + ); await testServerStreamingRetriesWithShouldRetryFn( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); await testServerStreamingRetrieswithRetryOptions( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); await testServerStreamingRetrieswithRetryRequestOptions( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); await testServerStreamingRetrieswithRetryRequestOptionsResumptionStrategy( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); await testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResumptionStrategy( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); await testServerStreamingThrowsClassifiedTransientError( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); await testServerStreamingRetriesAndThrowsClassifiedTransientError( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); await testServerStreamingThrowsCannotSetTotalTimeoutMillisMaxRetries( - grpcSequenceClientWithNewRetry + grpcSequenceClientWithServerStreamingRetries ); - await testEcho(grpcClientWithNewRetry); - await testEchoError(grpcClientWithNewRetry); - await testExpand(grpcClientWithNewRetry); - await testPagedExpand(grpcClientWithNewRetry); - await testPagedExpandAsync(grpcClientWithNewRetry); - await testCollect(grpcClientWithNewRetry); - await testChat(grpcClientWithNewRetry); - await testWait(grpcClientWithNewRetry); + await testEcho(grpcClientWithServerStreamingRetries); + await testEchoError(grpcClientWithServerStreamingRetries); + await testExpand(grpcClientWithServerStreamingRetries); + await testPagedExpand(grpcClientWithServerStreamingRetries); + await testPagedExpandAsync(grpcClientWithServerStreamingRetries); + await testCollect(grpcClientWithServerStreamingRetries); + await testChat(grpcClientWithServerStreamingRetries); + await testWait(grpcClientWithServerStreamingRetries); } function createStreamingSequenceRequestFactory( From 12bd5d0d2c3ddd1efad31e8af07c5f8cacbdc1d0 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 30 Aug 2023 11:45:27 -0400 Subject: [PATCH 09/35] remove extend dependency --- package.json | 1 - src/streamingRetryRequest.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 231ee212b..17560ea6d 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,6 @@ "@types/long": "^4.0.0", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", - "extend": "^3.0.2", "google-auth-library": "^9.0.0", "node-fetch": "^2.6.1", "object-hash": "^3.0.0", diff --git a/src/streamingRetryRequest.ts b/src/streamingRetryRequest.ts index 0e32ff70e..12e4c8f7a 100644 --- a/src/streamingRetryRequest.ts +++ b/src/streamingRetryRequest.ts @@ -95,7 +95,7 @@ export function streamingRetryRequest( callback = opts; } - opts = extend({}, DEFAULTS, opts); + opts = Object.assign({}, DEFAULTS, opts); if (typeof opts.request === 'undefined') { try { From 288f3b10127e8c744a0bca9e8cce47216ce11c6c Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 30 Aug 2023 11:53:39 -0400 Subject: [PATCH 10/35] fix lint --- src/streamingRetryRequest.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/streamingRetryRequest.ts b/src/streamingRetryRequest.ts index 12e4c8f7a..c1275a0a0 100644 --- a/src/streamingRetryRequest.ts +++ b/src/streamingRetryRequest.ts @@ -13,7 +13,6 @@ // limitations under the License. const {PassThrough} = require('stream'); -const extend = require('extend'); const DEFAULTS = { /* From 8a7d5a21c8157cf6449281597813627af87bc17b Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Thu, 31 Aug 2023 16:00:42 -0400 Subject: [PATCH 11/35] fix issue where underlying errors were swallowed --- src/streamingCalls/streaming.ts | 22 +++++++++------------- test/test-application/src/index.ts | 18 +++++++++--------- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/streamingCalls/streaming.ts b/src/streamingCalls/streaming.ts index e5b18ecf6..fd37ebbbe 100644 --- a/src/streamingCalls/streaming.ts +++ b/src/streamingCalls/streaming.ts @@ -231,7 +231,6 @@ export class StreamProxy extends duplexify implements GRPCCallResult { ); this.retries!++; - const e = GoogleError.parseGRPCStatusDetails(error); let shouldRetry = this.defaultShouldRetry(e!, retry); if (typeof retry.retryCodesOrShouldRetryFn! === 'function') { @@ -250,13 +249,11 @@ export class StreamProxy extends duplexify implements GRPCCallResult { timeout = Math.min(timeoutCal, rpcTimeout, newDeadline); }, toSleep); } else { - const newError = new GoogleError( + e.note = 'Exception occurred in retry method that was ' + - 'not classified as transient' - ); - newError.code = Status.INVALID_ARGUMENT; - this.emit('error', newError); - this.destroy(newError); + 'not classified as transient'; + this.emit('error', e); + this.destroy(e); return; } @@ -437,13 +434,12 @@ export class StreamProxy extends duplexify implements GRPCCallResult { return retryStream; } } else { - const newError = new GoogleError( + const e = GoogleError.parseGRPCStatusDetails(error); + e.note = 'Exception occurred in retry method that was ' + - 'not classified as transient' - ); - newError.code = Status.INVALID_ARGUMENT; - this.emit('error', newError); - this.destroy(newError); + 'not classified as transient'; + this.emit('error', error); + this.destroy(error); return; } } else { diff --git a/test/test-application/src/index.ts b/test/test-application/src/index.ts index dd92fb59e..163857993 100644 --- a/test/test-application/src/index.ts +++ b/test/test-application/src/index.ts @@ -129,11 +129,11 @@ async function testShowcase() { grpcSequenceClientWithServerStreamingRetries ); - await testServerStreamingThrowsClassifiedTransientError( + await testServerStreamingThrowsClassifiedTransientErrorNote( grpcSequenceClientWithServerStreamingRetries ); - await testServerStreamingRetriesAndThrowsClassifiedTransientError( + await testServerStreamingRetriesAndThrowsClassifiedTransientErrorNote( grpcSequenceClientWithServerStreamingRetries ); @@ -840,12 +840,12 @@ async function testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResum }, (err: GoogleError) => { assert.strictEqual(err.code, 3); - assert.match(err.message, /not classified as transient/); + assert.match(err.note!, /not classified as transient/); } ); } -async function testServerStreamingThrowsClassifiedTransientError( +async function testServerStreamingThrowsClassifiedTransientErrorNote( client: SequenceServiceClient ) { const backoffSettings = createBackoffSettings( @@ -895,13 +895,13 @@ async function testServerStreamingThrowsClassifiedTransientError( assert(false); }, (err: GoogleError) => { - assert.strictEqual(err.code, 3); - assert.match(err.message, /not classified as transient/); + assert.strictEqual(err.code, 14); + assert.match(err.note!, /not classified as transient/); } ); } -async function testServerStreamingRetriesAndThrowsClassifiedTransientError( +async function testServerStreamingRetriesAndThrowsClassifiedTransientErrorNote( client: SequenceServiceClient ) { const backoffSettings = createBackoffSettings( @@ -951,8 +951,8 @@ async function testServerStreamingRetriesAndThrowsClassifiedTransientError( assert(false); }, (err: GoogleError) => { - assert.strictEqual(err.code, 3); - assert.match(err.message, /not classified as transient/); + assert.strictEqual(err.code, 4); + assert.match(err.note!, /not classified as transient/); } ); } From a40172b8b359667037d5eb2d6e581adb94bd814d Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 5 Sep 2023 14:36:34 -0400 Subject: [PATCH 12/35] resolve some comments --- src/createApiCall.ts | 6 +++--- src/gax.ts | 11 +---------- src/streamingRetryRequest.ts | 2 +- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/createApiCall.ts b/src/createApiCall.ts index 1acd12fe1..2e6b2689d 100644 --- a/src/createApiCall.ts +++ b/src/createApiCall.ts @@ -102,14 +102,14 @@ export function createApiCall( ?.streaming; const retry = thisSettings.retry; - if (!streaming && retry && retry.getResumptionRequestFn) { + if (!streaming && retry && retry?.getResumptionRequestFn) { throw new Error( 'Resumption strategy can only be used with server streaming retries' ); } - if (!streaming && retry && retry.retryCodesOrShouldRetryFn) { + if (!streaming && retry && retry?.retryCodesOrShouldRetryFn) { if ( - retry.retryCodesOrShouldRetryFn instanceof Array && + Array.isArray(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.length > 0 ) { retry.backoffSettings.initialRpcTimeoutMillis = diff --git a/src/gax.ts b/src/gax.ts index 8497d53d8..a1b56b954 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -240,12 +240,10 @@ export class CallSettings { // method are non-null, then that timeout value will be used to // override backoff settings. if (retry !== undefined && retry !== null) { - // if verify that the retry codes/retry function are not null or undefined if ( retry.retryCodesOrShouldRetryFn !== null && retry.retryCodesOrShouldRetryFn !== undefined ) { - // if it's an array of retry codes, make sure it has an element or check if it's a function if ( (retry.retryCodesOrShouldRetryFn instanceof Array && retry.retryCodesOrShouldRetryFn.length > 0) || @@ -263,12 +261,10 @@ export class CallSettings { if ('timeout' in options) { timeout = options.timeout!; if (retry !== undefined && retry !== null) { - // if verify that the retry codes/retry function are not null or undefined if ( retry.retryCodesOrShouldRetryFn !== null && retry.retryCodesOrShouldRetryFn !== undefined ) { - // if it's an array of retry codes, make sure it has an element or if it's a function if ( (retry.retryCodesOrShouldRetryFn instanceof Array && retry.retryCodesOrShouldRetryFn.length > 0) || @@ -307,11 +303,7 @@ export class CallSettings { isBundling = options.isBundling!; } - if ('maxRetries' in options && typeof options.maxRetries !== 'undefined') { - console.log( - 'removing timeout in favor of max retries', - retry!.backoffSettings!.totalTimeoutMillis - ); + if ('maxRetries' in options && options.maxRetries !== undefined) { retry!.backoffSettings!.maxRetries = options.maxRetries; delete retry!.backoffSettings!.totalTimeoutMillis; } @@ -322,7 +314,6 @@ export class CallSettings { if ('apiName' in options) { apiName = options.apiName; } - // if ('retryRequestOptions' in options) { retryRequestOptions = options.retryRequestOptions; } diff --git a/src/streamingRetryRequest.ts b/src/streamingRetryRequest.ts index c1275a0a0..6ecdac9c3 100644 --- a/src/streamingRetryRequest.ts +++ b/src/streamingRetryRequest.ts @@ -96,7 +96,7 @@ export function streamingRetryRequest( opts = Object.assign({}, DEFAULTS, opts); - if (typeof opts.request === 'undefined') { + if (opts.request === undefined) { try { // eslint-disable-next-line node/no-unpublished-require opts.request = require('request'); From ee9261f8e478dfffdcb39ff3fd4a21d81a180954 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 5 Sep 2023 15:37:46 -0400 Subject: [PATCH 13/35] replace console logs with our warn module --- src/gax.ts | 25 ++++++++++++--------- test/unit/streaming.ts | 50 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/gax.ts b/src/gax.ts index a1b56b954..ea095d10a 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -358,20 +358,25 @@ export function checkRetryOptions( throw new Error('Only one of retry or retryRequestOptions may be set'); } else { if (options.retryRequestOptions !== undefined) { - // // Retry settings - if (options.retryRequestOptions.objectMode) { - console.log( - 'objectMode override is not supported. It is set to true internally by default in gax.' + if (options.retryRequestOptions.objectMode !== undefined) { + warn( + 'retry_request_options', + 'objectMode override is not supported. It is set to true internally by default in gax.', + 'UnsupportedParameterWarning' ); } - if (options.retryRequestOptions.noResponseRetries) { - console.log( - 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.' + if (options.retryRequestOptions.noResponseRetries !== undefined) { + warn( + 'retry_request_options', + 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.', + 'UnsupportedParameterWarning' ); } - if (options.retryRequestOptions.currentRetryAttempt) { - console.log( - 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.' + if (options.retryRequestOptions.currentRetryAttempt !== undefined) { + warn( + 'retry_request_options', + 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.', + 'UnsupportedParameterWarning' ); } let retryCodesOrShouldRetryFn; diff --git a/test/unit/streaming.ts b/test/unit/streaming.ts index 38efc7d34..79c543857 100644 --- a/test/unit/streaming.ts +++ b/test/unit/streaming.ts @@ -906,9 +906,6 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en .stub(StreamingApiCaller.prototype, 'call') .callsFake((apiCall, argument, settings, stream) => { try { - // Retry settings - // TODO: retries - one to one with maxRetries - this should be undefined if timeout is defined, I think - // //Backoff settings assert(settings.retry); assert(typeof settings.retryRequestOptions === 'undefined'); assert.strictEqual( @@ -962,7 +959,6 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en return true; }, }; - // make the call with both options passed at call time apiCall( {}, { @@ -970,7 +966,7 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en } ); - assert.strictEqual(warnStub.callCount, 1); + assert.strictEqual(warnStub.callCount, 4); assert( warnStub.calledWith( 'retry_request_options', @@ -978,6 +974,27 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en 'DeprecationWarning' ) ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'objectMode override is not supported. It is set to true internally by default in gax.', + 'UnsupportedParameterWarning' + ) + ); }); it('throws a warning and converts retryRequestOptions for new retry behavior - no maxRetries', done => { const warnStub = sinon.stub(warnings, 'warn'); @@ -1044,7 +1061,7 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en } ); - assert.strictEqual(warnStub.callCount, 1); + assert.strictEqual(warnStub.callCount, 4); assert( warnStub.calledWith( 'retry_request_options', @@ -1052,6 +1069,27 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en 'DeprecationWarning' ) ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'objectMode override is not supported. It is set to true internally by default in gax.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.', + 'UnsupportedParameterWarning' + ) + ); }); }); describe('warns/errors about server streaming retry behavior when gaxStreamingRetries is disabled', () => { From e5e332e0a379520f60296da7889216151ea881a7 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 5 Sep 2023 15:40:48 -0400 Subject: [PATCH 14/35] utilize enum --- src/gax.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gax.ts b/src/gax.ts index ea095d10a..ebfdf50ce 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -22,6 +22,7 @@ import type {Message} from 'protobufjs'; import {warn} from './warnings'; import {BundleOptions} from './bundlingCalls/bundleExecutor'; import {toLowerCamelCase} from './util'; +import {Status} from './status'; /** * Encapsulates the overridable settings for a particular API call. @@ -386,7 +387,7 @@ export function checkRetryOptions( options.retryRequestOptions.shouldRetryFn; } else { // default to retry code 14 per AIP-194 - retryCodesOrShouldRetryFn = [14]; + retryCodesOrShouldRetryFn = [Status.UNAVAILABLE]; } //Backoff settings From 1d0d0f7ee4c5476a96dc334411718ff469c7e116 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 5 Sep 2023 15:44:49 -0400 Subject: [PATCH 15/35] replace "any" with "GoogleError" --- src/gax.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gax.ts b/src/gax.ts index ebfdf50ce..00f25c117 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -20,6 +20,7 @@ import type {Message} from 'protobufjs'; import {warn} from './warnings'; +import {GoogleError} from './googleError'; import {BundleOptions} from './bundlingCalls/bundleExecutor'; import {toLowerCamelCase} from './util'; import {Status} from './status'; @@ -75,11 +76,11 @@ import {Status} from './status'; * @property {(function)} getResumptionRequestFn */ export class RetryOptions { - retryCodesOrShouldRetryFn: number[] | ((error: any) => boolean); + retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean); backoffSettings: BackoffSettings; getResumptionRequestFn?: (response: any) => any; constructor( - retryCodesOrShouldRetryFn: number[] | ((error: any) => boolean), + retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean), backoffSettings: BackoffSettings, getResumptionRequestFn?: (response: any) => any ) { @@ -111,7 +112,7 @@ export interface RetryRequestOptions { retries?: number; noResponseRetries?: number; currentRetryAttempt?: number; - shouldRetryFn?: (error: any) => boolean; + shouldRetryFn?: (error: GoogleError) => boolean; maxRetryDelay?: number; retryDelayMultiplier?: number; totalTimeout?: number; From 666671020f49df10a6a362e7ff74dd59e96f3d34 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 5 Sep 2023 15:57:44 -0400 Subject: [PATCH 16/35] update array checks --- src/gax.ts | 4 ++-- src/normalCalls/retries.ts | 2 +- src/streamingCalls/streaming.ts | 2 +- test/unit/gax.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gax.ts b/src/gax.ts index 00f25c117..b0e449e5a 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -247,7 +247,7 @@ export class CallSettings { retry.retryCodesOrShouldRetryFn !== undefined ) { if ( - (retry.retryCodesOrShouldRetryFn instanceof Array && + (Array.isArray(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.length > 0) || retry.retryCodesOrShouldRetryFn instanceof Function ) { @@ -268,7 +268,7 @@ export class CallSettings { retry.retryCodesOrShouldRetryFn !== undefined ) { if ( - (retry.retryCodesOrShouldRetryFn instanceof Array && + (Array.isArray(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.length > 0) || retry.retryCodesOrShouldRetryFn instanceof Function ) { diff --git a/src/normalCalls/retries.ts b/src/normalCalls/retries.ts index e00e5087d..a7204402f 100644 --- a/src/normalCalls/retries.ts +++ b/src/normalCalls/retries.ts @@ -109,7 +109,7 @@ export function retryable( canceller = null; if ( retry.retryCodesOrShouldRetryFn !== undefined && - retry.retryCodesOrShouldRetryFn instanceof Array && + Array.isArray(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.indexOf(err!.code!) < 0 ) { err.note = diff --git a/src/streamingCalls/streaming.ts b/src/streamingCalls/streaming.ts index fd37ebbbe..7ec6b6495 100644 --- a/src/streamingCalls/streaming.ts +++ b/src/streamingCalls/streaming.ts @@ -353,7 +353,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { defaultShouldRetry(error: GoogleError, retry: RetryOptions) { if ( - retry.retryCodesOrShouldRetryFn instanceof Array && + Array.isArray(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.indexOf(error!.code!) < 0 ) { return false; diff --git a/test/unit/gax.ts b/test/unit/gax.ts index 62ff7cfea..7f20f49c7 100644 --- a/test/unit/gax.ts +++ b/test/unit/gax.ts @@ -79,7 +79,7 @@ function expectRetryOptions(obj: gax.RetryOptions) { assert.ok(obj.hasOwnProperty(k)) ); assert.ok( - obj.retryCodesOrShouldRetryFn instanceof Array || + Array.isArray(obj.retryCodesOrShouldRetryFn) || obj.retryCodesOrShouldRetryFn instanceof Function ); expectBackoffSettings(obj.backoffSettings); From a2b9cc90124194c5cd1c1d66f392b1a6ea7da879 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 5 Sep 2023 15:58:40 -0400 Subject: [PATCH 17/35] remove debug statement --- test/unit/streaming.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/streaming.ts b/test/unit/streaming.ts index 79c543857..18f58c7c6 100644 --- a/test/unit/streaming.ts +++ b/test/unit/streaming.ts @@ -796,7 +796,6 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en sinon .stub(streaming.StreamProxy.prototype, 'forwardEventsNewImplementation') .callsFake((stream, retry): any => { - console.log('hello i am here', retry); assert(stream instanceof internal.Stream); assert(retry.getResumptionRequestFn instanceof Function); done(); From c6cccb921c34d4f958f9ba95a026957c438f609a Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Tue, 5 Sep 2023 16:38:26 -0400 Subject: [PATCH 18/35] remove need to typecast --- src/createApiCall.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/createApiCall.ts b/src/createApiCall.ts index 2e6b2689d..d7376d464 100644 --- a/src/createApiCall.ts +++ b/src/createApiCall.ts @@ -72,10 +72,10 @@ export function createApiCall( callback?: APICallback ) => { let currentApiCaller = apiCaller; - const gaxStreamingRetries = (currentApiCaller as StreamingApiCaller) - .descriptor?.gaxStreamingRetries; + let thisSettings: CallSettings; if (currentApiCaller instanceof StreamingApiCaller) { + const gaxStreamingRetries = currentApiCaller.descriptor?.gaxStreamingRetries; // If Gax streaming retries are enabled, check settings passed at call time and convert parameters if needed const thisSettingsTemp = checkRetryOptions( callOptions, From 62875cc96a1ea292ac510e38803c3444e4ee804d Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 6 Sep 2023 13:19:24 -0400 Subject: [PATCH 19/35] null coalescing fix --- src/createApiCall.ts | 6 +++--- test/unit/streaming.ts | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/createApiCall.ts b/src/createApiCall.ts index d7376d464..3b7286a8c 100644 --- a/src/createApiCall.ts +++ b/src/createApiCall.ts @@ -75,7 +75,8 @@ export function createApiCall( let thisSettings: CallSettings; if (currentApiCaller instanceof StreamingApiCaller) { - const gaxStreamingRetries = currentApiCaller.descriptor?.gaxStreamingRetries; + const gaxStreamingRetries = + currentApiCaller.descriptor?.gaxStreamingRetries; // If Gax streaming retries are enabled, check settings passed at call time and convert parameters if needed const thisSettingsTemp = checkRetryOptions( callOptions, @@ -112,8 +113,7 @@ export function createApiCall( Array.isArray(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.length > 0 ) { - retry.backoffSettings.initialRpcTimeoutMillis = - retry.backoffSettings.initialRpcTimeoutMillis || + retry.backoffSettings.initialRpcTimeoutMillis ??= thisSettings.timeout; return retryable( func, diff --git a/test/unit/streaming.ts b/test/unit/streaming.ts index 18f58c7c6..8270f980d 100644 --- a/test/unit/streaming.ts +++ b/test/unit/streaming.ts @@ -1097,7 +1097,6 @@ describe('warns/errors about server streaming retry behavior when gaxStreamingRe sinon.restore(); }); - // NO RETRY BEHAVIOR ENABLED it('throws a warning when retryRequestOptions are passed', done => { const warnStub = sinon.stub(warnings, 'warn'); From a0951637f41ef8b0c90641dfae4022a29a300433 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 6 Sep 2023 14:13:47 -0400 Subject: [PATCH 20/35] reduce duplication --- src/gax.ts | 234 ++++++++++++++++++++++++----------------------------- 1 file changed, 106 insertions(+), 128 deletions(-) diff --git a/src/gax.ts b/src/gax.ts index b0e449e5a..12205560d 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -238,6 +238,11 @@ export class CallSettings { let apiName = this.apiName; let retryRequestOptions = this.retryRequestOptions; + // If the user provides a timeout to the method, that timeout value will be used + // to override the backoff settings. + if ('timeout' in options) { + timeout = options.timeout!; + } // If a method-specific timeout is set in the service config, and the retry codes for that // method are non-null, then that timeout value will be used to // override backoff settings. @@ -258,27 +263,6 @@ export class CallSettings { } } - // If the user provides a timeout to the method, that timeout value will be used - // to override the backoff settings. - if ('timeout' in options) { - timeout = options.timeout!; - if (retry !== undefined && retry !== null) { - if ( - retry.retryCodesOrShouldRetryFn !== null && - retry.retryCodesOrShouldRetryFn !== undefined - ) { - if ( - (Array.isArray(retry.retryCodesOrShouldRetryFn) && - retry.retryCodesOrShouldRetryFn.length > 0) || - retry.retryCodesOrShouldRetryFn instanceof Function - ) { - retry.backoffSettings.initialRpcTimeoutMillis = timeout; - retry.backoffSettings.maxRpcTimeoutMillis = timeout; - retry.backoffSettings.totalTimeoutMillis = timeout; - } - } - } - } if ('retry' in options) { retry = mergeRetryOptions(retry || ({} as RetryOptions), options.retry!); } @@ -344,120 +328,114 @@ export class CallSettings { * @return {CallOptions} A new CallOptions object. * */ - export function checkRetryOptions( options?: CallOptions, gaxStreamingRetries?: boolean ): CallOptions | undefined { // options will be undefined if no CallOptions object is passed at call time - if (options) { - // if a user provided retry AND retryRequestOptions at call time, throw an error - if (gaxStreamingRetries) { - if ( - options.retry !== undefined && - options.retryRequestOptions !== undefined - ) { - throw new Error('Only one of retry or retryRequestOptions may be set'); - } else { - if (options.retryRequestOptions !== undefined) { - if (options.retryRequestOptions.objectMode !== undefined) { - warn( - 'retry_request_options', - 'objectMode override is not supported. It is set to true internally by default in gax.', - 'UnsupportedParameterWarning' - ); - } - if (options.retryRequestOptions.noResponseRetries !== undefined) { - warn( - 'retry_request_options', - 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.', - 'UnsupportedParameterWarning' - ); - } - if (options.retryRequestOptions.currentRetryAttempt !== undefined) { - warn( - 'retry_request_options', - 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.', - 'UnsupportedParameterWarning' - ); - } - let retryCodesOrShouldRetryFn; - - if (options.retryRequestOptions.shouldRetryFn) { - retryCodesOrShouldRetryFn = - options.retryRequestOptions.shouldRetryFn; - } else { - // default to retry code 14 per AIP-194 - retryCodesOrShouldRetryFn = [Status.UNAVAILABLE]; - } - - //Backoff settings - if ( - options.retryRequestOptions.retries !== null && - options.retryRequestOptions !== undefined - ) { - // don't want to just check for truthiness here in case it's 0 - options.maxRetries = options.retryRequestOptions.retries; - } - // create a default backoff settings object in case the user didn't provide overrides for everything - const backoffSettings = createDefaultBackoffSettings(); - let maxRetryDelayMillis; - let totalTimeoutMillis; - // maxRetryDelay - this is in seconds, need to convert to milliseconds - if (options.retryRequestOptions.maxRetryDelay) { - maxRetryDelayMillis = - options.retryRequestOptions.maxRetryDelay * 1000; - } - // retryDelayMultiplier - should be a one to one mapping to retryDelayMultiplier - const retryDelayMultiplier = - options.retryRequestOptions.retryDelayMultiplier; - // totalTimeout - this is in seconds and needs to be converted to milliseconds and the totalTimeoutMillis parameter - if (options.retryRequestOptions.totalTimeout) { - totalTimeoutMillis = - options.retryRequestOptions.totalTimeout * 1000; - } - - // for the variables the user wants to override, override in the backoff settings object we made - if (maxRetryDelayMillis) { - backoffSettings.maxRetryDelayMillis = maxRetryDelayMillis; - } - if (retryDelayMultiplier) { - backoffSettings.retryDelayMultiplier = retryDelayMultiplier; - } - if (totalTimeoutMillis) { - backoffSettings.totalTimeoutMillis = totalTimeoutMillis; - } - - const convertedRetryOptions = createRetryOptions( - retryCodesOrShouldRetryFn, - backoffSettings - ); - options.retry = convertedRetryOptions; - delete options.retryRequestOptions; // completely remove them to avoid any further confusion - warn( - 'retry_request_options', - 'retryRequestOptions will be deprecated in a future release. Please use retryOptions to pass retry options at call time', - 'DeprecationWarning' - ); - } - } + if (!options) { + return options; + } + // if a user provided retry AND retryRequestOptions at call time, throw an error + // otherwise, convert supported parameters + if (!gaxStreamingRetries) { + // if user is opted into legacy settings but has passed retry settings, let them know there might be an issue if it's a streaming call + if (options.retry !== undefined) { + warn( + 'legacy_streaming_retry_behavior', + 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', + 'DeprecationWarning' + ); + } + if (options.retryRequestOptions !== undefined) { + warn( + 'legacy_streaming_retry_request_behavior', + 'Legacy streaming retry behavior will not honor retryRequestOptions passed at call time. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will convert retryRequestOptions to retry parameters by default in future releases.', + 'DeprecationWarning' + ); + } + return options; + } + if ( + options.retry !== undefined && + options.retryRequestOptions !== undefined + ) { + throw new Error('Only one of retry or retryRequestOptions may be set'); + } // handle parameter conversion from retryRequestOptions to retryOptions + if (options.retryRequestOptions !== undefined) { + if (options.retryRequestOptions.objectMode !== undefined) { + warn( + 'retry_request_options', + 'objectMode override is not supported. It is set to true internally by default in gax.', + 'UnsupportedParameterWarning' + ); + } + if (options.retryRequestOptions.noResponseRetries !== undefined) { + warn( + 'retry_request_options', + 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.', + 'UnsupportedParameterWarning' + ); + } + if (options.retryRequestOptions.currentRetryAttempt !== undefined) { + warn( + 'retry_request_options', + 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.', + 'UnsupportedParameterWarning' + ); + } + let retryCodesOrShouldRetryFn; + + if (options.retryRequestOptions.shouldRetryFn) { + retryCodesOrShouldRetryFn = options.retryRequestOptions.shouldRetryFn; } else { - // if user is opted into legacy settings but has passed retry settings, let them know there might be an issue if it's a streaming call - if (options.retry !== undefined) { - warn( - 'legacy_streaming_retry_behavior', - 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', - 'DeprecationWarning' - ); - } - if (options.retryRequestOptions !== undefined) { - warn( - 'legacy_streaming_retry_request_behavior', - 'Legacy streaming retry behavior will not honor retryRequestOptions passed at call time. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will convert retryRequestOptions to retry parameters by default in future releases.', - 'DeprecationWarning' - ); - } + // default to retry code 14 per AIP-194 + retryCodesOrShouldRetryFn = [Status.UNAVAILABLE]; + } + + //Backoff settings + if (options?.retryRequestOptions?.retries) { + // don't want to just check for truthiness here in case it's 0 + options.maxRetries = options.retryRequestOptions.retries; } + // create a default backoff settings object in case the user didn't provide overrides for everything + const backoffSettings = createDefaultBackoffSettings(); + let maxRetryDelayMillis; + let totalTimeoutMillis; + // maxRetryDelay - this is in seconds, need to convert to milliseconds + if (options.retryRequestOptions.maxRetryDelay) { + maxRetryDelayMillis = options.retryRequestOptions.maxRetryDelay * 1000; + } + // retryDelayMultiplier - should be a one to one mapping to retryDelayMultiplier + const retryDelayMultiplier = + options.retryRequestOptions.retryDelayMultiplier; + // totalTimeout - this is in seconds and needs to be converted to milliseconds and the totalTimeoutMillis parameter + if (options.retryRequestOptions.totalTimeout) { + totalTimeoutMillis = options.retryRequestOptions.totalTimeout * 1000; + } + + // for the variables the user wants to override, override in the backoff settings object we made + if (maxRetryDelayMillis) { + backoffSettings.maxRetryDelayMillis = maxRetryDelayMillis; + } + if (retryDelayMultiplier) { + backoffSettings.retryDelayMultiplier = retryDelayMultiplier; + } + if (totalTimeoutMillis) { + backoffSettings.totalTimeoutMillis = totalTimeoutMillis; + } + + const convertedRetryOptions = createRetryOptions( + retryCodesOrShouldRetryFn, + backoffSettings + ); + options.retry = convertedRetryOptions; + delete options.retryRequestOptions; // completely remove them to avoid any further confusion + warn( + 'retry_request_options', + 'retryRequestOptions will be deprecated in a future release. Please use retryOptions to pass retry options at call time', + 'DeprecationWarning' + ); } return options; } From 2df47aab4e388615afca6207aaf3cb4054f4695c Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 6 Sep 2023 15:57:39 -0400 Subject: [PATCH 21/35] clean up some optional chaining --- src/gax.ts | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/gax.ts b/src/gax.ts index 12205560d..f17961dc0 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -246,21 +246,14 @@ export class CallSettings { // If a method-specific timeout is set in the service config, and the retry codes for that // method are non-null, then that timeout value will be used to // override backoff settings. - if (retry !== undefined && retry !== null) { - if ( - retry.retryCodesOrShouldRetryFn !== null && - retry.retryCodesOrShouldRetryFn !== undefined - ) { - if ( - (Array.isArray(retry.retryCodesOrShouldRetryFn) && - retry.retryCodesOrShouldRetryFn.length > 0) || - retry.retryCodesOrShouldRetryFn instanceof Function - ) { - retry.backoffSettings.initialRpcTimeoutMillis = timeout; - retry.backoffSettings.maxRpcTimeoutMillis = timeout; - retry.backoffSettings.totalTimeoutMillis = timeout; - } - } + if ( + (Array.isArray(retry?.retryCodesOrShouldRetryFn) && + retry!.retryCodesOrShouldRetryFn.length > 0) || + retry?.retryCodesOrShouldRetryFn instanceof Function + ) { + retry!.backoffSettings.initialRpcTimeoutMillis = timeout; + retry!.backoffSettings.maxRpcTimeoutMillis = timeout; + retry!.backoffSettings.totalTimeoutMillis = timeout; } if ('retry' in options) { From 152981e4e28622f5be8bc123cceecb37d06587fa Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Thu, 7 Sep 2023 14:28:48 -0400 Subject: [PATCH 22/35] make error optional --- src/gax.ts | 6 +++--- test/test-application/src/index.ts | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/gax.ts b/src/gax.ts index f17961dc0..54fef2e4c 100644 --- a/src/gax.ts +++ b/src/gax.ts @@ -76,11 +76,11 @@ import {Status} from './status'; * @property {(function)} getResumptionRequestFn */ export class RetryOptions { - retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean); + retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean); backoffSettings: BackoffSettings; getResumptionRequestFn?: (response: any) => any; constructor( - retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean), + retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean), backoffSettings: BackoffSettings, getResumptionRequestFn?: (response: any) => any ) { @@ -112,7 +112,7 @@ export interface RetryRequestOptions { retries?: number; noResponseRetries?: number; currentRetryAttempt?: number; - shouldRetryFn?: (error: GoogleError) => boolean; + shouldRetryFn?: (error?: GoogleError) => boolean; maxRetryDelay?: number; retryDelayMultiplier?: number; totalTimeout?: number; diff --git a/test/test-application/src/index.ts b/test/test-application/src/index.ts index 163857993..a233f4249 100644 --- a/test/test-application/src/index.ts +++ b/test/test-application/src/index.ts @@ -586,8 +586,8 @@ async function testServerStreamingRetriesWithShouldRetryFn( client: SequenceServiceClient ) { const finalData: string[] = []; - const shouldRetryFn = function checkRetry(error: GoogleError) { - return [14, 4].includes(error.code!); + const shouldRetryFn = function checkRetry(error?: GoogleError) { + return [14, 4].includes(error!.code!); }; const backoffSettings = createBackoffSettings( @@ -656,8 +656,8 @@ async function testServerStreamingRetrieswithRetryRequestOptions( totalTimeout: 650, noResponseRetries: 3, currentRetryAttempt: 0, - shouldRetryFn: function checkRetry(error: GoogleError) { - return [14, 4].includes(error.code!); + shouldRetryFn: function checkRetry(error?: GoogleError) { + return [14, 4].includes(error!.code!); }, }; @@ -707,8 +707,8 @@ async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrate client: SequenceServiceClient ) { const finalData: string[] = []; - const shouldRetryFn = (error: GoogleError) => { - return [4, 14].includes(error.code!); + const shouldRetryFn = (error?: GoogleError) => { + return [4, 14].includes(error!.code!); }; const backoffSettings = createBackoffSettings( 10000, @@ -780,8 +780,8 @@ async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrate async function testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResumptionStrategy( client: SequenceServiceClient ) { - const shouldRetryFn = (error: GoogleError) => { - return [4, 14].includes(error.code!); + const shouldRetryFn = (error?: GoogleError) => { + return [4, 14].includes(error!.code!); }; const backoffSettings = createBackoffSettings( 10000, From 9251c20d313f13819563bd05ad423e7a70ea7d07 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 27 Sep 2023 16:45:30 -0400 Subject: [PATCH 23/35] WIP: use nullish coalescing and optional chaining for parameter converesion --- gax/src/gax.ts | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 54fef2e4c..62f9672fd 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -378,16 +378,11 @@ export function checkRetryOptions( ); } let retryCodesOrShouldRetryFn; + retryCodesOrShouldRetryFn = options?.retryRequestOptions?.shouldRetryFn ?? [Status.UNAVAILABLE]; - if (options.retryRequestOptions.shouldRetryFn) { - retryCodesOrShouldRetryFn = options.retryRequestOptions.shouldRetryFn; - } else { - // default to retry code 14 per AIP-194 - retryCodesOrShouldRetryFn = [Status.UNAVAILABLE]; - } - //Backoff settings - if (options?.retryRequestOptions?.retries) { + //Backoff settings //TODO(coleleah): use nullish coalescing assignment + if (options?.retryRequestOptions?.retries) { //TODO(coleleah) this doesn't match comment below it // don't want to just check for truthiness here in case it's 0 options.maxRetries = options.retryRequestOptions.retries; } @@ -396,27 +391,26 @@ export function checkRetryOptions( let maxRetryDelayMillis; let totalTimeoutMillis; // maxRetryDelay - this is in seconds, need to convert to milliseconds + //TODO(coleleah): simplify with nullisch coalescing if possible and optional chaining if (options.retryRequestOptions.maxRetryDelay) { maxRetryDelayMillis = options.retryRequestOptions.maxRetryDelay * 1000; } // retryDelayMultiplier - should be a one to one mapping to retryDelayMultiplier const retryDelayMultiplier = - options.retryRequestOptions.retryDelayMultiplier; - // totalTimeout - this is in seconds and needs to be converted to milliseconds and the totalTimeoutMillis parameter + options?.retryRequestOptions?.retryDelayMultiplier ?? backoffSettings.retryDelayMultiplier; + // this is in seconds and needs to be converted to milliseconds and the totalTimeoutMillis parameter + //TODO(coleleah) if (options.retryRequestOptions.totalTimeout) { totalTimeoutMillis = options.retryRequestOptions.totalTimeout * 1000; } + // totalTimeoutMillis = (options?.retryRequestOptions?.totalTimeout * 1000) ?? totalTimeoutMillis; + // for the variables the user wants to override, override in the backoff settings object we made - if (maxRetryDelayMillis) { - backoffSettings.maxRetryDelayMillis = maxRetryDelayMillis; - } - if (retryDelayMultiplier) { - backoffSettings.retryDelayMultiplier = retryDelayMultiplier; - } - if (totalTimeoutMillis) { - backoffSettings.totalTimeoutMillis = totalTimeoutMillis; - } + backoffSettings.maxRetryDelayMillis = maxRetryDelayMillis ?? backoffSettings.maxRetryDelayMillis; + backoffSettings.retryDelayMultiplier = retryDelayMultiplier ?? backoffSettings.retryDelayMultiplier; + backoffSettings.totalTimeoutMillis = totalTimeoutMillis ?? backoffSettings.totalTimeoutMillis; + const convertedRetryOptions = createRetryOptions( retryCodesOrShouldRetryFn, From a7377988a4b0822e6b6dc30ec1d5dd7db1b0af0f Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 27 Sep 2023 17:59:19 -0400 Subject: [PATCH 24/35] more nullish coalescing and optional chaining --- gax/src/gax.ts | 38 ++++---- gax/test/unit/streaming.ts | 191 +++++++++++++++++++++++++++++++++++++ 2 files changed, 208 insertions(+), 21 deletions(-) diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 62f9672fd..02d50da0d 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -332,7 +332,6 @@ export function checkRetryOptions( // if a user provided retry AND retryRequestOptions at call time, throw an error // otherwise, convert supported parameters if (!gaxStreamingRetries) { - // if user is opted into legacy settings but has passed retry settings, let them know there might be an issue if it's a streaming call if (options.retry !== undefined) { warn( 'legacy_streaming_retry_behavior', @@ -354,7 +353,7 @@ export function checkRetryOptions( options.retryRequestOptions !== undefined ) { throw new Error('Only one of retry or retryRequestOptions may be set'); - } // handle parameter conversion from retryRequestOptions to retryOptions + } // handles parameter conversion from retryRequestOptions to retryOptions if (options.retryRequestOptions !== undefined) { if (options.retryRequestOptions.objectMode !== undefined) { warn( @@ -377,40 +376,37 @@ export function checkRetryOptions( 'UnsupportedParameterWarning' ); } - let retryCodesOrShouldRetryFn; - retryCodesOrShouldRetryFn = options?.retryRequestOptions?.shouldRetryFn ?? [Status.UNAVAILABLE]; + const retryCodesOrShouldRetryFn = options?.retryRequestOptions?.shouldRetryFn ?? [ + Status.UNAVAILABLE, + ]; - - //Backoff settings //TODO(coleleah): use nullish coalescing assignment - if (options?.retryRequestOptions?.retries) { //TODO(coleleah) this doesn't match comment below it - // don't want to just check for truthiness here in case it's 0 - options.maxRetries = options.retryRequestOptions.retries; - } + //Backoff settings + options.maxRetries = + options?.retryRequestOptions?.retries ?? options.maxRetries; // create a default backoff settings object in case the user didn't provide overrides for everything const backoffSettings = createDefaultBackoffSettings(); let maxRetryDelayMillis; let totalTimeoutMillis; // maxRetryDelay - this is in seconds, need to convert to milliseconds - //TODO(coleleah): simplify with nullisch coalescing if possible and optional chaining - if (options.retryRequestOptions.maxRetryDelay) { + if (options.retryRequestOptions.maxRetryDelay !== undefined) { maxRetryDelayMillis = options.retryRequestOptions.maxRetryDelay * 1000; } // retryDelayMultiplier - should be a one to one mapping to retryDelayMultiplier const retryDelayMultiplier = - options?.retryRequestOptions?.retryDelayMultiplier ?? backoffSettings.retryDelayMultiplier; + options?.retryRequestOptions?.retryDelayMultiplier ?? + backoffSettings.retryDelayMultiplier; // this is in seconds and needs to be converted to milliseconds and the totalTimeoutMillis parameter - //TODO(coleleah) - if (options.retryRequestOptions.totalTimeout) { + if (options.retryRequestOptions.totalTimeout !== undefined) { totalTimeoutMillis = options.retryRequestOptions.totalTimeout * 1000; } - // totalTimeoutMillis = (options?.retryRequestOptions?.totalTimeout * 1000) ?? totalTimeoutMillis; - // for the variables the user wants to override, override in the backoff settings object we made - backoffSettings.maxRetryDelayMillis = maxRetryDelayMillis ?? backoffSettings.maxRetryDelayMillis; - backoffSettings.retryDelayMultiplier = retryDelayMultiplier ?? backoffSettings.retryDelayMultiplier; - backoffSettings.totalTimeoutMillis = totalTimeoutMillis ?? backoffSettings.totalTimeoutMillis; - + backoffSettings.maxRetryDelayMillis = + maxRetryDelayMillis ?? backoffSettings.maxRetryDelayMillis; + backoffSettings.retryDelayMultiplier = + retryDelayMultiplier ?? backoffSettings.retryDelayMultiplier; + backoffSettings.totalTimeoutMillis = + totalTimeoutMillis ?? backoffSettings.totalTimeoutMillis; const convertedRetryOptions = createRetryOptions( retryCodesOrShouldRetryFn, diff --git a/gax/test/unit/streaming.ts b/gax/test/unit/streaming.ts index 8270f980d..9e4ded03f 100644 --- a/gax/test/unit/streaming.ts +++ b/gax/test/unit/streaming.ts @@ -995,6 +995,102 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en ) ); }); + it('throws a warning and converts retryRequestOptions for new retry behavior - zero/falsiness check', done => { + const warnStub = sinon.stub(warnings, 'warn'); + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + try { + assert(settings.retry); + assert(typeof settings.retryRequestOptions === 'undefined'); + assert.strictEqual( + settings.retry?.backoffSettings.maxRetryDelayMillis, + 0 + ); + assert.strictEqual( + settings.retry?.backoffSettings.retryDelayMultiplier, + 0 + ); + // totalTimeout is undefined because maxRetries is passed + assert( + typeof settings.retry?.backoffSettings.totalTimeoutMillis === + 'undefined' + ); + + assert.strictEqual(settings.retry?.backoffSettings.maxRetries, 0); + assert( + typeof settings.retry.retryCodesOrShouldRetryFn === 'function' + ); + assert(settings.retry !== new gax.CallSettings().retry); + done(); + } catch (err) { + done(err); + } + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // gaxStreamingRetries + ); + const passedRetryRequestOptions = { + objectMode: false, + retries: 0, + maxRetryDelay: 0, + retryDelayMultiplier: 0, + totalTimeout: 0, + noResponseRetries: 0, + currentRetryAttempt: 0, + shouldRetryFn: function alwaysRetry() { + return true; + }, + }; + apiCall( + {}, + { + retryRequestOptions: passedRetryRequestOptions, + } + ); + + assert.strictEqual(warnStub.callCount, 4); + assert( + warnStub.calledWith( + 'retry_request_options', + 'retryRequestOptions will be deprecated in a future release. Please use retryOptions to pass retry options at call time', + 'DeprecationWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'objectMode override is not supported. It is set to true internally by default in gax.', + 'UnsupportedParameterWarning' + ) + ); + }); it('throws a warning and converts retryRequestOptions for new retry behavior - no maxRetries', done => { const warnStub = sinon.stub(warnings, 'warn'); sinon @@ -1060,6 +1156,101 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en } ); + assert.strictEqual(warnStub.callCount, 4); + assert( + warnStub.calledWith( + 'retry_request_options', + 'retryRequestOptions will be deprecated in a future release. Please use retryOptions to pass retry options at call time', + 'DeprecationWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'objectMode override is not supported. It is set to true internally by default in gax.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'noResponseRetries override is not supported. Please specify retry codes or a function to determine retry eligibility.', + 'UnsupportedParameterWarning' + ) + ); + assert( + warnStub.calledWith( + 'retry_request_options', + 'currentRetryAttempt override is not supported. Retry attempts are tracked internally.', + 'UnsupportedParameterWarning' + ) + ); + }); + it('throws a warning and converts retryRequestOptions for new retry behavior - no maxRetries zero/falsiness check', done => { + const warnStub = sinon.stub(warnings, 'warn'); + sinon + .stub(StreamingApiCaller.prototype, 'call') + .callsFake((apiCall, argument, settings, stream) => { + try { + assert(settings.retry); + assert(typeof settings.retryRequestOptions === 'undefined'); + assert.strictEqual( + settings.retry?.backoffSettings.maxRetryDelayMillis, + 0 + ); + assert.strictEqual( + settings.retry?.backoffSettings.retryDelayMultiplier, + 0 + ); + assert.strictEqual( + settings.retry?.backoffSettings.totalTimeoutMillis, + 0 + ); + assert( + typeof settings.retry?.backoffSettings.maxRetries === 'undefined' + ); + assert( + typeof settings.retry.retryCodesOrShouldRetryFn === 'function' + ); + assert(settings.retry !== new gax.CallSettings().retry); + done(); + } catch (err) { + done(err); + } + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // gaxStreamingRetries + ); + const passedRetryRequestOptions = { + objectMode: false, + maxRetryDelay: 0, + retryDelayMultiplier: 0, + totalTimeout: 0, + noResponseRetries: 0, + currentRetryAttempt: 0, + shouldRetryFn: function alwaysRetry() { + return true; + }, + }; + apiCall( + {}, + { + retryRequestOptions: passedRetryRequestOptions, + } + ); + assert.strictEqual(warnStub.callCount, 4); assert( warnStub.calledWith( From 1db7f49059883c2b2806bd43f4dc5d5c71e96895 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Thu, 28 Sep 2023 15:00:44 -0400 Subject: [PATCH 25/35] falsiness checks --- gax/src/gax.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 02d50da0d..5395df944 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -332,14 +332,14 @@ export function checkRetryOptions( // if a user provided retry AND retryRequestOptions at call time, throw an error // otherwise, convert supported parameters if (!gaxStreamingRetries) { - if (options.retry !== undefined) { + if (options.retry) { warn( 'legacy_streaming_retry_behavior', 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', 'DeprecationWarning' ); } - if (options.retryRequestOptions !== undefined) { + if (options.retryRequestOptions) { warn( 'legacy_streaming_retry_request_behavior', 'Legacy streaming retry behavior will not honor retryRequestOptions passed at call time. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will convert retryRequestOptions to retry parameters by default in future releases.', @@ -348,13 +348,10 @@ export function checkRetryOptions( } return options; } - if ( - options.retry !== undefined && - options.retryRequestOptions !== undefined - ) { + if (options.retry && options.retryRequestOptions) { throw new Error('Only one of retry or retryRequestOptions may be set'); } // handles parameter conversion from retryRequestOptions to retryOptions - if (options.retryRequestOptions !== undefined) { + if (options.retryRequestOptions) { if (options.retryRequestOptions.objectMode !== undefined) { warn( 'retry_request_options', @@ -376,9 +373,8 @@ export function checkRetryOptions( 'UnsupportedParameterWarning' ); } - const retryCodesOrShouldRetryFn = options?.retryRequestOptions?.shouldRetryFn ?? [ - Status.UNAVAILABLE, - ]; + const retryCodesOrShouldRetryFn = options?.retryRequestOptions + ?.shouldRetryFn ?? [Status.UNAVAILABLE]; //Backoff settings options.maxRetries = From da3d8c3c5c1eb183d7e65f8b435fa4e8588ec52d Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Fri, 29 Sep 2023 17:10:30 -0400 Subject: [PATCH 26/35] Request/response type lint --- gax/src/gax.ts | 7 ++++--- gax/src/streamingCalls/streaming.ts | 11 ++++++++--- gax/test/test-application/src/index.ts | 11 +++++------ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 5395df944..3cf39a713 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -24,6 +24,7 @@ import {GoogleError} from './googleError'; import {BundleOptions} from './bundlingCalls/bundleExecutor'; import {toLowerCamelCase} from './util'; import {Status} from './status'; +import {RequestType} from './apitypes'; /** * Encapsulates the overridable settings for a particular API call. @@ -78,11 +79,11 @@ import {Status} from './status'; export class RetryOptions { retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean); backoffSettings: BackoffSettings; - getResumptionRequestFn?: (response: any) => any; + getResumptionRequestFn?: (request: RequestType) => RequestType; constructor( retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean), backoffSettings: BackoffSettings, - getResumptionRequestFn?: (response: any) => any + getResumptionRequestFn?: (request: RequestType) => RequestType ) { this.retryCodesOrShouldRetryFn = retryCodesOrShouldRetryFn; this.backoffSettings = backoffSettings; @@ -433,7 +434,7 @@ export function checkRetryOptions( export function createRetryOptions( retryCodesOrShouldRetryFn: number[] | ((response: any) => boolean), backoffSettings: BackoffSettings, - getResumptionRequestFn?: (response: any) => any + getResumptionRequestFn?: (request: RequestType) => RequestType ): RetryOptions { return { retryCodesOrShouldRetryFn, diff --git a/gax/src/streamingCalls/streaming.ts b/gax/src/streamingCalls/streaming.ts index 7ec6b6495..292ceae01 100644 --- a/gax/src/streamingCalls/streaming.ts +++ b/gax/src/streamingCalls/streaming.ts @@ -22,6 +22,7 @@ import { APICallback, CancellableStream, GRPCCallResult, + RequestType, SimpleCallbackFunction, } from '../apitypes'; import {RetryOptions, RetryRequestOptions} from '../gax'; @@ -128,13 +129,17 @@ export class StreamProxy extends duplexify implements GRPCCallResult { } retry(stream: CancellableStream, retry: RetryOptions) { - let retryArgument = this.argument!; + let retryArgument = this.argument! as unknown as RequestType; + + const newRetryArgument = retry.getResumptionRequestFn!( + this.argument as unknown as RequestType + ); if ( typeof retry.getResumptionRequestFn! === 'function' && - retry.getResumptionRequestFn!(this.argument) + newRetryArgument ) { - retryArgument = retry.getResumptionRequestFn!(this.argument); + retryArgument = newRetryArgument; } this.resetStreams(stream); diff --git a/gax/test/test-application/src/index.ts b/gax/test/test-application/src/index.ts index a233f4249..e3b766163 100644 --- a/gax/test/test-application/src/index.ts +++ b/gax/test/test-application/src/index.ts @@ -30,6 +30,7 @@ import { createBackoffSettings, RetryOptions, } from 'google-gax'; +import {RequestType} from 'google-gax/build/src/apitypes'; async function testShowcase() { const grpcClientOpts = { @@ -719,14 +720,12 @@ async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrate 3000, 600000 ); - const getResumptionRequestFn = ( - originalRequest: protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest - ) => { + const getResumptionRequestFn = (request: RequestType) => { const newRequest = - new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); - newRequest.name = originalRequest.name; + new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest() as unknown as RequestType; + newRequest.name = request.name; newRequest.lastFailIndex = 5; - return newRequest; + return newRequest as unknown as RequestType; }; const retryOptions = new RetryOptions( From c59005b594c0c8670a1317a034c14e6883998cad Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Fri, 29 Sep 2023 17:10:30 -0400 Subject: [PATCH 27/35] Request/response type lint fix failing test --- gax/src/streamingCalls/streaming.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/gax/src/streamingCalls/streaming.ts b/gax/src/streamingCalls/streaming.ts index 292ceae01..f139a9d87 100644 --- a/gax/src/streamingCalls/streaming.ts +++ b/gax/src/streamingCalls/streaming.ts @@ -131,15 +131,11 @@ export class StreamProxy extends duplexify implements GRPCCallResult { retry(stream: CancellableStream, retry: RetryOptions) { let retryArgument = this.argument! as unknown as RequestType; - const newRetryArgument = retry.getResumptionRequestFn!( - this.argument as unknown as RequestType - ); - if ( typeof retry.getResumptionRequestFn! === 'function' && - newRetryArgument + retry.getResumptionRequestFn(retryArgument) ) { - retryArgument = newRetryArgument; + retryArgument = retry.getResumptionRequestFn(retryArgument); } this.resetStreams(stream); From e09f94ffd2dcc2ab40ed294cf2b8255da2a64210 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Fri, 29 Sep 2023 18:11:48 -0400 Subject: [PATCH 28/35] make createAPIcall nested statements more readable --- gax/src/createApiCall.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/gax/src/createApiCall.ts b/gax/src/createApiCall.ts index 3b7286a8c..d77158f50 100644 --- a/gax/src/createApiCall.ts +++ b/gax/src/createApiCall.ts @@ -109,6 +109,14 @@ export function createApiCall( ); } if (!streaming && retry && retry?.retryCodesOrShouldRetryFn) { + if ( + retry.retryCodesOrShouldRetryFn instanceof Function && + !streaming + ) { + throw new Error( + 'Using a function to determine retry eligibility is only supported with server streaming calls' + ); + } if ( Array.isArray(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.length > 0 @@ -121,15 +129,6 @@ export function createApiCall( thisSettings.otherArgs as GRPCCallOtherArgs, thisSettings.apiName ); - } else { - if ( - retry.retryCodesOrShouldRetryFn instanceof Function && - !streaming - ) { - throw new Error( - 'Using a function to determine retry eligibility is only supported with server streaming calls' - ); - } } } return addTimeoutArg( From ca8d1f76a2ae914c744b132131a53db12c5d5170 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Mon, 2 Oct 2023 18:07:57 -0400 Subject: [PATCH 29/35] retryCodesOrShouldRetryFn --- gax/src/createApiCall.ts | 14 +++++---- gax/src/gax.ts | 33 ++++++++++++++++++--- gax/src/streamingCalls/streaming.ts | 4 +-- gax/test/unit/gax.ts | 46 +++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 12 deletions(-) diff --git a/gax/src/createApiCall.ts b/gax/src/createApiCall.ts index d77158f50..c1707e0e9 100644 --- a/gax/src/createApiCall.ts +++ b/gax/src/createApiCall.ts @@ -28,7 +28,12 @@ import { SimpleCallbackFunction, } from './apitypes'; import {Descriptor} from './descriptor'; -import {CallOptions, CallSettings, checkRetryOptions} from './gax'; +import { + CallOptions, + CallSettings, + checkRetryOptions, + isRetryCodes, +} from './gax'; import {retryable} from './normalCalls/retries'; import {addTimeoutArg} from './normalCalls/timeout'; import {StreamingApiCaller} from './streamingCalls/streamingApiCaller'; @@ -109,16 +114,13 @@ export function createApiCall( ); } if (!streaming && retry && retry?.retryCodesOrShouldRetryFn) { - if ( - retry.retryCodesOrShouldRetryFn instanceof Function && - !streaming - ) { + if (!isRetryCodes(retry.retryCodesOrShouldRetryFn) && !streaming) { throw new Error( 'Using a function to determine retry eligibility is only supported with server streaming calls' ); } if ( - Array.isArray(retry.retryCodesOrShouldRetryFn) && + isRetryCodes(retry.retryCodesOrShouldRetryFn) && retry.retryCodesOrShouldRetryFn.length > 0 ) { retry.backoffSettings.initialRpcTimeoutMillis ??= diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 3cf39a713..579beae66 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -72,7 +72,7 @@ import {RequestType} from './apitypes'; * Per-call configurable settings for retrying upon transient failure. * @implements {RetryOptionsType} * @typedef {Object} RetryOptions - * @property {String[] | (function)} retryCodesOrShouldRetryFn + * @property {number[] | (function)} retryCodesOrShouldRetryFn * @property {BackoffSettings} backoffSettings * @property {(function)} getResumptionRequestFn */ @@ -91,6 +91,30 @@ export class RetryOptions { } } +/** + * Helper function to reduce the type checking for this variable to one spot + * @param retryCodesOrShouldRetryFn + * @returns + */ +export function isRetryCodes( + retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean) +) { + let retryCodes: number[] | undefined; + let shouldRetryFunction: ((error: GoogleError) => boolean) | undefined; + if (Array.isArray(retryCodesOrShouldRetryFn)) { + retryCodes = retryCodesOrShouldRetryFn; + } else if (retryCodesOrShouldRetryFn instanceof Function) { + shouldRetryFunction = retryCodesOrShouldRetryFn; + } + if (retryCodes) { + return true; + } else if (shouldRetryFunction) { + return false; + } else { + throw new Error('retryCodesOrShouldRetryFn must be an array or a function'); + } +} + /** * Per-call configurable settings for working with retry-request * See the repo README for more about the parameters @@ -248,9 +272,10 @@ export class CallSettings { // method are non-null, then that timeout value will be used to // override backoff settings. if ( - (Array.isArray(retry?.retryCodesOrShouldRetryFn) && + retry?.retryCodesOrShouldRetryFn && + ((isRetryCodes(retry!.retryCodesOrShouldRetryFn) && retry!.retryCodesOrShouldRetryFn.length > 0) || - retry?.retryCodesOrShouldRetryFn instanceof Function + !isRetryCodes(retry!.retryCodesOrShouldRetryFn)) ) { retry!.backoffSettings.initialRpcTimeoutMillis = timeout; retry!.backoffSettings.maxRpcTimeoutMillis = timeout; @@ -432,7 +457,7 @@ export function checkRetryOptions( * */ export function createRetryOptions( - retryCodesOrShouldRetryFn: number[] | ((response: any) => boolean), + retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean), backoffSettings: BackoffSettings, getResumptionRequestFn?: (request: RequestType) => RequestType ): RetryOptions { diff --git a/gax/src/streamingCalls/streaming.ts b/gax/src/streamingCalls/streaming.ts index f139a9d87..6f5bc68ce 100644 --- a/gax/src/streamingCalls/streaming.ts +++ b/gax/src/streamingCalls/streaming.ts @@ -234,8 +234,8 @@ export class StreamProxy extends duplexify implements GRPCCallResult { this.retries!++; const e = GoogleError.parseGRPCStatusDetails(error); let shouldRetry = this.defaultShouldRetry(e!, retry); - if (typeof retry.retryCodesOrShouldRetryFn! === 'function') { - shouldRetry = retry.retryCodesOrShouldRetryFn!(e!); + if (typeof retry.retryCodesOrShouldRetryFn === 'function') { + shouldRetry = retry.retryCodesOrShouldRetryFn(e!); } if (shouldRetry) { diff --git a/gax/test/unit/gax.ts b/gax/test/unit/gax.ts index 7f20f49c7..a8fb7305f 100644 --- a/gax/test/unit/gax.ts +++ b/gax/test/unit/gax.ts @@ -25,6 +25,7 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; import * as gax from '../../src/gax'; +import {GoogleError} from '../../src'; const SERVICE_NAME = 'test.interface.v1.api'; @@ -102,6 +103,51 @@ function expectBackoffSettings(obj: gax.BackoffSettings) { } describe('gax construct settings', () => { + it('checks helper function for retry codes', () => { + const defaults = gax.constructSettings( + SERVICE_NAME, + A_CONFIG, + {}, + RETRY_DICT + ); + assert( + gax.isRetryCodes(defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn) + ); + }); + it('checks helper function for should retry function', () => { + const defaults = gax.constructSettings( + SERVICE_NAME, + A_CONFIG, + {}, + RETRY_DICT + ); + function neverRetry(): boolean { + return false; + } + defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn = neverRetry; + assert( + !gax.isRetryCodes(defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn) + ); + }); + it('helper function errors on bad input', () => { + const defaults = gax.constructSettings( + SERVICE_NAME, + A_CONFIG, + {}, + RETRY_DICT + ); + defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn = 5; + try { + gax.isRetryCodes(defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn); + } catch (err) { + assert(err instanceof Error); + assert( + err.message === + 'retryCodesOrShouldRetryFn must be an array or a function' + ); + } + }); + it('creates settings', () => { const otherArgs = {key: 'value'}; const defaults = gax.constructSettings( From deb5328f31bbbb9ab945c937344e6446b3c4648f Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Mon, 2 Oct 2023 18:16:22 -0400 Subject: [PATCH 30/35] make retryCodesOrShouldRetryFn less awful --- gax/src/gax.ts | 10 +++++----- gax/test/test-application/src/index.ts | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 579beae66..549298928 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -77,11 +77,11 @@ import {RequestType} from './apitypes'; * @property {(function)} getResumptionRequestFn */ export class RetryOptions { - retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean); + retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean); backoffSettings: BackoffSettings; getResumptionRequestFn?: (request: RequestType) => RequestType; constructor( - retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean), + retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean), backoffSettings: BackoffSettings, getResumptionRequestFn?: (request: RequestType) => RequestType ) { @@ -97,7 +97,7 @@ export class RetryOptions { * @returns */ export function isRetryCodes( - retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean) + retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean) ) { let retryCodes: number[] | undefined; let shouldRetryFunction: ((error: GoogleError) => boolean) | undefined; @@ -137,7 +137,7 @@ export interface RetryRequestOptions { retries?: number; noResponseRetries?: number; currentRetryAttempt?: number; - shouldRetryFn?: (error?: GoogleError) => boolean; + shouldRetryFn?: (error: GoogleError) => boolean; maxRetryDelay?: number; retryDelayMultiplier?: number; totalTimeout?: number; @@ -457,7 +457,7 @@ export function checkRetryOptions( * */ export function createRetryOptions( - retryCodesOrShouldRetryFn: number[] | ((error?: GoogleError) => boolean), + retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean), backoffSettings: BackoffSettings, getResumptionRequestFn?: (request: RequestType) => RequestType ): RetryOptions { diff --git a/gax/test/test-application/src/index.ts b/gax/test/test-application/src/index.ts index e3b766163..7df1086ae 100644 --- a/gax/test/test-application/src/index.ts +++ b/gax/test/test-application/src/index.ts @@ -587,7 +587,7 @@ async function testServerStreamingRetriesWithShouldRetryFn( client: SequenceServiceClient ) { const finalData: string[] = []; - const shouldRetryFn = function checkRetry(error?: GoogleError) { + const shouldRetryFn = function checkRetry(error: GoogleError) { return [14, 4].includes(error!.code!); }; @@ -657,7 +657,7 @@ async function testServerStreamingRetrieswithRetryRequestOptions( totalTimeout: 650, noResponseRetries: 3, currentRetryAttempt: 0, - shouldRetryFn: function checkRetry(error?: GoogleError) { + shouldRetryFn: function checkRetry(error: GoogleError) { return [14, 4].includes(error!.code!); }, }; @@ -708,7 +708,7 @@ async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrate client: SequenceServiceClient ) { const finalData: string[] = []; - const shouldRetryFn = (error?: GoogleError) => { + const shouldRetryFn = (error: GoogleError) => { return [4, 14].includes(error!.code!); }; const backoffSettings = createBackoffSettings( @@ -779,7 +779,7 @@ async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrate async function testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResumptionStrategy( client: SequenceServiceClient ) { - const shouldRetryFn = (error?: GoogleError) => { + const shouldRetryFn = (error: GoogleError) => { return [4, 14].includes(error!.code!); }; const backoffSettings = createBackoffSettings( From 823e1d75c93f78f27fb7133387f605cb3360f5ac Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Wed, 4 Oct 2023 10:28:50 -0400 Subject: [PATCH 31/35] fix Sofia's comments --- gax/src/gax.ts | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 549298928..391d2a56b 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -99,16 +99,9 @@ export class RetryOptions { export function isRetryCodes( retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean) ) { - let retryCodes: number[] | undefined; - let shouldRetryFunction: ((error: GoogleError) => boolean) | undefined; if (Array.isArray(retryCodesOrShouldRetryFn)) { - retryCodes = retryCodesOrShouldRetryFn; - } else if (retryCodesOrShouldRetryFn instanceof Function) { - shouldRetryFunction = retryCodesOrShouldRetryFn; - } - if (retryCodes) { return true; - } else if (shouldRetryFunction) { + } else if (retryCodesOrShouldRetryFn instanceof Function) { return false; } else { throw new Error('retryCodesOrShouldRetryFn must be an array or a function'); @@ -271,12 +264,7 @@ export class CallSettings { // If a method-specific timeout is set in the service config, and the retry codes for that // method are non-null, then that timeout value will be used to // override backoff settings. - if ( - retry?.retryCodesOrShouldRetryFn && - ((isRetryCodes(retry!.retryCodesOrShouldRetryFn) && - retry!.retryCodesOrShouldRetryFn.length > 0) || - !isRetryCodes(retry!.retryCodesOrShouldRetryFn)) - ) { + if (retry?.retryCodesOrShouldRetryFn) { retry!.backoffSettings.initialRpcTimeoutMillis = timeout; retry!.backoffSettings.maxRpcTimeoutMillis = timeout; retry!.backoffSettings.totalTimeoutMillis = timeout; From 2d1756358dc892c67dca955e525ae43935e6476e Mon Sep 17 00:00:00 2001 From: "Leah E. Cole" <6719667+leahecole@users.noreply.github.com> Date: Fri, 10 Nov 2023 18:45:10 -0500 Subject: [PATCH 32/35] Retrycodes refactor (#13) * WIP - accidentally tests transient error * WIP - updating parameter, adding tests * update retrycodes * update unit tests * remove redundant parameter check * remove unused check * uncomment necessary return * add second transient check * WIP: more unit tests * remove unneeded function from streaming test * remove debug statements * npm run fix * address feedback, remove unused parts of streamingRetryRequest * remove unused streamingRetryRequestCode --- gax/src/createApiCall.ts | 37 +- gax/src/gax.ts | 73 +- gax/src/normalCalls/retries.ts | 5 +- gax/src/normalCalls/timeout.ts | 20 - gax/src/streamingCalls/streamDescriptor.ts | 3 +- gax/src/streamingCalls/streaming.ts | 155 ++- gax/src/streamingRetryRequest.ts | 199 +--- gax/test/test-application/src/index.ts | 8 +- gax/test/unit/apiCallable.ts | 11 +- gax/test/unit/gax.ts | 65 +- gax/test/unit/streaming.ts | 1055 +++++++++++++++++--- gax/test/unit/streamingRetryRequest.ts | 57 +- 12 files changed, 1111 insertions(+), 577 deletions(-) diff --git a/gax/src/createApiCall.ts b/gax/src/createApiCall.ts index c1707e0e9..55bd1fb1b 100644 --- a/gax/src/createApiCall.ts +++ b/gax/src/createApiCall.ts @@ -28,12 +28,7 @@ import { SimpleCallbackFunction, } from './apitypes'; import {Descriptor} from './descriptor'; -import { - CallOptions, - CallSettings, - checkRetryOptions, - isRetryCodes, -} from './gax'; +import {CallOptions, CallSettings, convertRetryOptions} from './gax'; import {retryable} from './normalCalls/retries'; import {addTimeoutArg} from './normalCalls/timeout'; import {StreamingApiCaller} from './streamingCalls/streamingApiCaller'; @@ -81,13 +76,13 @@ export function createApiCall( let thisSettings: CallSettings; if (currentApiCaller instanceof StreamingApiCaller) { const gaxStreamingRetries = - currentApiCaller.descriptor?.gaxStreamingRetries; + currentApiCaller.descriptor?.gaxStreamingRetries ?? false; // If Gax streaming retries are enabled, check settings passed at call time and convert parameters if needed - const thisSettingsTemp = checkRetryOptions( + const convertedRetryOptions = convertRetryOptions( callOptions, gaxStreamingRetries ); - thisSettings = settings.merge(thisSettingsTemp); + thisSettings = settings.merge(convertedRetryOptions); } else { thisSettings = settings.merge(callOptions); } @@ -108,21 +103,29 @@ export function createApiCall( ?.streaming; const retry = thisSettings.retry; - if (!streaming && retry && retry?.getResumptionRequestFn) { + + if ( + streaming && + retry && + retry.retryCodes.length > 0 && + retry.shouldRetryFn + ) { throw new Error( - 'Resumption strategy can only be used with server streaming retries' + 'Only one of retryCodes or shouldRetryFn may be defined' ); } - if (!streaming && retry && retry?.retryCodesOrShouldRetryFn) { - if (!isRetryCodes(retry.retryCodesOrShouldRetryFn) && !streaming) { + if (!streaming && retry) { + if (retry.shouldRetryFn) { throw new Error( 'Using a function to determine retry eligibility is only supported with server streaming calls' ); } - if ( - isRetryCodes(retry.retryCodesOrShouldRetryFn) && - retry.retryCodesOrShouldRetryFn.length > 0 - ) { + if (retry.getResumptionRequestFn) { + throw new Error( + 'Resumption strategy can only be used with server streaming retries' + ); + } + if (retry.retryCodes && retry.retryCodes.length > 0) { retry.backoffSettings.initialRpcTimeoutMillis ??= thisSettings.timeout; return retryable( diff --git a/gax/src/gax.ts b/gax/src/gax.ts index 391d2a56b..da17c1c2e 100644 --- a/gax/src/gax.ts +++ b/gax/src/gax.ts @@ -72,42 +72,29 @@ import {RequestType} from './apitypes'; * Per-call configurable settings for retrying upon transient failure. * @implements {RetryOptionsType} * @typedef {Object} RetryOptions - * @property {number[] | (function)} retryCodesOrShouldRetryFn + * @property {number[]} retryCodes * @property {BackoffSettings} backoffSettings + * @property {(function)} shouldRetryFn * @property {(function)} getResumptionRequestFn */ export class RetryOptions { - retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean); + retryCodes: number[]; backoffSettings: BackoffSettings; + shouldRetryFn?: (error: GoogleError) => boolean; getResumptionRequestFn?: (request: RequestType) => RequestType; constructor( - retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean), + retryCodes: number[], backoffSettings: BackoffSettings, + shouldRetryFn?: (error: GoogleError) => boolean, getResumptionRequestFn?: (request: RequestType) => RequestType ) { - this.retryCodesOrShouldRetryFn = retryCodesOrShouldRetryFn; + this.retryCodes = retryCodes; this.backoffSettings = backoffSettings; + this.shouldRetryFn = shouldRetryFn; this.getResumptionRequestFn = getResumptionRequestFn; } } -/** - * Helper function to reduce the type checking for this variable to one spot - * @param retryCodesOrShouldRetryFn - * @returns - */ -export function isRetryCodes( - retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean) -) { - if (Array.isArray(retryCodesOrShouldRetryFn)) { - return true; - } else if (retryCodesOrShouldRetryFn instanceof Function) { - return false; - } else { - throw new Error('retryCodesOrShouldRetryFn must be an array or a function'); - } -} - /** * Per-call configurable settings for working with retry-request * See the repo README for more about the parameters @@ -264,7 +251,7 @@ export class CallSettings { // If a method-specific timeout is set in the service config, and the retry codes for that // method are non-null, then that timeout value will be used to // override backoff settings. - if (retry?.retryCodesOrShouldRetryFn) { + if (retry?.retryCodes) { retry!.backoffSettings.initialRpcTimeoutMillis = timeout; retry!.backoffSettings.maxRpcTimeoutMillis = timeout; retry!.backoffSettings.totalTimeoutMillis = timeout; @@ -335,7 +322,7 @@ export class CallSettings { * @return {CallOptions} A new CallOptions object. * */ -export function checkRetryOptions( +export function convertRetryOptions( options?: CallOptions, gaxStreamingRetries?: boolean ): CallOptions | undefined { @@ -387,8 +374,13 @@ export function checkRetryOptions( 'UnsupportedParameterWarning' ); } - const retryCodesOrShouldRetryFn = options?.retryRequestOptions - ?.shouldRetryFn ?? [Status.UNAVAILABLE]; + + let retryCodes = [Status.UNAVAILABLE]; + let shouldRetryFn; + if (options.retryRequestOptions.shouldRetryFn) { + retryCodes = []; + shouldRetryFn = options.retryRequestOptions.shouldRetryFn; + } //Backoff settings options.maxRetries = @@ -419,8 +411,9 @@ export function checkRetryOptions( totalTimeoutMillis ?? backoffSettings.totalTimeoutMillis; const convertedRetryOptions = createRetryOptions( - retryCodesOrShouldRetryFn, - backoffSettings + retryCodes, + backoffSettings, + shouldRetryFn ); options.retry = convertedRetryOptions; delete options.retryRequestOptions; // completely remove them to avoid any further confusion @@ -435,23 +428,25 @@ export function checkRetryOptions( /** * Per-call configurable settings for retrying upon transient failure. - * - * @param {number[] | function} retryCodesOrShouldRetryFn - a list of Google API canonical error codes OR a function that returns a boolean to determine retry behavior + * @param {number[]} retryCodes - a list of Google API canonical error codes OR a function that returns a boolean to determine retry behavior * upon which a retry should be attempted. * @param {BackoffSettings} backoffSettings - configures the retry * exponential backoff algorithm. + * @param {function} shouldRetryFn - a function that determines whether a call should retry. If this is defined retryCodes must be empty * @param {function} getResumptionRequestFn - a function with a resumption strategy - only used with server streaming retries * @return {RetryOptions} A new RetryOptions object. * */ export function createRetryOptions( - retryCodesOrShouldRetryFn: number[] | ((error: GoogleError) => boolean), + retryCodes: number[], backoffSettings: BackoffSettings, + shouldRetryFn?: (error: GoogleError) => boolean, getResumptionRequestFn?: (request: RequestType) => RequestType ): RetryOptions { return { - retryCodesOrShouldRetryFn, + retryCodes, backoffSettings, + shouldRetryFn, getResumptionRequestFn, }; } @@ -671,27 +666,31 @@ function mergeRetryOptions( } if ( - !overrides.retryCodesOrShouldRetryFn && + !overrides.retryCodes && !overrides.backoffSettings && + !overrides.shouldRetryFn && !overrides.getResumptionRequestFn ) { return retry; } - const codesOrFunction = overrides.retryCodesOrShouldRetryFn - ? overrides.retryCodesOrShouldRetryFn - : retry.retryCodesOrShouldRetryFn; + const retryCodes = overrides.retryCodes + ? overrides.retryCodes + : retry.retryCodes; const backoffSettings = overrides.backoffSettings ? overrides.backoffSettings : retry.backoffSettings; - + const shouldRetryFn = overrides.shouldRetryFn + ? overrides.shouldRetryFn + : retry.shouldRetryFn; const getResumptionRequestFn = overrides.getResumptionRequestFn ? overrides.getResumptionRequestFn : retry.getResumptionRequestFn; return createRetryOptions( - codesOrFunction!, + retryCodes!, backoffSettings!, + shouldRetryFn!, getResumptionRequestFn! ); } diff --git a/gax/src/normalCalls/retries.ts b/gax/src/normalCalls/retries.ts index a7204402f..2377b1360 100644 --- a/gax/src/normalCalls/retries.ts +++ b/gax/src/normalCalls/retries.ts @@ -108,9 +108,8 @@ export function retryable( } canceller = null; if ( - retry.retryCodesOrShouldRetryFn !== undefined && - Array.isArray(retry.retryCodesOrShouldRetryFn) && - retry.retryCodesOrShouldRetryFn.indexOf(err!.code!) < 0 + retry.retryCodes.length > 0 && + retry.retryCodes.indexOf(err!.code!) < 0 ) { err.note = 'Exception occurred in retry method that was ' + diff --git a/gax/src/normalCalls/timeout.ts b/gax/src/normalCalls/timeout.ts index f703e3b14..b1790d854 100644 --- a/gax/src/normalCalls/timeout.ts +++ b/gax/src/normalCalls/timeout.ts @@ -17,7 +17,6 @@ import { GRPCCall, GRPCCallOtherArgs, - ServerStreamingCall, SimpleCallbackFunction, UnaryCall, } from '../apitypes'; @@ -55,22 +54,3 @@ export function addTimeoutArg( return (func as UnaryCall)(argument, metadata!, options, callback); }; } - -export function addServerTimeoutArg( - func: GRPCCall, - timeout: number, - otherArgs: GRPCCallOtherArgs, - abTests?: {} -): SimpleCallbackFunction { - // TODO: this assumes the other arguments consist of metadata and options, - // which is specific to gRPC calls. Remove the hidden dependency on gRPC. - return argument => { - const now = new Date(); - const options = otherArgs.options || {}; - options.deadline = new Date(now.getTime() + timeout); - const metadata = otherArgs.metadataBuilder - ? otherArgs.metadataBuilder(abTests, otherArgs.headers || {}) - : null; - return (func as ServerStreamingCall)(argument, metadata!, options); - }; -} diff --git a/gax/src/streamingCalls/streamDescriptor.ts b/gax/src/streamingCalls/streamDescriptor.ts index 569374d31..fc6090d09 100644 --- a/gax/src/streamingCalls/streamDescriptor.ts +++ b/gax/src/streamingCalls/streamDescriptor.ts @@ -16,7 +16,6 @@ import {APICaller} from '../apiCaller'; import {Descriptor} from '../descriptor'; -import {CallSettings} from '../gax'; import {StreamType} from './streaming'; import {StreamingApiCaller} from './streamingApiCaller'; @@ -40,7 +39,7 @@ export class StreamDescriptor implements Descriptor { this.gaxStreamingRetries = gaxStreamingRetries; } - getApiCaller(settings: CallSettings): APICaller { + getApiCaller(): APICaller { // Right now retrying does not work with gRPC-streaming, because retryable // assumes an API call returns an event emitter while gRPC-streaming methods // return Stream. diff --git a/gax/src/streamingCalls/streaming.ts b/gax/src/streamingCalls/streaming.ts index 6f5bc68ce..db2341b54 100644 --- a/gax/src/streamingCalls/streaming.ts +++ b/gax/src/streamingCalls/streaming.ts @@ -88,7 +88,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { stream?: CancellableStream; private _responseHasSent: boolean; rest?: boolean; - new_retry?: boolean; + gaxServerStreamingRetries?: boolean; apiCall?: SimpleCallbackFunction; argument?: {}; prevDeadline?: number; @@ -105,7 +105,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { type: StreamType, callback: APICallback, rest?: boolean, - new_retry?: boolean + gaxServerStreamingRetries?: boolean ) { super(undefined, undefined, { objectMode: true, @@ -117,7 +117,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { this._isCancelCalled = false; this._responseHasSent = false; this.rest = rest; - this.new_retry = new_retry; + this.gaxServerStreamingRetries = gaxServerStreamingRetries; } cancel() { @@ -130,27 +130,23 @@ export class StreamProxy extends duplexify implements GRPCCallResult { retry(stream: CancellableStream, retry: RetryOptions) { let retryArgument = this.argument! as unknown as RequestType; - - if ( - typeof retry.getResumptionRequestFn! === 'function' && - retry.getResumptionRequestFn(retryArgument) - ) { - retryArgument = retry.getResumptionRequestFn(retryArgument); + if (typeof retry.getResumptionRequestFn! === 'function') { + const resumptionRetryArgument = + retry.getResumptionRequestFn(retryArgument); + if (resumptionRetryArgument !== undefined) { + retryArgument = retry.getResumptionRequestFn(retryArgument); + } } - this.resetStreams(stream); - const new_stream = this.apiCall!( + const newStream = this.apiCall!( retryArgument, this._callback ) as CancellableStream; - this.stream = new_stream; + this.stream = newStream; - const retryStream = this.streamHandoffHelper(new_stream, retry); - if (retryStream !== undefined) { - return retryStream; - } - return new_stream; + this.streamHandoffHelper(newStream, retry); + return newStream; } /** @@ -159,7 +155,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { * @param {number} maxRetries - maximum total number of retries * @param {number} totalTimeoutMillis - total timeout in milliseconds */ - timeoutAndMaxRetryCheck( + throwIfMaxRetriesOrTotalTimeoutExceeded( deadline: number, maxRetries: number, totalTimeoutMillis: number @@ -176,22 +172,20 @@ export class StreamProxy extends duplexify implements GRPCCallResult { ); error.code = Status.DEADLINE_EXCEEDED; this.emit('error', error); - - this.destroy(error); + this.destroy(); // Without throwing error you get unhandled error since we are returning a new stream // There might be a better way to do this throw error; } - if (this.retries && this.retries > maxRetries) { + if (this.retries && this.retries >= maxRetries) { const error = new GoogleError( 'Exceeded maximum number of retries before any ' + 'response was received' ); error.code = Status.DEADLINE_EXCEEDED; this.emit('error', error); - - this.destroy(error); + this.destroy(); throw error; } } @@ -224,18 +218,21 @@ export class StreamProxy extends duplexify implements GRPCCallResult { deadline = now.getTime() + retry.backoffSettings.totalTimeoutMillis; } const maxRetries = retry.backoffSettings.maxRetries!; - - this.timeoutAndMaxRetryCheck( - deadline, - maxRetries, - retry.backoffSettings.totalTimeoutMillis! - ); + try { + this.throwIfMaxRetriesOrTotalTimeoutExceeded( + deadline, + maxRetries, + retry.backoffSettings.totalTimeoutMillis! + ); + } catch (error) { + return; + } this.retries!++; const e = GoogleError.parseGRPCStatusDetails(error); let shouldRetry = this.defaultShouldRetry(e!, retry); - if (typeof retry.retryCodesOrShouldRetryFn === 'function') { - shouldRetry = retry.retryCodesOrShouldRetryFn(e!); + if (retry.shouldRetryFn) { + shouldRetry = retry.shouldRetryFn(e!); } if (shouldRetry) { @@ -253,25 +250,16 @@ export class StreamProxy extends duplexify implements GRPCCallResult { e.note = 'Exception occurred in retry method that was ' + 'not classified as transient'; + // for some reason this error must be emitted here + // instead of the destroy, otherwise the error event + // is swallowed this.emit('error', e); - this.destroy(e); - return; - } - - if (maxRetries && deadline!) { - const newError = new GoogleError( - 'Cannot set both totalTimeoutMillis and maxRetries ' + - 'in backoffSettings.' - ); - newError.code = Status.INVALID_ARGUMENT; - this.emit('error', newError); - - this.destroy(newError); - } else { - retryStream = this.retry(stream, retry); - this.stream = retryStream; + this.destroy(); return; } + retryStream = this.retry(stream, retry); + this.stream = retryStream; + return; } /** @@ -285,6 +273,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { streamHandoffHelper(stream: CancellableStream, retry: RetryOptions): void { let enteredError = false; const eventsToForward = ['metadata', 'response', 'status', 'data']; + eventsToForward.forEach(event => { stream.on(event, this.emit.bind(this, event)); }); @@ -354,8 +343,8 @@ export class StreamProxy extends duplexify implements GRPCCallResult { defaultShouldRetry(error: GoogleError, retry: RetryOptions) { if ( - Array.isArray(retry.retryCodesOrShouldRetryFn) && - retry.retryCodesOrShouldRetryFn.indexOf(error!.code!) < 0 + retry.retryCodes.length > 0 && + retry.retryCodes.indexOf(error!.code!) < 0 ) { return false; } @@ -369,7 +358,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { * function eshould retry, and the parameters to the exponential backoff retry * algorithm. */ - forwardEventsNewImplementation( + forwardEventsWithRetries( stream: CancellableStream, retry: RetryOptions ): CancellableStream | undefined { @@ -415,8 +404,8 @@ export class StreamProxy extends duplexify implements GRPCCallResult { if ((maxRetries && maxRetries > 0) || (timeout && timeout > 0)) { const e = GoogleError.parseGRPCStatusDetails(error); let shouldRetry = this.defaultShouldRetry(e!, retry); - if (typeof retry.retryCodesOrShouldRetryFn! === 'function') { - shouldRetry = retry.retryCodesOrShouldRetryFn!(e!); + if (retry.shouldRetryFn) { + shouldRetry = retry.shouldRetryFn(e!); } if (shouldRetry) { @@ -427,8 +416,8 @@ export class StreamProxy extends duplexify implements GRPCCallResult { ); newError.code = Status.INVALID_ARGUMENT; this.emit('error', newError); - this.destroy(newError); - return; + this.destroy(); + return; //end chunk } else { retryStream = this.retry(stream, retry); this.stream = retryStream; @@ -439,9 +428,8 @@ export class StreamProxy extends duplexify implements GRPCCallResult { e.note = 'Exception occurred in retry method that was ' + 'not classified as transient'; - this.emit('error', error); - this.destroy(error); - return; + this.destroy(e); + return; // end chunk } } else { return GoogleError.parseGRPCStatusDetails(error); @@ -450,13 +438,18 @@ export class StreamProxy extends duplexify implements GRPCCallResult { return retryStream; } + /** + * Resets the target stream as part of the retry process + * @param {CancellableStream} requestStream - the stream to end + */ resetStreams(requestStream: CancellableStream) { if (requestStream) { requestStream.cancel && requestStream.cancel(); - if (requestStream.destroy) { requestStream.destroy(); } else if (requestStream.end) { + // TODO: not used in server streaming, but likely needed + // if we want to add BIDI or client side streaming requestStream.end(); } } @@ -467,7 +460,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { * @param {ApiCall} apiCall - the API function to be called. * @param {Object} argument - the argument to be passed to the apiCall. * @param {RetryOptions} retry - Configures the exceptions upon which the - * function eshould retry, and the parameters to the exponential backoff retry + * function should retry, and the parameters to the exponential backoff retry * algorithm. */ setStream( @@ -484,34 +477,29 @@ export class StreamProxy extends duplexify implements GRPCCallResult { const stream = apiCall(argument, this._callback) as CancellableStream; this.stream = stream; this.setReadable(stream); - } else if (this.new_retry) { + } else if (this.gaxServerStreamingRetries) { warn( 'gax_server_streaming_retries', 'You are using the new experimental gax native server streaming retry implementation', 'ExperimentalWarning' ); - const retryStream = streamingRetryRequest( - null, - { - objectMode: true, - request: () => { - if (this._isCancelCalled) { - if (this.stream) { - this.stream.cancel(); - } - return; + const retryStream = streamingRetryRequest({ + request: () => { + if (this._isCancelCalled) { + if (this.stream) { + this.stream.cancel(); } - const stream = apiCall( - argument, - this._callback - ) as CancellableStream; - this.stream = stream; - this.stream = this.forwardEventsNewImplementation(stream, retry); - return this.stream; - }, + return; + } + const stream = apiCall( + argument, + this._callback + ) as CancellableStream; + this.stream = stream; + this.stream = this.forwardEventsWithRetries(stream, retry); + return this.stream; }, - null - ); + }); this.setReadable(retryStream); } else { @@ -544,12 +532,7 @@ export class StreamProxy extends duplexify implements GRPCCallResult { const stream = apiCall(argument, this._callback) as CancellableStream; this.stream = stream; - - if (this.new_retry) { - this.forwardEventsNewImplementation(stream, retry); - } else { - this.forwardEvents(stream); - } + this.forwardEvents(stream); if (this.type === StreamType.CLIENT_STREAMING) { this.setWritable(stream); diff --git a/gax/src/streamingRetryRequest.ts b/gax/src/streamingRetryRequest.ts index 6ecdac9c3..f49877f46 100644 --- a/gax/src/streamingRetryRequest.ts +++ b/gax/src/streamingRetryRequest.ts @@ -13,87 +13,32 @@ // limitations under the License. const {PassThrough} = require('stream'); +import {GoogleError} from './googleError'; +import {ResponseType} from './apitypes'; +import {StreamProxy} from './streamingCalls/streaming'; const DEFAULTS = { /* Max # of retries */ maxRetries: 2, - - /* - The maximum time to delay in seconds. If retryDelayMultiplier results in a - delay greater than maxRetryDelay, retries should delay by maxRetryDelay - seconds instead. - */ - maxRetryDelayMillis: 64000, - - /* - The multiplier by which to increase the delay time between the completion of - failed requests, and the initiation of the subsequent retrying request. - */ - retryDelayMultiplier: 2, - - /* - The length of time to keep retrying in seconds. The last sleep period will - be shortened as necessary, so that the last retry runs at deadline (and not - considerably beyond it). The total time starting from when the initial - request is sent, after which an error will be returned, regardless of the - retrying attempts made meanwhile. - */ - totalTimeoutMillis: 600000, - - /* - The initial delay time, in milliseconds, between the completion of the first - failed request and the initiation of the first retrying request. - */ - initialRetryDelayMillis: 60, - - /* - Initial timeout parameter to the request. - */ - initialRpcTimeoutMillis: 60, - - /* - Maximum timeout in milliseconds for a request. - When this value is reached, rpcTimeoutMulitplier will no - longer be used to increase the timeout. - */ - maxRpcTimeoutMillis: 60, - - /* - Multiplier by which to increase timeout parameter in - between failed requests. - */ - rpcTimeoutMultiplier: 2, - - /* - The number of retries that have occured. - */ - retries: 0, - - retryCodesOrShouldRetryFn: - [14] || - function (response: any) { - return undefined; - }, - - getResumptionRequestFn: function (response: any) { - return undefined; - }, }; - -export function streamingRetryRequest( - requestOpts: any = null, - opts: any = null, - callback: any = null, - ...args: any -) { - const streamMode = typeof args[args.length - 1] !== 'function'; - - if (typeof opts === 'function') { - callback = opts; - } - +// In retry-request, you could pass parameters to request using the requestOpts parameter +// when we called retry-request from gax, we always passed null +// passing null here removes an unnecessary parameter from this implementation +const requestOps = null; +const objectMode = true; // we don't support objectMode being false + +interface streamingRetryRequestOptions { + request?: Function; //TODO update, + maxRetries?: number; //TODO update +} +/** + * Localized adaptation derived from retry-request + * @param opts - corresponds to https://github.com/googleapis/retry-request#opts-optional + * @returns + */ +export function streamingRetryRequest(opts: streamingRetryRequestOptions) { opts = Object.assign({}, DEFAULTS, opts); if (opts.request === undefined) { @@ -108,97 +53,61 @@ export function streamingRetryRequest( let numNoResponseAttempts = 0; let streamResponseHandled = false; - let retryStream: any; - let requestStream: any; - let delayStream: any; - - let activeRequest: {abort: () => void}; - const retryRequest = { - abort: function () { - if (activeRequest && activeRequest.abort) { - activeRequest.abort(); - } - }, - }; + let requestStream: StreamProxy; + let delayStream: StreamProxy; - if (streamMode) { - retryStream = new PassThrough({objectMode: opts.objectMode}); - // retryStream.abort = resetStreams; - } + const retryStream = new PassThrough({objectMode: objectMode}); makeRequest(); - - if (streamMode) { - return retryStream; - } else { - return retryRequest; - } + return retryStream; function makeRequest() { - if (streamMode) { - streamResponseHandled = false; - - delayStream = new PassThrough({objectMode: opts.objectMode}); - requestStream = opts.request(requestOpts); + streamResponseHandled = false; + + delayStream = new PassThrough({objectMode: objectMode}); + requestStream = opts.request!(requestOps); + + requestStream + // gRPC via google-cloud-node can emit an `error` as well as a `response` + // Whichever it emits, we run with-- we can't run with both. That's what + // is up with the `streamResponseHandled` tracking. + .on('error', (err: GoogleError) => { + if (streamResponseHandled) { + return; + } + streamResponseHandled = true; + onResponse(err); + }) + .on('response', (resp: ResponseType) => { + if (streamResponseHandled) { + return; + } - setImmediate(() => { - retryStream.emit('request'); + streamResponseHandled = true; + onResponse(null, resp); }); - - requestStream - // gRPC via google-cloud-node can emit an `error` as well as a `response` - // Whichever it emits, we run with-- we can't run with both. That's what - // is up with the `streamResponseHandled` tracking. - .on('error', (err: any) => { - if (streamResponseHandled) { - return; - } - streamResponseHandled = true; - onResponse(err); - }) - .on('response', (resp: any, body: any) => { - if (streamResponseHandled) { - return; - } - - streamResponseHandled = true; - onResponse(null, resp, body); - }) - .on('complete', retryStream.emit.bind(retryStream, 'complete')); - - requestStream.pipe(delayStream); - } else { - activeRequest = opts.request(requestOpts, onResponse); - } + requestStream.pipe(delayStream); } - function onResponse(err: any, response: any = null, body: any = null) { + function onResponse(err: GoogleError | null, response: ResponseType = null) { // An error such as DNS resolution. if (err) { numNoResponseAttempts++; - if (numNoResponseAttempts <= opts.maxRetries) { + if (numNoResponseAttempts <= opts.maxRetries!) { makeRequest(); } else { - if (streamMode) { - retryStream.emit('error', err); - } else { - callback(err, response, body); - } + retryStream.emit('error', err); } return; } // No more attempts need to be made, just continue on. - if (streamMode) { - retryStream.emit('response', response); - delayStream.pipe(retryStream); - requestStream.on('error', (err: any) => { - retryStream.destroy(err); - }); - } else { - callback(err, response, body); - } + retryStream.emit('response', response); + delayStream.pipe(retryStream); + requestStream.on('error', (err: GoogleError) => { + retryStream.destroy(err); + }); } } diff --git a/gax/test/test-application/src/index.ts b/gax/test/test-application/src/index.ts index 7df1086ae..cda7e49a1 100644 --- a/gax/test/test-application/src/index.ts +++ b/gax/test/test-application/src/index.ts @@ -601,7 +601,7 @@ async function testServerStreamingRetriesWithShouldRetryFn( 10000 ); - const retryOptions = new RetryOptions(shouldRetryFn, backoffSettings); + const retryOptions = new RetryOptions([], backoffSettings, shouldRetryFn); const settings = { retry: retryOptions, @@ -729,8 +729,9 @@ async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrate }; const retryOptions = new RetryOptions( - shouldRetryFn, + [], backoffSettings, + shouldRetryFn, getResumptionRequestFn ); @@ -797,8 +798,9 @@ async function testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResum }; const retryOptions = new RetryOptions( - shouldRetryFn, + [], backoffSettings, + shouldRetryFn, getResumptionRequestFn ); diff --git a/gax/test/unit/apiCallable.ts b/gax/test/unit/apiCallable.ts index 628c6cf36..a11b9d30e 100644 --- a/gax/test/unit/apiCallable.ts +++ b/gax/test/unit/apiCallable.ts @@ -140,13 +140,13 @@ describe('createApiCall', () => { done(); }); }); - it('override just custom retry.retryCodesOrShouldRetryFn with retry codes', done => { + it('override just custom retry.retryCodes with retry codes', done => { const initialRetryCodes = [1]; const overrideRetryCodes = [1, 2, 3]; // eslint-disable-next-line @typescript-eslint/no-explicit-any sinon.stub(retries, 'retryable').callsFake((func, retry): any => { try { - assert.strictEqual(retry.retryCodesOrShouldRetryFn, overrideRetryCodes); + assert.strictEqual(retry.retryCodes, overrideRetryCodes); return func; } catch (err) { done(err); @@ -174,12 +174,12 @@ describe('createApiCall', () => { {}, { retry: { - retryCodesOrShouldRetryFn: overrideRetryCodes, + retryCodes: overrideRetryCodes, }, } ); }); - it('errors when you override just custom retry.retryCodesOrShouldRetryFn with a function on a non streaming call', async () => { + it('errors when you override custom retry.shouldRetryFn with a function on a non streaming call', async () => { function neverRetry() { return false; } @@ -207,7 +207,7 @@ describe('createApiCall', () => { {}, { retry: { - retryCodesOrShouldRetryFn: overrideRetryCodes, + shouldRetryFn: overrideRetryCodes, }, } ); @@ -280,6 +280,7 @@ describe('createApiCall', () => { retry: gax.createRetryOptions( [1], initialBackoffSettings, + undefined, getResumptionRequestFn ), }, diff --git a/gax/test/unit/gax.ts b/gax/test/unit/gax.ts index a8fb7305f..846dd51a0 100644 --- a/gax/test/unit/gax.ts +++ b/gax/test/unit/gax.ts @@ -25,7 +25,6 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; import * as gax from '../../src/gax'; -import {GoogleError} from '../../src'; const SERVICE_NAME = 'test.interface.v1.api'; @@ -75,14 +74,11 @@ const RETRY_DICT = { function expectRetryOptions(obj: gax.RetryOptions) { assert.ok(obj instanceof Object); - ['retryCodesOrShouldRetryFn', 'backoffSettings'].forEach(k => + ['retryCodes', 'backoffSettings'].forEach(k => // eslint-disable-next-line no-prototype-builtins assert.ok(obj.hasOwnProperty(k)) ); - assert.ok( - Array.isArray(obj.retryCodesOrShouldRetryFn) || - obj.retryCodesOrShouldRetryFn instanceof Function - ); + assert.ok(Array.isArray(obj.retryCodes)); expectBackoffSettings(obj.backoffSettings); } @@ -103,51 +99,6 @@ function expectBackoffSettings(obj: gax.BackoffSettings) { } describe('gax construct settings', () => { - it('checks helper function for retry codes', () => { - const defaults = gax.constructSettings( - SERVICE_NAME, - A_CONFIG, - {}, - RETRY_DICT - ); - assert( - gax.isRetryCodes(defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn) - ); - }); - it('checks helper function for should retry function', () => { - const defaults = gax.constructSettings( - SERVICE_NAME, - A_CONFIG, - {}, - RETRY_DICT - ); - function neverRetry(): boolean { - return false; - } - defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn = neverRetry; - assert( - !gax.isRetryCodes(defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn) - ); - }); - it('helper function errors on bad input', () => { - const defaults = gax.constructSettings( - SERVICE_NAME, - A_CONFIG, - {}, - RETRY_DICT - ); - defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn = 5; - try { - gax.isRetryCodes(defaults.bundlingMethod.retry.retryCodesOrShouldRetryFn); - } catch (err) { - assert(err instanceof Error); - assert( - err.message === - 'retryCodesOrShouldRetryFn must be an array or a function' - ); - } - }); - it('creates settings', () => { const otherArgs = {key: 'value'}; const defaults = gax.constructSettings( @@ -161,13 +112,13 @@ describe('gax construct settings', () => { assert.strictEqual(settings.timeout, 40000); assert.strictEqual(settings.apiName, SERVICE_NAME); expectRetryOptions(settings.retry); - assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [1, 2]); + assert.deepStrictEqual(settings.retry.retryCodes, [1, 2]); assert.strictEqual(settings.otherArgs, otherArgs); settings = defaults.pageStreamingMethod; assert.strictEqual(settings.timeout, 30000); expectRetryOptions(settings.retry); - assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [3]); + assert.deepStrictEqual(settings.retry.retryCodes, [3]); assert.strictEqual(settings.otherArgs, otherArgs); }); @@ -234,9 +185,7 @@ describe('gax construct settings', () => { let settings = defaults.bundlingMethod; let backoff = settings.retry.backoffSettings; assert.strictEqual(backoff.initialRetryDelayMillis, 1000); - assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [ - RETRY_DICT.code_a, - ]); + assert.deepStrictEqual(settings.retry.retryCodes, [RETRY_DICT.code_a]); assert.strictEqual(settings.timeout, 50000); /* page_streaming_method is unaffected because it's not specified in @@ -247,8 +196,6 @@ describe('gax construct settings', () => { assert.strictEqual(backoff.initialRetryDelayMillis, 100); assert.strictEqual(backoff.retryDelayMultiplier, 1.2); assert.strictEqual(backoff.maxRetryDelayMillis, 1000); - assert.deepStrictEqual(settings.retry.retryCodesOrShouldRetryFn, [ - RETRY_DICT.code_c, - ]); + assert.deepStrictEqual(settings.retry.retryCodes, [RETRY_DICT.code_c]); }); }); diff --git a/gax/test/unit/streaming.ts b/gax/test/unit/streaming.ts index 9e4ded03f..6cd191600 100644 --- a/gax/test/unit/streaming.ts +++ b/gax/test/unit/streaming.ts @@ -53,22 +53,6 @@ function createApiCallStreaming( ) as GaxCallStream; } -function createApiCallStreamingWithNewLogic( - func: - | Promise - | sinon.SinonSpy, internal.Transform | StreamArrayParser>, - type: streaming.StreamType, - rest?: boolean -) { - const settings = new gax.CallSettings(); - return createApiCall( - //@ts-ignore - Promise.resolve(func), - settings, - new StreamDescriptor(type, rest, true) - ) as GaxCallStream; -} - describe('streaming', () => { afterEach(() => { sinon.restore(); @@ -248,6 +232,7 @@ describe('streaming', () => { s.end(s); }, 50); }); + it('cancels in the middle', done => { const warnStub = sinon.stub(warnings, 'warn'); @@ -397,6 +382,85 @@ describe('streaming', () => { assert.strictEqual(responseCallback.callCount, 1); }); }); + it('emit response when stream received metadata event and new gax retries is enabled', done => { + const responseMetadata = {metadata: true}; + const expectedStatus = {code: 0, metadata: responseMetadata}; + const expectedResponse = { + code: 200, + message: 'OK', + details: '', + metadata: responseMetadata, + }; + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + s.push(null); + setImmediate(() => { + s.emit('metadata', responseMetadata); + }); + s.on('end', () => { + setTimeout(() => { + s.emit('status', expectedStatus); + }, 10); + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // new gax retries + ); + const s = apiCall({}, undefined); + let receivedMetadata: {}; + let receivedStatus: {}; + let receivedResponse: {}; + let ended = false; + + function check() { + if ( + typeof receivedMetadata !== 'undefined' && + typeof receivedStatus !== 'undefined' && + typeof receivedResponse !== 'undefined' && + ended + ) { + assert.deepStrictEqual(receivedMetadata, responseMetadata); + assert.deepStrictEqual(receivedStatus, expectedStatus); + assert.deepStrictEqual(receivedResponse, expectedResponse); + done(); + } + } + + const dataCallback = sinon.spy(data => { + assert.deepStrictEqual(data, undefined); + }); + const responseCallback = sinon.spy(); + assert.strictEqual(s.readable, true); + assert.strictEqual(s.writable, false); + s.on('data', dataCallback); + s.on('metadata', data => { + receivedMetadata = data; + check(); + }); + s.on('response', data => { + receivedResponse = data; + responseCallback(); + check(); + }); + s.on('status', data => { + receivedStatus = data; + check(); + }); + s.on('end', () => { + ended = true; + check(); + assert.strictEqual(dataCallback.callCount, 0); + assert.strictEqual(responseCallback.callCount, 1); + }); + }); it('emit response when stream received no metadata event', done => { const responseMetadata = {metadata: true}; @@ -464,6 +528,74 @@ describe('streaming', () => { assert.strictEqual(responseCallback.callCount, 1); }); }); + it('emit response when stream received no metadata event with new gax retries', done => { + const responseMetadata = {metadata: true}; + const expectedStatus = {code: 0, metadata: responseMetadata}; + const expectedResponse = { + code: 200, + message: 'OK', + details: '', + }; + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + s.push(null); + s.on('end', () => { + setTimeout(() => { + s.emit('status', expectedStatus); + }, 10); + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // new gax retries + ); + const s = apiCall({}, undefined); + let receivedStatus: {}; + let receivedResponse: {}; + let ended = false; + + function check() { + if ( + typeof receivedStatus !== 'undefined' && + typeof receivedResponse !== 'undefined' && + ended + ) { + assert.deepStrictEqual(receivedStatus, expectedStatus); + assert.deepStrictEqual(receivedResponse, expectedResponse); + done(); + } + } + + const dataCallback = sinon.spy(data => { + assert.deepStrictEqual(data, undefined); + }); + const responseCallback = sinon.spy(); + assert.strictEqual(s.readable, true); + assert.strictEqual(s.writable, false); + s.on('data', dataCallback); + s.on('response', data => { + receivedResponse = data; + responseCallback(); + check(); + }); + s.on('status', data => { + receivedStatus = data; + check(); + }); + s.on('end', () => { + ended = true; + check(); + assert.strictEqual(dataCallback.callCount, 0); + assert.strictEqual(responseCallback.callCount, 1); + }); + }); it('emit parsed GoogleError', done => { const warnStub = sinon.stub(warnings, 'warn'); @@ -500,106 +632,629 @@ describe('streaming', () => { const s = new PassThrough({ objectMode: true, }); - s.push(null); - setImmediate(() => { - s.emit('error', error); - }); - setImmediate(() => { - s.emit('end'); - }); - return s; + s.push(null); + setImmediate(() => { + s.emit('error', error); + }); + setImmediate(() => { + s.emit('end'); + }); + return s; + }); + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING + ); + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 0, + }), + } + ); + + s.on('error', err => { + s.pause(); + s.destroy(); + assert(err instanceof GoogleError); + assert.deepStrictEqual(err.message, 'test error'); + assert.strictEqual(err.domain, errorInfoObj.domain); + assert.strictEqual(err.reason, errorInfoObj.reason); + assert.strictEqual( + JSON.stringify(err.errorInfoMetadata), + JSON.stringify(errorInfoObj.metadata) + ); + done(); + }); + s.on('end', () => { + done(); + }); + assert.strictEqual(warnStub.callCount, 1); + assert( + warnStub.calledWith( + 'legacy_streaming_retry_behavior', + 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', + 'DeprecationWarning' + ) + ); + }); + it('emit parsed GoogleError when new retries are enabled', done => { + const errorInfoObj = { + reason: 'SERVICE_DISABLED', + domain: 'googleapis.com', + metadata: { + consumer: 'projects/455411330361', + service: 'translate.googleapis.com', + }, + }; + const errorProtoJson = require('../../protos/status.json'); + const root = protobuf.Root.fromJSON(errorProtoJson); + const errorInfoType = root.lookupType('ErrorInfo'); + const buffer = errorInfoType.encode(errorInfoObj).finish() as Buffer; + const any = { + type_url: 'type.googleapis.com/google.rpc.ErrorInfo', + value: buffer, + }; + const status = {code: 3, message: 'test', details: [any]}; + const Status = root.lookupType('google.rpc.Status'); + const status_buffer = Status.encode(status).finish() as Buffer; + const metadata = new Metadata(); + metadata.set('grpc-status-details-bin', status_buffer); + const error = Object.assign(new GoogleError('test error'), { + code: 5, + details: 'Failed to read', + metadata: metadata, + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + s.push(null); + setImmediate(() => { + s.emit('error', error); + }); + setImmediate(() => { + s.emit('end'); + }); + return s; + }); + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // new retry behavior enabled + ); + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 0, + }), + } + ); + + s.on('error', err => { + s.pause(); + s.destroy(); + assert(err instanceof GoogleError); + assert.deepStrictEqual(err.message, 'test error'); + assert.strictEqual(err.domain, errorInfoObj.domain); + assert.strictEqual(err.reason, errorInfoObj.reason); + assert.strictEqual( + JSON.stringify(err.errorInfoMetadata), + JSON.stringify(errorInfoObj.metadata) + ); + done(); + }); + s.on('end', () => { + done(); + }); + }); + it('emit transient error message on first error when new retries are enabled', done => { + const errorInfoObj = { + reason: 'SERVICE_DISABLED', + domain: 'googleapis.com', + metadata: { + consumer: 'projects/455411330361', + service: 'translate.googleapis.com', + }, + }; + const errorProtoJson = require('../../protos/status.json'); + const root = protobuf.Root.fromJSON(errorProtoJson); + const errorInfoType = root.lookupType('ErrorInfo'); + const buffer = errorInfoType.encode(errorInfoObj).finish() as Buffer; + const any = { + type_url: 'type.googleapis.com/google.rpc.ErrorInfo', + value: buffer, + }; + const status = {code: 3, message: 'test', details: [any]}; + const Status = root.lookupType('google.rpc.Status'); + const status_buffer = Status.encode(status).finish() as Buffer; + const metadata = new Metadata(); + metadata.set('grpc-status-details-bin', status_buffer); + const error = Object.assign(new GoogleError('test error'), { + code: 3, + details: 'Failed to read', + metadata: metadata, + }); + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + s.push(null); + setImmediate(() => { + // emits an error not in our included retry codes + s.emit('error', error); + }); + setImmediate(() => { + s.emit('status', status); + }); + + return s; + }); + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // new retry behavior enabled + ); + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 1, // max retries or timeout must be > 0 in order to reach the code we want to test + }), + } + ); + + s.on('error', err => { + s.pause(); + s.destroy(); + + assert(err instanceof GoogleError); + assert.deepStrictEqual(err.message, 'test error'); + assert.deepStrictEqual( + err.note, + 'Exception occurred in retry method that was not classified as transient' + ); + assert.strictEqual(err.domain, errorInfoObj.domain); + assert.strictEqual(err.reason, errorInfoObj.reason); + assert.strictEqual( + JSON.stringify(err.errorInfoMetadata), + JSON.stringify(errorInfoObj.metadata) + ); + done(); + }); + }); + it('emit transient error on second or later error when new retries are enabled', done => { + // stubbing cancel is needed because PassThrough doesn't have + // a cancel method and cancel is called as part of the retry + const cancelStub = sinon.stub(streaming.StreamProxy.prototype, 'cancel'); + + const errorInfoObj = { + reason: 'SERVICE_DISABLED', + domain: 'googleapis.com', + metadata: { + consumer: 'projects/455411330361', + service: 'translate.googleapis.com', + }, + }; + const errorProtoJson = require('../../protos/status.json'); + const root = protobuf.Root.fromJSON(errorProtoJson); + const errorInfoType = root.lookupType('ErrorInfo'); + const buffer = errorInfoType.encode(errorInfoObj).finish() as Buffer; + const any = { + type_url: 'type.googleapis.com/google.rpc.ErrorInfo', + value: buffer, + }; + const status = {code: 3, message: 'test', details: [any]}; + const Status = root.lookupType('google.rpc.Status'); + const status_buffer = Status.encode(status).finish() as Buffer; + const metadata = new Metadata(); + metadata.set('grpc-status-details-bin', status_buffer); + const error = Object.assign(new GoogleError('test error'), { + code: 2, + details: 'Failed to read', + metadata: metadata, + }); + const error2 = Object.assign(new GoogleError('test error 2'), { + code: 3, + details: 'Failed to read', + metadata: metadata, + }); + let count = 0; + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + let e = new Error(); + switch (count) { + case 0: + e = error; + + s.push(null); + setImmediate(() => { + s.emit('error', e); // is included in our retry codes + }); + setImmediate(() => { + s.emit('status', status); + }); + count++; + return s; + + case 1: + e = error2; // is not in our retry codes + + s.push(null); + setImmediate(() => { + s.emit('error', e); + }); + + setImmediate(() => { + s.emit('status', status); + }); + count++; + + return s; + default: + setImmediate(() => { + s.emit('end'); + }); + return s; + } + }); + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // new retry behavior enabled + ); + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([2, 5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 2, // max retries or timeout must be > 0 in order to reach the code we want to test + }), + } + ); + + s.on('error', err => { + s.pause(); + s.destroy(); + assert(err instanceof GoogleError); + assert.deepStrictEqual(err.message, 'test error 2'); + assert.deepStrictEqual( + err.note, + 'Exception occurred in retry method that was not classified as transient' + ); + assert.strictEqual(err.domain, errorInfoObj.domain); + assert.strictEqual(err.reason, errorInfoObj.reason); + assert.strictEqual( + JSON.stringify(err.errorInfoMetadata), + JSON.stringify(errorInfoObj.metadata) + ); + assert.strictEqual(cancelStub.callCount, 1); + done(); + }); + }); + + it('emit error and retry once', done => { + const firstError = Object.assign(new GoogleError('UNAVAILABLE'), { + code: 14, + details: 'UNAVAILABLE', + }); + let counter = 0; + const expectedStatus = {code: 0}; + const receivedData: string[] = []; + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + setImmediate(() => { + s.push('Hello'); + s.push('World'); + switch (counter) { + case 0: + s.emit('error', firstError); + counter++; + break; + case 1: + s.push('testing'); + s.push('retries'); + s.emit('status', expectedStatus); + counter++; + assert.deepStrictEqual( + receivedData.join(' '), + 'Hello World testing retries' + ); + done(); + break; + default: + break; + } + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // streaming retries + ); + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions([14], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 1, + }), + } + ); + s.on('data', data => { + receivedData.push(data); + }); + }); + + it('emit error and retry twice with shouldRetryFn', done => { + // stubbing cancel is needed because PassThrough doesn't have + // a cancel method and cancel is called as part of the retry + sinon.stub(streaming.StreamProxy.prototype, 'cancel').callsFake(() => { + done(); + }); + const firstError = Object.assign(new GoogleError('UNAVAILABLE'), { + code: 14, + details: 'UNAVAILABLE', + }); + let counter = 0; + + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + + switch (counter) { + case 0: + setImmediate(() => { + s.emit('error', firstError); + }); + setImmediate(() => { + s.emit('status'); + }); + counter++; + return s; + case 1: + setImmediate(() => { + s.emit('error', firstError); + }); + setImmediate(() => { + s.emit('status'); + }); + counter++; + return s; + default: + setImmediate(() => { + s.emit('end'); + }); + return s; + } + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // new retries + ); + + function shouldRetryFn(error: GoogleError) { + return [14].includes(error.code!); + } + const s = apiCall( + {}, + { + retry: gax.createRetryOptions( + [], + { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 2, // maxRetries must be > 1 to ensure we hit both checks for a shouldRetry function + }, + shouldRetryFn + ), + } + ); + + s.on('end', () => { + s.destroy(); + assert.strictEqual(counter, 2); + }); + }); + it('retries using resumption request function ', done => { + // stubbing cancel is needed because PassThrough doesn't have + // a cancel method and cancel is called as part of the retry + sinon.stub(streaming.StreamProxy.prototype, 'cancel'); + const receivedData: string[] = []; + const error = Object.assign(new GoogleError('test error'), { + code: 14, + details: 'UNAVAILABLE', + metadata: new Metadata(), + }); + + const spy = sinon.spy((...args: Array<{}>) => { + //@ts-ignore + const arg = args[0].arg; + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + switch (arg) { + case 0: + s.push('Hello'); + s.push('World'); + setImmediate(() => { + s.emit('metadata'); + }); + setImmediate(() => { + s.emit('error', error); + }); + setImmediate(() => { + s.emit('status'); + }); + return s; + case 1: + s.push(null); + setImmediate(() => { + s.emit('error', new Error('Should not reach')); + }); + + setImmediate(() => { + s.emit('status'); + }); + return s; + case 2: + s.push('testing'); + s.push('retries'); + setImmediate(() => { + s.emit('metadata'); + }); + setImmediate(() => { + s.emit('end'); + }); + return s; + default: + setImmediate(() => { + s.emit('end'); + }); + return s; + } }); const apiCall = createApiCallStreaming( spy, - streaming.StreamType.SERVER_STREAMING + streaming.StreamType.SERVER_STREAMING, + false, + true // new retry behavior enabled ); - + // resumption strategy is to pass a different arg to the function + const getResumptionRequestFn = (originalRequest: RequestType) => { + assert.strictEqual(originalRequest.arg, 0); + return {arg: 2}; + }; const s = apiCall( - {}, + {arg: 0}, { - retry: gax.createRetryOptions([5], { - initialRetryDelayMillis: 100, - retryDelayMultiplier: 1.2, - maxRetryDelayMillis: 1000, - rpcTimeoutMultiplier: 1.5, - maxRpcTimeoutMillis: 3000, - maxRetries: 0, - }), + retry: gax.createRetryOptions( + [14], + { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 2, // max retries or timeout must be > 0 in order to reach the code we want to test + }, + undefined, + getResumptionRequestFn + ), } ); - + s.on('data', data => { + receivedData.push(data); + }); s.on('error', err => { - s.pause(); - s.destroy(); + // double check it's the expected error on the stream + // stream will continue after retry assert(err instanceof GoogleError); assert.deepStrictEqual(err.message, 'test error'); - assert.strictEqual(err.domain, errorInfoObj.domain); - assert.strictEqual(err.reason, errorInfoObj.reason); - assert.strictEqual( - JSON.stringify(err.errorInfoMetadata), - JSON.stringify(errorInfoObj.metadata) - ); - done(); }); s.on('end', () => { + assert.strictEqual(receivedData.length, 4); + assert.deepStrictEqual( + receivedData.join(' '), + 'Hello World testing retries' + ); done(); }); - assert.strictEqual(warnStub.callCount, 1); - assert( - warnStub.calledWith( - 'legacy_streaming_retry_behavior', - 'Legacy streaming retry behavior will not honor settings passed at call time or via client configuration. Please set gaxStreamingRetries to true to utilize passed retry settings. gaxStreamingRetries behavior will be set to true by default in future releases.', - 'DeprecationWarning' - ) - ); + }); +}); + +describe('handles server streaming retries in gax when gaxStreamingRetries is enabled', () => { + afterEach(() => { + sinon.restore(); }); - it('emit error and retry once', done => { + it('server streaming call retries until exceeding max retries', done => { + const retrySpy = sinon.spy(streaming.StreamProxy.prototype, 'retry'); const firstError = Object.assign(new GoogleError('UNAVAILABLE'), { code: 14, details: 'UNAVAILABLE', + metadata: new Metadata(), }); - let counter = 0; - const expectedStatus = {code: 0}; - const receivedData: string[] = []; const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); const s = new PassThrough({ objectMode: true, }); + s.push('hello'); setImmediate(() => { - s.push('Hello'); - s.push('World'); - switch (counter) { - case 0: - s.emit('error', firstError); - counter++; - break; - case 1: - s.push('testing'); - s.push('retries'); - s.emit('status', expectedStatus); - counter++; - assert.deepStrictEqual( - receivedData.join(' '), - 'Hello World testing retries' - ); - done(); - break; - default: - break; - } + s.emit('metadata'); + }); + setImmediate(() => { + s.emit('error', firstError); }); return s; }); - const apiCall = createApiCallStreamingWithNewLogic( + const apiCall = createApiCallStreaming( spy, - streaming.StreamType.SERVER_STREAMING + streaming.StreamType.SERVER_STREAMING, + false, + true ); - const s = apiCall( + const call = apiCall( {}, { retry: gax.createRetryOptions([14], { @@ -608,116 +1263,88 @@ describe('streaming', () => { maxRetryDelayMillis: 1000, rpcTimeoutMultiplier: 1.5, maxRpcTimeoutMillis: 3000, - maxRetries: 1, + maxRetries: 2, }), } ); - let errorCount = 0; - s.on('data', data => { - receivedData.push(data); - }); - s.on('error', err => { + + call.on('error', err => { assert(err instanceof GoogleError); - switch (errorCount) { - case 0: - assert.deepStrictEqual(err.message, 'UNAVAILABLE'); - assert.strictEqual(err.code, 14); - break; - default: - break; + if (err.code !== 14) { + // ignore the error we are expecting + assert.strictEqual(err.code, 4); + // even though max retries is 2 + // the retry function will always be called maxRetries+1 + // the final call is where the failure happens + assert.strictEqual(retrySpy.callCount, 3); + assert.strictEqual( + err.message, + 'Exceeded maximum number of retries before any response was received' + ); + done(); } - errorCount++; }); }); - it('emit error and retry once with shouldRetryFn', done => { + it('server streaming call retries until exceeding total timeout', done => { const firstError = Object.assign(new GoogleError('UNAVAILABLE'), { code: 14, details: 'UNAVAILABLE', + metadata: new Metadata(), }); - let counter = 0; - const expectedStatus = {code: 0}; - const receivedData: string[] = []; const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); const s = new PassThrough({ objectMode: true, }); + s.push('hello'); setImmediate(() => { - s.push('Hello'); - s.push('World'); - switch (counter) { - case 0: - s.emit('error', firstError); - counter++; - break; - case 1: - s.push('testing'); - s.push('retries'); - s.emit('status', expectedStatus); - counter++; - assert.deepStrictEqual( - receivedData.join(' '), - 'Hello World testing retries' - ); - done(); - break; - default: - break; - } + s.emit('metadata'); + }); + setImmediate(() => { + s.emit('error', firstError); }); return s; }); - const apiCall = createApiCallStreamingWithNewLogic( + const apiCall = createApiCallStreaming( spy, - streaming.StreamType.SERVER_STREAMING + streaming.StreamType.SERVER_STREAMING, + false, + true ); - function retryCodesOrShouldRetryFn(error: GoogleError) { - return [14].includes(error.code!); - } - - const s = apiCall( + const call = apiCall( {}, { - retry: gax.createRetryOptions(retryCodesOrShouldRetryFn, { + retry: gax.createRetryOptions([14], { initialRetryDelayMillis: 100, retryDelayMultiplier: 1.2, maxRetryDelayMillis: 1000, rpcTimeoutMultiplier: 1.5, maxRpcTimeoutMillis: 3000, - maxRetries: 1, + totalTimeoutMillis: 10, }), } ); - let errorCount = 0; - s.on('data', data => { - receivedData.push(data); - }); - s.on('error', err => { + + call.on('error', err => { assert(err instanceof GoogleError); - switch (errorCount) { - case 0: - assert.deepStrictEqual(err.message, 'UNAVAILABLE'); - assert.strictEqual(err.code, 14); - break; - default: - break; + if (err.code !== 14) { + // ignore the error we are expecting + assert.strictEqual(err.code, 4); + assert.strictEqual( + err.message, + 'Total timeout of API exceeded 10 milliseconds before any response was received.' + ); + done(); } - errorCount++; }); }); -}); - -describe('handles server streaming retries in gax when gaxStreamingRetries is enabled', () => { - afterEach(() => { - sinon.restore(); - }); it('allows custom CallOptions.retry settings with shouldRetryFn instead of retryCodes and new retry behavior', done => { sinon - .stub(streaming.StreamProxy.prototype, 'forwardEventsNewImplementation') + .stub(streaming.StreamProxy.prototype, 'forwardEventsWithRetries') .callsFake((stream): any => { assert(stream instanceof internal.Stream); done(); @@ -741,9 +1368,43 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en {}, { retry: gax.createRetryOptions( + [], + { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4500, + }, () => { return true; - }, + } + ), + } + ); + }); + it('errors when both retryCodes and shouldRetryFn are passed', done => { + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true //gaxStreamingRetries + ); + + const s = apiCall( + {}, + { + retry: gax.createRetryOptions( + [14], { initialRetryDelayMillis: 100, retryDelayMultiplier: 1.2, @@ -751,14 +1412,24 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en rpcTimeoutMultiplier: 1.5, maxRpcTimeoutMillis: 3000, totalTimeoutMillis: 4500, + }, + () => { + return true; } ), } ); + s.on('error', error => { + assert.strictEqual( + error.message, + 'Only one of retryCodes or shouldRetryFn may be defined' + ); + done(); + }); }); it('allows custom CallOptions.retry settings with retryCodes and new retry behavior', done => { sinon - .stub(streaming.StreamProxy.prototype, 'forwardEventsNewImplementation') + .stub(streaming.StreamProxy.prototype, 'forwardEventsWithRetries') .callsFake((stream): any => { assert(stream instanceof internal.Stream); done(); @@ -794,7 +1465,7 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en }); it('allows the user to pass a custom resumption strategy', done => { sinon - .stub(streaming.StreamProxy.prototype, 'forwardEventsNewImplementation') + .stub(streaming.StreamProxy.prototype, 'forwardEventsWithRetries') .callsFake((stream, retry): any => { assert(stream instanceof internal.Stream); assert(retry.getResumptionRequestFn instanceof Function); @@ -833,12 +1504,72 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en maxRpcTimeoutMillis: 3000, totalTimeoutMillis: 4500, }, + undefined, getResumptionRequestFn ), } ); }); + it('throws an error when both totalTimeoutMillis and maxRetries are passed at call time when new retry behavior is enabled', done => { + const status = {code: 4, message: 'test'}; + const error = Object.assign(new GoogleError('test error'), { + code: 4, + details: 'Failed to read', + }); + const spy = sinon.spy((...args: Array<{}>) => { + assert.strictEqual(args.length, 3); + const s = new PassThrough({ + objectMode: true, + }); + s.push(null); + setImmediate(() => { + s.emit('metadata'); + }); + setImmediate(() => { + // emits an error not in our included retry codes + s.emit('error', error); + }); + setImmediate(() => { + s.emit('status', status); + }); + + return s; + }); + + const apiCall = createApiCallStreaming( + spy, + streaming.StreamType.SERVER_STREAMING, + false, + true // ensure we're doing the new retries + ); + // make the call with both options passed at call time + const call = apiCall( + {}, + { + retry: gax.createRetryOptions([1, 4], { + initialRetryDelayMillis: 300, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + totalTimeoutMillis: 4000, + maxRetries: 5, + }), + } + ); + call.on('error', err => { + assert(err instanceof GoogleError); + if (err.code !== 4) { + assert.strictEqual(err.code, 3); + assert.strictEqual( + err.message, + 'Cannot set both totalTimeoutMillis and maxRetries in backoffSettings.' + ); + done(); + } + }); + }); it('throws an error when both retryRequestoptions and retryOptions are passed at call time when new retry behavior is enabled', done => { //if this is reached, it means the settings merge in createAPICall did not fail properly sinon @@ -922,9 +1653,8 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en ); assert.strictEqual(settings.retry?.backoffSettings.maxRetries, 1); - assert( - typeof settings.retry.retryCodesOrShouldRetryFn === 'function' - ); + assert(settings.retry.shouldRetryFn); + assert(settings.retry.retryCodes.length === 0); assert(settings.retry !== new gax.CallSettings().retry); done(); } catch (err) { @@ -1018,9 +1748,8 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en ); assert.strictEqual(settings.retry?.backoffSettings.maxRetries, 0); - assert( - typeof settings.retry.retryCodesOrShouldRetryFn === 'function' - ); + assert(settings.retry.shouldRetryFn); + assert(settings.retry.retryCodes.length === 0); assert(settings.retry !== new gax.CallSettings().retry); done(); } catch (err) { @@ -1114,9 +1843,8 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en assert( typeof settings.retry?.backoffSettings.maxRetries === 'undefined' ); - assert( - typeof settings.retry.retryCodesOrShouldRetryFn === 'function' - ); + assert(settings.retry.shouldRetryFn); + assert(settings.retry.retryCodes.length === 0); assert(settings.retry !== new gax.CallSettings().retry); done(); } catch (err) { @@ -1209,9 +1937,8 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en assert( typeof settings.retry?.backoffSettings.maxRetries === 'undefined' ); - assert( - typeof settings.retry.retryCodesOrShouldRetryFn === 'function' - ); + assert(settings.retry.shouldRetryFn); + assert(settings.retry.retryCodes.length === 0); assert(settings.retry !== new gax.CallSettings().retry); done(); } catch (err) { diff --git a/gax/test/unit/streamingRetryRequest.ts b/gax/test/unit/streamingRetryRequest.ts index dbdb204b6..d8ed372f6 100644 --- a/gax/test/unit/streamingRetryRequest.ts +++ b/gax/test/unit/streamingRetryRequest.ts @@ -68,29 +68,24 @@ describe('retry-request', () => { true ); - const retryStream = streamingRetryRequest( - null, - { - objectMode: true, - request: () => { - const stream = apiCall( - {}, - { - retry: gax.createRetryOptions([5], { - initialRetryDelayMillis: 100, - retryDelayMultiplier: 1.2, - maxRetryDelayMillis: 1000, - rpcTimeoutMultiplier: 1.5, - maxRpcTimeoutMillis: 3000, - maxRetries: 0, - }), - } - ) as CancellableStream; - return stream; - }, + const retryStream = streamingRetryRequest({ + request: () => { + const stream = apiCall( + {}, + { + retry: gax.createRetryOptions([5], { + initialRetryDelayMillis: 100, + retryDelayMultiplier: 1.2, + maxRetryDelayMillis: 1000, + rpcTimeoutMultiplier: 1.5, + maxRpcTimeoutMillis: 3000, + maxRetries: 0, + }), + } + ) as CancellableStream; + return stream; }, - null - ) + }) .on('end', done()) .on('data', (data: any) => { console.log(data); @@ -100,20 +95,10 @@ describe('retry-request', () => { it('throws request error', done => { try { - streamingRetryRequest(null, null, null); - } catch (err: any) { - assert.match(err.message, /A request library must be provided/); - done(); - } - }); - - it('opts is a function and throws request error', done => { - try { - const opts = () => { - return true; - }; - streamingRetryRequest(null, opts, null); - } catch (err: any) { + const opts = {}; + streamingRetryRequest(opts); + } catch (err) { + assert(err instanceof Error); assert.match(err.message, /A request library must be provided/); done(); } From 3c920363f1d862dd7cc321fa0d0ddec082f20cf5 Mon Sep 17 00:00:00 2001 From: Leah Cole Date: Mon, 13 Nov 2023 11:17:50 -0500 Subject: [PATCH 33/35] resolve typescript warnings --- gax/src/streamingCalls/streaming.ts | 6 --- gax/src/streamingRetryRequest.ts | 4 +- gax/test/test-application/src/index.ts | 2 +- gax/test/unit/apiCallable.ts | 3 +- gax/test/unit/streaming.ts | 58 +++++++++++++------------- gax/test/unit/streamingRetryRequest.ts | 10 +++-- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/gax/src/streamingCalls/streaming.ts b/gax/src/streamingCalls/streaming.ts index db2341b54..c55b5ffe2 100644 --- a/gax/src/streamingCalls/streaming.ts +++ b/gax/src/streamingCalls/streaming.ts @@ -29,7 +29,6 @@ import {RetryOptions, RetryRequestOptions} from '../gax'; import {GoogleError} from '../googleError'; import {streamingRetryRequest} from '../streamingRetryRequest'; import {Status} from '../status'; -import {warn} from '../warnings'; // eslint-disable-next-line @typescript-eslint/no-var-requires const duplexify: DuplexifyConstructor = require('duplexify'); @@ -478,11 +477,6 @@ export class StreamProxy extends duplexify implements GRPCCallResult { this.stream = stream; this.setReadable(stream); } else if (this.gaxServerStreamingRetries) { - warn( - 'gax_server_streaming_retries', - 'You are using the new experimental gax native server streaming retry implementation', - 'ExperimentalWarning' - ); const retryStream = streamingRetryRequest({ request: () => { if (this._isCancelCalled) { diff --git a/gax/src/streamingRetryRequest.ts b/gax/src/streamingRetryRequest.ts index f49877f46..e97f64355 100644 --- a/gax/src/streamingRetryRequest.ts +++ b/gax/src/streamingRetryRequest.ts @@ -30,8 +30,8 @@ const requestOps = null; const objectMode = true; // we don't support objectMode being false interface streamingRetryRequestOptions { - request?: Function; //TODO update, - maxRetries?: number; //TODO update + request?: Function; + maxRetries?: number; } /** * Localized adaptation derived from retry-request diff --git a/gax/test/test-application/src/index.ts b/gax/test/test-application/src/index.ts index cda7e49a1..501a77f17 100644 --- a/gax/test/test-application/src/index.ts +++ b/gax/test/test-application/src/index.ts @@ -830,7 +830,7 @@ async function testServerStreamingRetrieswithRetryRequestOptionsErrorsOnBadResum settings ); - attemptStream.on('error', (e: any) => { + attemptStream.on('error', (e: GoogleError) => { if (!allowedCodes.includes(e.code!)) { reject(e); } diff --git a/gax/test/unit/apiCallable.ts b/gax/test/unit/apiCallable.ts index a11b9d30e..4fc642953 100644 --- a/gax/test/unit/apiCallable.ts +++ b/gax/test/unit/apiCallable.ts @@ -211,7 +211,8 @@ describe('createApiCall', () => { }, } ); - } catch (err: any) { + } catch (err) { + assert(err instanceof Error); assert.strictEqual( err.message, 'Using a function to determine retry eligibility is only supported with server streaming calls' diff --git a/gax/test/unit/streaming.ts b/gax/test/unit/streaming.ts index 6cd191600..277d9a1b3 100644 --- a/gax/test/unit/streaming.ts +++ b/gax/test/unit/streaming.ts @@ -1345,7 +1345,7 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en it('allows custom CallOptions.retry settings with shouldRetryFn instead of retryCodes and new retry behavior', done => { sinon .stub(streaming.StreamProxy.prototype, 'forwardEventsWithRetries') - .callsFake((stream): any => { + .callsFake((stream): undefined => { assert(stream instanceof internal.Stream); done(); }); @@ -1430,7 +1430,7 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en it('allows custom CallOptions.retry settings with retryCodes and new retry behavior', done => { sinon .stub(streaming.StreamProxy.prototype, 'forwardEventsWithRetries') - .callsFake((stream): any => { + .callsFake((stream): undefined => { assert(stream instanceof internal.Stream); done(); }); @@ -1466,7 +1466,7 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en it('allows the user to pass a custom resumption strategy', done => { sinon .stub(streaming.StreamProxy.prototype, 'forwardEventsWithRetries') - .callsFake((stream, retry): any => { + .callsFake((stream, retry): undefined => { assert(stream instanceof internal.Stream); assert(retry.getResumptionRequestFn instanceof Function); done(); @@ -1572,11 +1572,9 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en }); it('throws an error when both retryRequestoptions and retryOptions are passed at call time when new retry behavior is enabled', done => { //if this is reached, it means the settings merge in createAPICall did not fail properly - sinon - .stub(StreamingApiCaller.prototype, 'call') - .callsFake((apiCall, argument, settings, stream) => { - throw new Error("This shouldn't be happening"); - }); + sinon.stub(StreamingApiCaller.prototype, 'call').callsFake(() => { + throw new Error("This shouldn't be happening"); + }); const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); @@ -1635,6 +1633,9 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en sinon .stub(StreamingApiCaller.prototype, 'call') .callsFake((apiCall, argument, settings, stream) => { + assert(typeof argument === 'object'); + assert(typeof apiCall === 'function'); + assert(stream instanceof streaming.StreamProxy); try { assert(settings.retry); assert(typeof settings.retryRequestOptions === 'undefined'); @@ -1730,6 +1731,9 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en sinon .stub(StreamingApiCaller.prototype, 'call') .callsFake((apiCall, argument, settings, stream) => { + assert(typeof argument === 'object'); + assert(typeof apiCall === 'function'); + assert(stream instanceof streaming.StreamProxy); try { assert(settings.retry); assert(typeof settings.retryRequestOptions === 'undefined'); @@ -1825,6 +1829,9 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en sinon .stub(StreamingApiCaller.prototype, 'call') .callsFake((apiCall, argument, settings, stream) => { + assert(typeof argument === 'object'); + assert(typeof apiCall === 'function'); + assert(stream instanceof streaming.StreamProxy); try { assert(settings.retry); assert(typeof settings.retryRequestOptions === 'undefined'); @@ -1919,6 +1926,9 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en sinon .stub(StreamingApiCaller.prototype, 'call') .callsFake((apiCall, argument, settings, stream) => { + assert(typeof argument === 'object'); + assert(typeof apiCall === 'function'); + assert(stream instanceof streaming.StreamProxy); try { assert(settings.retry); assert(typeof settings.retryRequestOptions === 'undefined'); @@ -2019,11 +2029,9 @@ describe('warns/errors about server streaming retry behavior when gaxStreamingRe const warnStub = sinon.stub(warnings, 'warn'); // this exists to help resolve createApiCall - sinon - .stub(StreamingApiCaller.prototype, 'call') - .callsFake((apiCall, argument, settings, stream) => { - done(); - }); + sinon.stub(StreamingApiCaller.prototype, 'call').callsFake(() => { + done(); + }); const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); @@ -2070,11 +2078,9 @@ describe('warns/errors about server streaming retry behavior when gaxStreamingRe it('throws a warning when retry options are passed', done => { const warnStub = sinon.stub(warnings, 'warn'); // this exists to help resolve createApiCall - sinon - .stub(StreamingApiCaller.prototype, 'call') - .callsFake((apiCall, argument, settings, stream) => { - done(); - }); + sinon.stub(StreamingApiCaller.prototype, 'call').callsFake(() => { + done(); + }); const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); @@ -2117,11 +2123,9 @@ describe('warns/errors about server streaming retry behavior when gaxStreamingRe it('throws no warnings when when no retry options are passed', done => { const warnStub = sinon.stub(warnings, 'warn'); // this exists to help resolve createApiCall - sinon - .stub(StreamingApiCaller.prototype, 'call') - .callsFake((apiCall, argument, settings, stream) => { - done(); - }); + sinon.stub(StreamingApiCaller.prototype, 'call').callsFake(() => { + done(); + }); const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); @@ -2145,11 +2149,9 @@ describe('warns/errors about server streaming retry behavior when gaxStreamingRe it('throws two warnings when when retry and retryRequestoptions are passed', done => { const warnStub = sinon.stub(warnings, 'warn'); // this exists to help resolve createApiCall - sinon - .stub(StreamingApiCaller.prototype, 'call') - .callsFake((apiCall, argument, settings, stream) => { - done(); - }); + sinon.stub(StreamingApiCaller.prototype, 'call').callsFake(() => { + done(); + }); const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); diff --git a/gax/test/unit/streamingRetryRequest.ts b/gax/test/unit/streamingRetryRequest.ts index d8ed372f6..5dca3ef32 100644 --- a/gax/test/unit/streamingRetryRequest.ts +++ b/gax/test/unit/streamingRetryRequest.ts @@ -46,6 +46,7 @@ function createApiCallStreaming( describe('retry-request', () => { describe('streams', () => { + let receivedData: number[] = []; it('works with defaults in a stream', done => { const spy = sinon.spy((...args: Array<{}>) => { assert.strictEqual(args.length, 3); @@ -86,9 +87,12 @@ describe('retry-request', () => { return stream; }, }) - .on('end', done()) - .on('data', (data: any) => { - console.log(data); + .on('end', () => { + assert.deepStrictEqual(receivedData, [1, 2, 3, 4, 5]); + done(); + }) + .on('data', (data: {resources: number[]}) => { + receivedData = receivedData.concat(data.resources); }); assert.strictEqual(retryStream._readableState.objectMode, true); }); From a8488ce5e4f9c8096ca55f0265733c7902256610 Mon Sep 17 00:00:00 2001 From: Alexander Fenster Date: Mon, 27 Nov 2023 20:05:13 +0000 Subject: [PATCH 34/35] fix: do not throw error if both retryCodes and shouldRetryFn are defined --- gax/src/createApiCall.ts | 7 ++++-- gax/test/unit/apiCallable.ts | 4 ++-- gax/test/unit/streaming.ts | 43 ------------------------------------ 3 files changed, 7 insertions(+), 47 deletions(-) diff --git a/gax/src/createApiCall.ts b/gax/src/createApiCall.ts index 55bd1fb1b..c51b613d1 100644 --- a/gax/src/createApiCall.ts +++ b/gax/src/createApiCall.ts @@ -32,6 +32,7 @@ import {CallOptions, CallSettings, convertRetryOptions} from './gax'; import {retryable} from './normalCalls/retries'; import {addTimeoutArg} from './normalCalls/timeout'; import {StreamingApiCaller} from './streamingCalls/streamingApiCaller'; +import {warn} from './warnings'; /** * Converts an rpc call into an API call governed by the settings. @@ -110,9 +111,11 @@ export function createApiCall( retry.retryCodes.length > 0 && retry.shouldRetryFn ) { - throw new Error( - 'Only one of retryCodes or shouldRetryFn may be defined' + warn( + 'either_retrycodes_or_shouldretryfn', + 'Only one of retryCodes or shouldRetryFn may be defined. Ignoring retryCodes.' ); + retry.retryCodes = []; } if (!streaming && retry) { if (retry.shouldRetryFn) { diff --git a/gax/test/unit/apiCallable.ts b/gax/test/unit/apiCallable.ts index 4fc642953..ae98bbf41 100644 --- a/gax/test/unit/apiCallable.ts +++ b/gax/test/unit/apiCallable.ts @@ -67,8 +67,8 @@ describe('createApiCall', () => { const now = new Date(); const originalDeadline = now.getTime() + 100; const expectedDeadline = now.getTime() + 200; - assert((resp as any)! > originalDeadline); - assert((resp as any)! <= expectedDeadline); + assert((resp as unknown as number)! > originalDeadline); + assert((resp as unknown as number)! <= expectedDeadline); done(); }); }); diff --git a/gax/test/unit/streaming.ts b/gax/test/unit/streaming.ts index 277d9a1b3..9aa9c5ffc 100644 --- a/gax/test/unit/streaming.ts +++ b/gax/test/unit/streaming.ts @@ -1384,49 +1384,6 @@ describe('handles server streaming retries in gax when gaxStreamingRetries is en } ); }); - it('errors when both retryCodes and shouldRetryFn are passed', done => { - const spy = sinon.spy((...args: Array<{}>) => { - assert.strictEqual(args.length, 3); - const s = new PassThrough({ - objectMode: true, - }); - return s; - }); - - const apiCall = createApiCallStreaming( - spy, - streaming.StreamType.SERVER_STREAMING, - false, - true //gaxStreamingRetries - ); - - const s = apiCall( - {}, - { - retry: gax.createRetryOptions( - [14], - { - initialRetryDelayMillis: 100, - retryDelayMultiplier: 1.2, - maxRetryDelayMillis: 1000, - rpcTimeoutMultiplier: 1.5, - maxRpcTimeoutMillis: 3000, - totalTimeoutMillis: 4500, - }, - () => { - return true; - } - ), - } - ); - s.on('error', error => { - assert.strictEqual( - error.message, - 'Only one of retryCodes or shouldRetryFn may be defined' - ); - done(); - }); - }); it('allows custom CallOptions.retry settings with retryCodes and new retry behavior', done => { sinon .stub(streaming.StreamProxy.prototype, 'forwardEventsWithRetries') From 46187da8dfa76a635698fe68141a16e480855874 Mon Sep 17 00:00:00 2001 From: Alexander Fenster Date: Mon, 27 Nov 2023 20:23:55 +0000 Subject: [PATCH 35/35] test: remove lint warnings from tests --- gax/test/system-test/test.clientlibs.ts | 25 ------------------------- gax/test/test-application/src/index.ts | 10 +++++----- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/gax/test/system-test/test.clientlibs.ts b/gax/test/system-test/test.clientlibs.ts index 3733903ca..fcc14e761 100644 --- a/gax/test/system-test/test.clientlibs.ts +++ b/gax/test/system-test/test.clientlibs.ts @@ -165,14 +165,6 @@ async function runSystemTest( return await runScript(packageName, inMonorepo, 'system-test'); } -// nodejs-kms does not have system test. -async function runSamplesTest( - packageName: string, - inMonorepo: boolean -): Promise { - return await runScript(packageName, inMonorepo, 'samples-test'); -} - describe('Run system tests for some libraries', () => { before(async () => { console.log('Packing google-gax...'); @@ -218,21 +210,4 @@ describe('Run system tests for some libraries', () => { } }); }); - - // KMS api has IAM service injected from gax. All its IAM related test are in samples-test. - // KMS is in the google-cloud-node monorepo - // Temporarily skipped to avoid circular dependency issue. - /*describe('kms', () => { - before(async () => { - await preparePackage('kms', true); - }); - it('should pass samples tests', async function () { - const result = await runSamplesTest('kms', true); - if (result === TestResult.SKIP) { - this.skip(); - } else if (result === TestResult.FAIL) { - throw new Error('Test failed'); - } - }); - });*/ }); diff --git a/gax/test/test-application/src/index.ts b/gax/test/test-application/src/index.ts index 501a77f17..8d13f8618 100644 --- a/gax/test/test-application/src/index.ts +++ b/gax/test/test-application/src/index.ts @@ -495,7 +495,7 @@ async function testServerStreamingRetryOptions(client: SequenceServiceClient) { ); const response = await client.createStreamingSequence(request); - await new Promise((resolve, _) => { + await new Promise(resolve => { const sequence = response[0]; const attemptRequest = @@ -554,7 +554,7 @@ async function testServerStreamingRetrieswithRetryOptions( ); const response = await client.createStreamingSequence(request); - await new Promise((resolve, _) => { + await new Promise(resolve => { const sequence = response[0]; const attemptRequest = @@ -617,7 +617,7 @@ async function testServerStreamingRetriesWithShouldRetryFn( ); const response = await client.createStreamingSequence(request); - await new Promise((resolve, _) => { + await new Promise(resolve => { const sequence = response[0]; const attemptRequest = new protos.google.showcase.v1beta1.AttemptStreamingSequenceRequest(); @@ -676,7 +676,7 @@ async function testServerStreamingRetrieswithRetryRequestOptions( ); const response = await client.createStreamingSequence(request); - await new Promise((resolve, _) => { + await new Promise(resolve => { const sequence = response[0]; const attemptRequest = @@ -748,7 +748,7 @@ async function testServerStreamingRetrieswithRetryRequestOptionsResumptionStrate 'This is testing the brand new and shiny StreamingSequence server 3' ); const response = await client.createStreamingSequence(request); - await new Promise((resolve, _) => { + await new Promise(resolve => { const sequence = response[0]; const attemptRequest =