Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit c7004da

Browse files
feat(listBuckets): Add support for returning partial success (#2678)
* feat(listBuckets): Add support for returning partial success Implements the optional parameter `returnPartialSuccess`. If set to `true` and the API response contains an `unreachable` array, the function will return a successful callback (`err: null`) and pass the `unreachable` array as the 5th argument. This allows users to process the available buckets without the entire operation failing on soft errors. * docs(samples): Add listBucketsPartialSuccess sample New sample for partial success in ListBuckets (storage_list_buckets_partial_success) * code refactor * minor fix * Ensure positional consistency for optional callback results * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * Enable partial success via placeholder objects * bug fix * addressing commends * refactor: Simplify partial success placeholder logic * addressing comments * Revert "addressing comments" This reverts commit 10a7e87. * addressing comments * chore: Clean up unreachable placeholder creation loop * refactor: test case --------- Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 633a13a commit c7004da

6 files changed

Lines changed: 180 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre
172172
| Get HMAC SA Key Metadata. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/hmacKeyGet.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/hmacKeyGet.js,samples/README.md) |
173173
| List HMAC SA Keys Metadata. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/hmacKeysList.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/hmacKeysList.js,samples/README.md) |
174174
| List Buckets | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listBuckets.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listBuckets.js,samples/README.md) |
175+
| List Buckets Partial Success | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listBucketsPartialSuccess.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listBucketsPartialSuccess.js,samples/README.md) |
175176
| List Files | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFiles.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listFiles.js,samples/README.md) |
176177
| List Files By Prefix | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFilesByPrefix.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listFilesByPrefix.js,samples/README.md) |
177178
| List Files Paginate | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFilesPaginate.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listFilesPaginate.js,samples/README.md) |

