Skip to content

Commit

Permalink
feat: centralize sqsEventAdapter in spacecat-shared-utils (#128)
Browse files Browse the repository at this point in the history
Centralizes the `sqsEventAdapter` function in the `spacecat-shared-utils` package.
  • Loading branch information
blefebvre authored Feb 5, 2024
1 parent 5c80613 commit 52ec442
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 0 deletions.
17 changes: 17 additions & 0 deletions packages/spacecat-shared-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ The library includes the following utility functions:
- `hasText(str)`: Checks if the given string is not empty.
- `dateAfterDays(number)`: Calculates the date after a specified number of days from the current date.

## SQS Event Adapter

The library also includes an SQS event adapter to convert an SQS record into a function parameter. This is useful when working with AWS Lambda functions that are triggered by an SQS event. Usage:

```javascript
import { sqsEventAdapter } from '@adobe/spacecat-shared-utils';

// ...

export const main = wrap(run)
.with(dataAccess)
.with(sqsEventAdapter) // Add this line
.with(sqs)
.with(secrets)
.with(helixStatus);
````

## Testing

This library includes a comprehensive test suite to ensure the reliability of the utility functions. To run the tests, use the following command:
Expand Down
3 changes: 3 additions & 0 deletions packages/spacecat-shared-utils/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ export function toBoolean(value: unknown): boolean;
export function isValidUrl(urlString: string): boolean;

export function dateAfterDays(days: string): Date;

export function sqsEventAdapter(fn: (message: object, context: object) => Promise<Response>):
(request: object, context: object) => Promise<Response>;
2 changes: 2 additions & 0 deletions packages/spacecat-shared-utils/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ export {
} from './functions.js';

export { resolveSecretsName } from './helpers.js';

export { sqsEventAdapter } from './sqs.js';
42 changes: 42 additions & 0 deletions packages/spacecat-shared-utils/src/sqs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/**
* Wrapper to turn an SQS record into a function param
* Inspired by https://github.com/adobe/helix-admin/blob/main/src/index.js#L108-L133
*
* @param {UniversalAction} fn
* @returns {function(object, UniversalContext): Promise<Response>}
*/
export function sqsEventAdapter(fn) {
return async (req, context) => {
const { log } = context;
let message;

try {
// currently not publishing batch messages
const records = context.invocation?.event?.Records;
log.info(`Received ${records.length} many records. ID of the first message in the batch: ${records[0]?.messageId}`);
message = JSON.parse(records[0]?.body);
log.info(`Received message with id: ${context.invocation?.event?.Records.length}`);
} catch (e) {
log.error('Function was not invoked properly, message body is not a valid JSON', e);
return new Response('', {
status: 400,
headers: {
'x-error': 'Event does not contain a valid message body',
},
});
}
return fn(message, context);
};
}
1 change: 1 addition & 0 deletions packages/spacecat-shared-utils/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe('Index Exports', () => {
'isValidUrl',
'resolveSecretsName',
'dateAfterDays',
'sqsEventAdapter',
];

it('exports all expected functions', () => {
Expand Down
65 changes: 65 additions & 0 deletions packages/spacecat-shared-utils/test/sqs.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you 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 REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

/* eslint-env mocha */

import { expect } from 'chai';
import sinon from 'sinon';

import { sqsEventAdapter } from '../src/sqs.js';

const exampleHandler = sinon.spy(async (message, context) => {
const { log } = context;
const messageStr = JSON.stringify(message);
log.info(`Handling message ${messageStr}`);
return new Response(messageStr);
});

describe('SQS helpers', () => {
const emptyRequest = {};

it('should handle an invalid context with no records', async () => {
const contextNoRecords = {
log: console,
};

const handler = sqsEventAdapter(exampleHandler);
const response = await handler(emptyRequest, contextNoRecords);

expect(response.status).to.equal(400);
expect(response.headers.get('x-error')).to.equal('Event does not contain a valid message body');
});

it('should handle a valid context with an event record', async () => {
const context = {
log: console,
invocation: {
event: {
Records: [
{
body: JSON.stringify({ id: '1234567890' }),
messageId: 'abcd',
},
],
},
},
};

const handler = sqsEventAdapter(exampleHandler);
const response = await handler(emptyRequest, context);

expect(response.status).to.equal(200);
const result = await response.json();
expect(result.id).to.equal('1234567890');
expect(exampleHandler.calledWith({ id: '1234567890' })).to.be.true;
});
});

0 comments on commit 52ec442

Please sign in to comment.