Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
| 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) |
Expand Down
18 changes: 18 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ objects to users via direct download.
* [Get HMAC SA Key Metadata.](#get-hmac-sa-key-metadata.)
* [List HMAC SA Keys Metadata.](#list-hmac-sa-keys-metadata.)
* [List Buckets](#list-buckets)
* [List Buckets Partial Success](#list-buckets-partial-success)
* [List Files](#list-files)
* [List Files By Prefix](#list-files-by-prefix)
* [List Files Paginate](#list-files-paginate)
Expand Down Expand Up @@ -1421,6 +1422,23 @@ __Usage:__



### List Buckets Partial Success

View the [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)

__Usage:__


`node samples/listBucketsPartialSuccess.js`


-----




### List Files

View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/listFiles.js).
Expand Down
57 changes: 57 additions & 0 deletions samples/listBucketsPartialSuccess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Copyright 2025 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.
*/

function main() {
// [START storage_list_buckets_partial_success]
// Imports the Google Cloud client library
const {Storage} = require('@google-cloud/storage');

// Creates a client
const storage = new Storage();

async function listBucketsPartialSuccess() {
const option = {
returnPartialSuccess: true,
maxResults: 5,
};
const [buckets, nextQuery, apiResponse] = await storage.getBuckets(option);

if (nextQuery && nextQuery.pageToken) {
console.log(`Next Page Token: ${nextQuery.pageToken}`);
}

console.log('\nBuckets:');
buckets.forEach(bucket => {
if (bucket.unreachable) {
console.log(`${bucket.name} (unreachable: ${bucket.unreachable})`);
} else {
console.log(`${bucket.name}`);
}
});

if (apiResponse.unreachable && apiResponse.unreachable.length > 0) {
console.log('\nUnreachable Buckets:');
apiResponse.unreachable.forEach(item => {
console.log(item);
});
}
}

listBucketsPartialSuccess().catch(console.error);
// [END storage_list_buckets_partial_success]
}

main(...process.argv.slice(2));
7 changes: 7 additions & 0 deletions src/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,13 @@ class Bucket extends ServiceObject<Bucket, BucketMetadata> {
private instanceRetryValue?: boolean;
instancePreconditionOpts?: PreconditionOptions;

/**
* Indicates whether this Bucket object is a placeholder for an item
* that the API failed to retrieve (unreachable) due to partial failure.
* Consumers must check this flag before accessing other properties.
*/
unreachable = false;

constructor(storage: Storage, name: string, options?: BucketOptions) {
options = options || {};

Expand Down
24 changes: 23 additions & 1 deletion src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export interface GetBucketsRequest {
userProject?: string;
softDeleted?: boolean;
generation?: number;
returnPartialSuccess?: boolean;
}

export interface HmacKeyResourceResponse {
Expand Down Expand Up @@ -1338,17 +1339,38 @@ export class Storage extends Service {
}

const itemsArray = resp.items ? resp.items : [];
const unreachableArray = resp.unreachable ? resp.unreachable : [];

const buckets = itemsArray.map((bucket: BucketMetadata) => {
const bucketInstance = this.bucket(bucket.id!);
bucketInstance.metadata = bucket;

return bucketInstance;
});

let results: Bucket[] = buckets;
if (unreachableArray.length > 0) {
results = unreachableArray.reduce(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oversight on my suggestion.

Suggested change
results = unreachableArray.reduce(
unreachableArray.forEach(fullPath => {
const name = fullPath.split('/').pop();
const placeholder = this.bucket(name);
placeholder.unreachable = true;
placeholder.metadata = {};
buckets.push(placeholder);
});

(acc: Bucket[], fullPath: string) => {
const name = fullPath.split('/').pop();
if (name) {
const placeholder = this.bucket(name);
placeholder.unreachable = true;
placeholder.metadata = {};
acc.push(placeholder);
}

return acc;
},
buckets
);
}

const nextQuery = resp.nextPageToken
? Object.assign({}, options, {pageToken: resp.nextPageToken})
: null;

callback(null, buckets, nextQuery, resp);
callback(null, results, nextQuery, resp);
}
);
}
Expand Down
82 changes: 82 additions & 0 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,88 @@ describe('Storage', () => {
done();
});
});

it('should return unreachable when returnPartialSuccess is true', done => {
const unreachableList = ['projects/_/buckets/fail-bucket'];
const itemsList = [{id: 'fake-bucket-name'}];
const resp = {items: itemsList, unreachable: unreachableList};

storage.request = (
reqOpts: DecorateRequestOptions,
callback: Function
) => {
assert.strictEqual(reqOpts.qs.returnPartialSuccess, true);
callback(null, resp);
};

storage.getBuckets(
{returnPartialSuccess: true},
(err: Error, buckets: Bucket[], nextQuery: {}, apiResponse: {}) => {
assert.ifError(err);
assert.strictEqual(buckets.length, 2);

const reachableBucket = buckets.find(
b => b.name === 'fake-bucket-name'
);
assert.ok(reachableBucket);
assert.strictEqual(reachableBucket.unreachable, false);

const unreachableBucket = buckets.find(b => b.name === 'fail-bucket');
assert.ok(unreachableBucket);
assert.strictEqual(unreachableBucket.unreachable, true);
assert.deepStrictEqual(apiResponse, resp);
done();
}
);
});

it('should handle partial failure with zero reachable buckets', done => {
const unreachableList = ['projects/_/buckets/fail-bucket'];
const resp = {items: [], unreachable: unreachableList};

storage.request = (
reqOpts: DecorateRequestOptions,
callback: Function
) => {
callback(null, resp);
};

storage.getBuckets(
{returnPartialSuccess: true},
(err: Error, buckets: Bucket[]) => {
assert.ifError(err);
assert.strictEqual(buckets.length, 1);
assert.deepStrictEqual(buckets[0].name, 'fail-bucket');
assert.strictEqual(buckets[0].unreachable, true);
assert.deepStrictEqual(buckets[0].metadata, {});
done();
}
);
});

it('should handle API success where zero items and zero unreachable items are returned', done => {
const resp = {items: [], unreachable: []};

storage.request = (
reqOpts: DecorateRequestOptions,
callback: Function
) => {
if (reqOpts.qs.returnPartialSuccess !== undefined) {
assert.strictEqual(reqOpts.qs.returnPartialSuccess, true);
}
callback(null, resp);
};

storage.getBuckets(
{returnPartialSuccess: true},
(err: Error, buckets: Bucket[], nextQuery: {}, apiResponse: {}) => {
assert.ifError(err);
assert.strictEqual(buckets.length, 0);
assert.deepStrictEqual(apiResponse, resp);
done();
}
);
});
});

describe('getHmacKeys', () => {
Expand Down
Loading