From c3e1cdf2fc7b8c23a8de253f6e412ba3363fd733 Mon Sep 17 00:00:00 2001 From: Seb Aebischer Date: Fri, 8 Dec 2023 11:15:41 +0000 Subject: [PATCH 1/3] feat: `SQSMessageModel` checks message has required fields Allows us to remove the non-null assertions, making this model more type-safe. This is unlikely to break any existing applications as this model is intended for _received_ messages, which have all required fields. BREAKING CHANGE: `SQSMessageModel` will throw if required fields (`MessageID`, `ReceiptHandle`, `Body`) are missing from the SQS message. --- src/models/SQSMessageModel.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/models/SQSMessageModel.ts b/src/models/SQSMessageModel.ts index 58d00afe..ad27fc1f 100644 --- a/src/models/SQSMessageModel.ts +++ b/src/models/SQSMessageModel.ts @@ -15,10 +15,24 @@ export default class Message { metadata: Record = {}; constructor(message: SQS.Message) { - // todo: validate rather than assert the type - this.messageId = message.MessageId!; - this.receiptHandle = message.ReceiptHandle!; - this.body = JSON.parse(message.Body!); + if (!message.MessageId) { + throw new TypeError('Message does not have a MessageId'); + } + if (!message.ReceiptHandle) { + throw new TypeError('Message does not have a ReceiptHandle'); + } + if (!message.Body) { + throw new TypeError('Message does not have a Body'); + } + + this.messageId = message.MessageId; + this.receiptHandle = message.ReceiptHandle; + + try { + this.body = JSON.parse(message.Body); + } catch (error) { + throw new TypeError('Message body is not valid JSON'); + } } /** From febffac25bdbf2a65cb82aef9f688934db44060c Mon Sep 17 00:00:00 2001 From: Seb Aebischer Date: Fri, 8 Dec 2023 11:47:55 +0000 Subject: [PATCH 2/3] Add tests --- tests/unit/models/SQSMessageModel.spec.ts | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/unit/models/SQSMessageModel.spec.ts b/tests/unit/models/SQSMessageModel.spec.ts index 3bcd60c0..f1c8034b 100644 --- a/tests/unit/models/SQSMessageModel.spec.ts +++ b/tests/unit/models/SQSMessageModel.spec.ts @@ -1,3 +1,5 @@ +import type { SQS } from 'aws-sdk'; + import { SQSMessageModel as Message } from '@/src'; describe('unit.models.SQSMessageModel', () => { @@ -13,6 +15,38 @@ describe('unit.models.SQSMessageModel', () => { const messageModel = new Message(mockedMessage); + describe('constructor', () => { + it('should throw if message is missing MessageId', () => { + const message: SQS.Message = { ...mockedMessage }; + delete message.MessageId; + + expect(() => new Message(message)).toThrowError(TypeError); + }); + + it('should throw if message is missing ReceiptHandle', () => { + const message: SQS.Message = { ...mockedMessage }; + delete message.ReceiptHandle; + + expect(() => new Message(message)).toThrowError(TypeError); + }); + + it('should throw if message is missing Body', () => { + const message: SQS.Message = { ...mockedMessage }; + delete message.Body; + + expect(() => new Message(message)).toThrowError(TypeError); + }); + + it('should throw if message body is not valid JSON', () => { + const message: SQS.Message = { + ...mockedMessage, + Body: 'This is not JSON!', + }; + + expect(() => new Message(message)).toThrowError(TypeError); + }); + }); + describe('getMessageId', () => { it('should return the message ID', () => { expect(messageModel.getMessageId()).toEqual(mockedMessage.MessageId); From a82cbddfe9baaad0cb35649e6ad58076a3c0083f Mon Sep 17 00:00:00 2001 From: Seb Aebischer Date: Fri, 8 Dec 2023 13:07:20 +0000 Subject: [PATCH 3/3] Add note to v2 migration guide --- docs/migration/v2.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/migration/v2.md b/docs/migration/v2.md index 734f4199..56835045 100644 --- a/docs/migration/v2.md +++ b/docs/migration/v2.md @@ -158,7 +158,7 @@ The `MarketingPreference` model is removed, as this is application-specific and The `StatusModel` model has been replaced by a simple object type. See the [StatusModel](#statusmodel) section below. -Other models (`ResponseModel`, `SQSMessageModel`) are unaffected except that they no longer inherit from a common `Model` class. +Other models (`ResponseModel`, `SQSMessageModel`) are largely unaffected except that they no longer inherit from a common `Model` class. ### `StatusModel` @@ -201,3 +201,7 @@ async function checkStatus(): Promise { ``` Note that we can keep `status` unset initially, and TypeScript will complain if you forget to set it before `checkStatus` returns. + +### `SQSMessageModel` + +The model constructor will now validate that `message` has all fields required of a received SQS message. This should not break existing applications that are using this model correctly, but is included in the 2.0.0 release as a precaution.