diff --git a/libs/wingsdk/src/shared-aws/bucket.inflight.ts b/libs/wingsdk/src/shared-aws/bucket.inflight.ts index 5381d7eb506..29238f2b7c8 100644 --- a/libs/wingsdk/src/shared-aws/bucket.inflight.ts +++ b/libs/wingsdk/src/shared-aws/bucket.inflight.ts @@ -12,6 +12,7 @@ import { GetPublicAccessBlockCommandOutput, S3Client, GetObjectOutput, + NoSuchKey, } from "@aws-sdk/client-s3"; import { BucketDeleteOptions, IBucketClient } from "../cloud"; import { Duration, Json } from "../std"; @@ -70,34 +71,46 @@ export class BucketClient implements IBucketClient { } /** - * Get an object from the bucket - * - * @param key Key of the object - * @returns content of the object + * See https://github.com/aws/aws-sdk-js-v3/issues/1877 */ - public async get(key: string): Promise { - // See https://github.com/aws/aws-sdk-js-v3/issues/1877 + private async getObjectContent(key: string): Promise { const command = new GetObjectCommand({ Bucket: this.bucketName, Key: key, }); - let resp: GetObjectOutput; + try { - resp = await this.s3Client.send(command); + const resp: GetObjectOutput = await this.s3Client.send(command); + const objectContent = resp.Body as Readable; + try { + return await consumers.text(objectContent); + } catch (e) { + throw new Error( + `Object content could not be read as text (key=${key}): ${ + (e as Error).stack + })}` + ); + } } catch (e) { - throw new Error( - `Object does not exist (key=${key}): ${(e as Error).stack}` - ); + if (e instanceof NoSuchKey) { + return undefined; + } + throw new Error((e as Error).stack); } - try { - return await consumers.text(resp.Body as Readable); - } catch (e) { - throw new Error( - `Object contents could not be read as text (key=${key}): ${ - (e as Error).stack - })}` - ); + } + + /** + * Get an object from the bucket + * + * @param key Key of the object + * @returns content of the object + */ + public async get(key: string): Promise { + const objectContent = await this.getObjectContent(key); + if (objectContent !== undefined) { + return objectContent; } + throw new Error(`Object does not exist (key=${key}).`); } /** @@ -107,11 +120,7 @@ export class BucketClient implements IBucketClient { * @returns content of the object */ public async tryGet(key: string): Promise { - if (await this.exists(key)) { - return this.get(key); - } - - return undefined; + return this.getObjectContent(key); } /** @@ -131,11 +140,12 @@ export class BucketClient implements IBucketClient { * @returns Json content of the object */ public async tryGetJson(key: string): Promise { - if (await this.exists(key)) { - return this.getJson(key); + const objectContent = await this.tryGet(key); + if (objectContent !== undefined) { + return JSON.parse(objectContent); + } else { + return undefined; } - - return undefined; } /** @@ -158,13 +168,12 @@ export class BucketClient implements IBucketClient { try { await this.s3Client.send(command); - } catch (er) { - const error = er as any; - if (!mustExist && error.name === "NoSuchKey") { + } catch (e: any) { + if (!mustExist && e instanceof NoSuchKey) { return; } - throw new Error(`Unable to delete "${key}": ${error.message}`); + throw new Error(`Unable to delete "${key}": ${e.message}`); } } diff --git a/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts b/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts index aa27ea405f7..5d1bd4402ae 100644 --- a/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts +++ b/libs/wingsdk/test/shared-aws/bucket.inflight.test.ts @@ -8,6 +8,7 @@ import { ListObjectsV2Command, PutObjectCommand, S3Client, + NoSuchKey, } from "@aws-sdk/client-s3"; import { SdkStream } from "@aws-sdk/types"; import { sdkStreamMixin } from "@aws-sdk/util-stream-node"; @@ -93,7 +94,7 @@ test("get a non-existent object from the bucket", async () => { const VALUE = "VALUE"; s3Mock .on(GetObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }) - .rejects(new Error("fake error")); + .rejects(new Error("Object does not exist")); // WHEN const client = new BucketClient(BUCKET_NAME); @@ -372,7 +373,7 @@ test("tryGet a non-existent object from the bucket", async () => { const VALUE = "VALUE"; s3Mock .on(GetObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }) - .rejects(new Error("fake error")); + .rejects(new NoSuchKey({ message: "NoSuchKey error", $metadata: {} })); s3Mock .on(HeadObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }) .rejects({ name: "NotFound" }); @@ -385,6 +386,23 @@ test("tryGet a non-existent object from the bucket", async () => { expect(objectTryGet).toEqual(undefined); }); +test("tryGet object from the bucket throws an unknown error", async () => { + // GIVEN + const BUCKET_NAME = "BUCKET_NAME"; + const KEY = "KEY"; + const VALUE = "VALUE"; + s3Mock + .on(GetObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }) + .rejects(new Error("unknown error")); + s3Mock.on(HeadObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }).resolves({}); + + // WHEN + const client = new BucketClient(BUCKET_NAME); + + // THEN + await expect(() => client.tryGet(KEY)).rejects.toThrowError(/unknown error/); +}); + test("tryGetJson an existing object from the bucket", async () => { // GIVEN const BUCKET_NAME = "BUCKET_NAME"; @@ -418,7 +436,7 @@ test("tryGetJson a non-existent object from the bucket", async () => { const VALUE = { msg: "Hello, World!" }; s3Mock .on(GetObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }) - .rejects(new Error("fake error")); + .rejects(new NoSuchKey({ message: "NoSuchKey error", $metadata: {} })); s3Mock .on(HeadObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }) .rejects({ name: "NotFound" }); @@ -431,6 +449,23 @@ test("tryGetJson a non-existent object from the bucket", async () => { expect(objectTryGetJson).toEqual(undefined); }); +test("tryGetJson object from the bucket throws an unknown error", async () => { + // GIVEN + const BUCKET_NAME = "BUCKET_NAME"; + const KEY = "KEY"; + const VALUE = { msg: "Hello, World!" }; + s3Mock + .on(GetObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }) + .rejects(new Error("unknown error")); + s3Mock.on(HeadObjectCommand, { Bucket: BUCKET_NAME, Key: KEY }).resolves({}); + + // WHEN + const client = new BucketClient(BUCKET_NAME); + + // THEN + await expect(() => client.tryGet(KEY)).rejects.toThrowError(/unknown error/); +}); + test("tryGetJson an existing non-Json object from the bucket", async () => { // GIVEN const BUCKET_NAME = "BUCKET_NAME";