samples/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ objects to users via direct download.
9191
* [Get HMAC SA Key Metadata.](#get-hmac-sa-key-metadata.)
9292
* [List HMAC SA Keys Metadata.](#list-hmac-sa-keys-metadata.)
9393
* [List Buckets](#list-buckets)
94+
* [List Buckets Partial Success](#list-buckets-partial-success)
9495
* [List Files](#list-files)
9596
* [List Files By Prefix](#list-files-by-prefix)
9697
* [List Files Paginate](#list-files-paginate)
@@ -1461,6 +1462,23 @@ __Usage:__
14611462

14621463

14631464

1465+
### List Buckets Partial Success
1466+
1467+
View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listBucketsPartialSuccess.js).
1468+
1469+
[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/listBucketsPartialSuccess.js,samples/README.md)
1470+
1471+
__Usage:__
1472+
1473+
1474+
`node samples/listBucketsPartialSuccess.js`
1475+
1476+
1477+
-----
1478+
1479+
1480+
1481+
14641482
### List Files
14651483

14661484
View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFiles.js).
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
function main() {
18+
// [START storage_list_buckets_partial_success]
19+
// Imports the Google Cloud client library
20+
const {Storage} = require('@google-cloud/storage');
21+
22+
// Creates a client
23+
const storage = new Storage();
24+
25+
async function listBucketsPartialSuccess() {
26+
const option = {
27+
returnPartialSuccess: true,
28+
maxResults: 5,
29+
};
30+
const [buckets, nextQuery, apiResponse] = await storage.getBuckets(option);
31+
32+
if (nextQuery && nextQuery.pageToken) {
33+
console.log(`Next Page Token: ${nextQuery.pageToken}`);
34+
}
35+
36+
console.log('\nBuckets:');
37+
buckets.forEach(bucket => {
38+
if (bucket.unreachable) {
39+
console.log(`${bucket.name} (unreachable: ${bucket.unreachable})`);
40+
} else {
41+
console.log(`${bucket.name}`);
42+
}
43+
});
44+
45+
if (apiResponse.unreachable && apiResponse.unreachable.length > 0) {
46+
console.log('\nUnreachable Buckets:');
47+
apiResponse.unreachable.forEach(item => {
48+
console.log(item);
49+
});
50+
}
51+
}
52+
53+
listBucketsPartialSuccess().catch(console.error);
54+
// [END storage_list_buckets_partial_success]
55+
}
56+
57+
main(...process.argv.slice(2));

src/bucket.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,13 @@ class Bucket extends ServiceObject<Bucket, BucketMetadata> {
838838
private instanceRetryValue?: boolean;
839839
instancePreconditionOpts?: PreconditionOptions;
840840

841+
/**
842+
* Indicates whether this Bucket object is a placeholder for an item
843+
* that the API failed to retrieve (unreachable) due to partial failure.
844+
* Consumers must check this flag before accessing other properties.
845+
*/
846+
unreachable = false;
847+
841848
constructor(storage: Storage, name: string, options?: BucketOptions) {
842849
options = options || {};
843850

src/storage.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ export interface GetBucketsRequest {
189189
userProject?: string;
190190
softDeleted?: boolean;
191191
generation?: number;
192+
returnPartialSuccess?: boolean;
192193
}
193194

194195
export interface HmacKeyResourceResponse {
@@ -1338,12 +1339,27 @@ export class Storage extends Service {
13381339
}
13391340

13401341
const itemsArray = resp.items ? resp.items : [];
1342+
const unreachableArray = resp.unreachable ? resp.unreachable : [];
1343+
13411344
const buckets = itemsArray.map((bucket: BucketMetadata) => {
13421345
const bucketInstance = this.bucket(bucket.id!);
13431346
bucketInstance.metadata = bucket;
1347+
13441348
return bucketInstance;
13451349
});
13461350

1351+
if (unreachableArray.length > 0) {
1352+
unreachableArray.forEach((fullPath: string) => {
1353+
const name = fullPath.split('/').pop();
1354+
if (name) {
1355+
const placeholder = this.bucket(name);
1356+
placeholder.unreachable = true;
1357+
placeholder.metadata = {};
1358+
buckets.push(placeholder);
1359+
}
1360+
});
1361+
}
1362+
13471363
const nextQuery = resp.nextPageToken
13481364
? Object.assign({}, options, {pageToken: resp.nextPageToken})
13491365
: null;

test/index.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,87 @@ describe('Storage', () => {
11771177
done();
11781178
});
11791179
});
1180+
1181+
it('should return unreachable when returnPartialSuccess is true', done => {
1182+
const unreachableList = ['projects/_/buckets/fail-bucket'];
1183+
const itemsList = [{id: 'fake-bucket-name'}];
1184+
const resp = {items: itemsList, unreachable: unreachableList};
1185+
1186+
storage.request = (
1187+
reqOpts: DecorateRequestOptions,
1188+
callback: Function
1189+
) => {
1190+
assert.strictEqual(reqOpts.qs.returnPartialSuccess, true);
1191+
callback(null, resp);
1192+
};
1193+
1194+
storage.getBuckets(
1195+
{returnPartialSuccess: true},
1196+
(err: Error, buckets: Bucket[], nextQuery: {}, apiResponse: {}) => {
1197+
assert.ifError(err);
1198+
assert.strictEqual(buckets.length, 2);
1199+
1200+
const reachableBucket = buckets.find(
1201+
b => b.name === 'fake-bucket-name'
1202+
);
1203+
assert.ok(reachableBucket);
1204+
assert.strictEqual(reachableBucket.unreachable, false);
1205+
1206+
const unreachableBucket = buckets.find(b => b.name === 'fail-bucket');
1207+
assert.ok(unreachableBucket);
1208+
assert.strictEqual(unreachableBucket.unreachable, true);
1209+
assert.deepStrictEqual(apiResponse, resp);
1210+
done();
1211+
}
1212+
);
1213+
});
1214+
1215+
it('should handle partial failure with zero reachable buckets', done => {
1216+
const unreachableList = ['projects/_/buckets/fail-bucket'];
1217+
const resp = {items: [], unreachable: unreachableList};
1218+
1219+
storage.request = (
1220+
reqOpts: DecorateRequestOptions,
1221+
callback: Function
1222+
) => {
1223+
assert.strictEqual(reqOpts.qs.returnPartialSuccess, true);
1224+
callback(null, resp);
1225+
};
1226+
1227+
storage.getBuckets(
1228+
{returnPartialSuccess: true},
1229+
(err: Error, buckets: Bucket[]) => {
1230+
assert.ifError(err);
1231+
assert.strictEqual(buckets.length, 1);
1232+
assert.deepStrictEqual(buckets[0].name, 'fail-bucket');
1233+
assert.strictEqual(buckets[0].unreachable, true);
1234+
assert.deepStrictEqual(buckets[0].metadata, {});
1235+
done();
1236+
}
1237+
);
1238+
});
1239+
1240+
it('should handle API success where zero items and zero unreachable items are returned', done => {
1241+
const resp = {items: [], unreachable: []};
1242+
1243+
storage.request = (
1244+
reqOpts: DecorateRequestOptions,
1245+
callback: Function
1246+
) => {
1247+
assert.strictEqual(reqOpts.qs.returnPartialSuccess, true);
1248+
callback(null, resp);
1249+
};
1250+
1251+
storage.getBuckets(
1252+
{returnPartialSuccess: true},
1253+
(err: Error, buckets: Bucket[], nextQuery: {}, apiResponse: {}) => {
1254+
assert.ifError(err);
1255+
assert.strictEqual(buckets.length, 0);
1256+
assert.deepStrictEqual(apiResponse, resp);
1257+
done();
1258+
}
1259+
);
1260+
});
11801261
});
11811262

11821263
describe('getHmacKeys', () => {

0 commit comments

Comments
 (0)