Skip to content

Commit

Permalink
Merge pull request #72 from H4ad/feature/batch-support
Browse files Browse the repository at this point in the history
feature: batch support
  • Loading branch information
H4ad authored Dec 20, 2022
2 parents 066ae2d + 6edb996 commit 7ab7087
Show file tree
Hide file tree
Showing 21 changed files with 673 additions and 567 deletions.
179 changes: 179 additions & 0 deletions src/adapters/aws/base/aws-simple-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//#region Imports

import type { Context, SQSBatchItemFailure } from 'aws-lambda';
import {
AdapterContract,
AdapterRequest,
GetResponseAdapterProps,
OnErrorProps,
} from '../../../contracts/index';
import {
EmptyResponse,
IEmptyResponse,
getEventBodyAsBuffer,
} from '../../../core/index';

//#endregion

/**
* The options to customize the {@link AwsSimpleAdapter}
*
* @breadcrumb Adapters / AWS / AWS Simple Adapter
* @public
*/
export interface AWSSimpleAdapterOptions {
/**
* The path that will be used to create a request to be forwarded to the framework.
*/
forwardPath: string;

/**
* The http method that will be used to create a request to be forwarded to the framework.
*/
forwardMethod: string;

/**
* The AWS Service host that will be injected inside headers to developer being able to validate if request originate from the library.
*/
host: string;

/**
* Tells if this adapter should support batch item failures.
*/
batch?: true | false;
}

/**
* The batch item failure response expected from the API server
*
* @breadcrumb Adapters / AWS / AWS Simple Adapter
* @public
*/
export type BatchItemFailureResponse = SQSBatchItemFailure;

/**
* The possible options of response for {@link AwsSimpleAdapter}
*
* @breadcrumb Adapters / AWS / AWS Simple Adapter
* @public
*/
export type AWSSimpleAdapterResponseType =
| BatchItemFailureResponse
| IEmptyResponse;

/**
* The abstract adapter to use to implement other simple AWS adapters
*
* @breadcrumb Adapters / AWS / AWS Simple Adapter
* @public
*/
export abstract class AwsSimpleAdapter<TEvent>
implements AdapterContract<TEvent, Context, AWSSimpleAdapterResponseType>
{
//#region Constructor

/**
* Default constructor
*
* @param options - The options to customize the {@link AwsSimpleAdapter}
*/
constructor(protected readonly options: AWSSimpleAdapterOptions) {}

//#endregion

//#region Public Methods

/**
* {@inheritDoc}
*/
public getAdapterName(): string {
throw new Error('not implemented.');
}

/**
* {@inheritDoc}
*/
public canHandle(event: unknown): event is TEvent {
throw new Error('not implemented.');
}

/**
* {@inheritDoc}
*/
public getRequest(event: TEvent): AdapterRequest {
const path = this.options.forwardPath;
const method = this.options.forwardMethod;

const [body, contentLength] = getEventBodyAsBuffer(
JSON.stringify(event),
false,
);

const headers = {
host: this.options.host,
'content-type': 'application/json',
'content-length': String(contentLength),
};

return {
method,
headers,
body,
path,
};
}

/**
* {@inheritDoc}
*/
public getResponse({
body,
headers,
isBase64Encoded,
event,
statusCode,
}: GetResponseAdapterProps<TEvent>): AWSSimpleAdapterResponseType {
if (this.hasInvalidStatusCode(statusCode)) {
throw new Error(
JSON.stringify({ body, headers, isBase64Encoded, event, statusCode }),
);
}

if (!this.options.batch) return EmptyResponse;

if (isBase64Encoded) {
throw new Error(
'SERVERLESS_ADAPTER: The response could not be base64 encoded when you set batch: true, the response should be a JSON.',
);
}

if (!body) return EmptyResponse;

return JSON.parse(body);
}

/**
* {@inheritDoc}
*/
public onErrorWhileForwarding({
error,
delegatedResolver,
}: OnErrorProps<TEvent, AWSSimpleAdapterResponseType>): void {
delegatedResolver.fail(error);
}

//#endregion

//#region Protected Methods

/**
* Check if the status code is invalid
*
* @param statusCode - The status code
*/
protected hasInvalidStatusCode(statusCode: number): boolean {
return statusCode < 200 || statusCode >= 400;
}

//#endregion
}
1 change: 1 addition & 0 deletions src/adapters/aws/base/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './aws-simple-adapter';
82 changes: 20 additions & 62 deletions src/adapters/aws/dynamodb.adapter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
//#region Imports

import type { Context, DynamoDBStreamEvent } from 'aws-lambda';
import { AdapterContract, AdapterRequest, OnErrorProps } from '../../contracts';
import {
EmptyResponse,
IEmptyResponse,
getDefaultIfUndefined,
getEventBodyAsBuffer,
} from '../../core';
import type { DynamoDBStreamEvent } from 'aws-lambda';
import { getDefaultIfUndefined } from '../../core';
import { AWSSimpleAdapterOptions, AwsSimpleAdapter } from './base/index';

//#endregion

Expand All @@ -17,7 +12,8 @@ import {
* @breadcrumb Adapters / AWS / DynamoDBAdapter
* @public
*/
export interface DynamoDBAdapterOptions {
export interface DynamoDBAdapterOptions
extends Pick<AWSSimpleAdapterOptions, 'batch'> {
/**
* The path that will be used to create a request to be forwarded to the framework.
*
Expand Down Expand Up @@ -50,17 +46,28 @@ export interface DynamoDBAdapterOptions {
* @breadcrumb Adapters / AWS / DynamoDBAdapter
* @public
*/
export class DynamoDBAdapter
implements AdapterContract<DynamoDBStreamEvent, Context, IEmptyResponse>
{
export class DynamoDBAdapter extends AwsSimpleAdapter<DynamoDBStreamEvent> {
//#region Constructor

/**
* Default constructor
*
* @param options - The options to customize the {@link DynamoDBAdapter}
*/
constructor(protected readonly options?: DynamoDBAdapterOptions) {}
constructor(options?: DynamoDBAdapterOptions) {
super({
forwardPath: getDefaultIfUndefined(
options?.dynamoDBForwardPath,
'/dynamo',
),
forwardMethod: getDefaultIfUndefined(
options?.dynamoDBForwardMethod,
'POST',
),
batch: options?.batch,
host: 'dynamodb.amazonaws.com',
});
}

//#endregion

Expand All @@ -86,54 +93,5 @@ export class DynamoDBAdapter
return eventSource === 'aws:dynamodb';
}

/**
* {@inheritDoc}
*/
public getRequest(event: DynamoDBStreamEvent): AdapterRequest {
const path = getDefaultIfUndefined(
this.options?.dynamoDBForwardPath,
'/dynamo',
);
const method = getDefaultIfUndefined(
this.options?.dynamoDBForwardMethod,
'POST',
);

const [body, contentLength] = getEventBodyAsBuffer(
JSON.stringify(event),
false,
);

const headers = {
host: 'dynamodb.amazonaws.com',
'content-type': 'application/json',
'content-length': String(contentLength),
};

return {
method,
headers,
body,
path,
};
}

/**
* {@inheritDoc}
*/
public getResponse(): IEmptyResponse {
return EmptyResponse;
}

/**
* {@inheritDoc}
*/
public onErrorWhileForwarding({
error,
delegatedResolver,
}: OnErrorProps<DynamoDBStreamEvent, IEmptyResponse>): void {
delegatedResolver.fail(error);
}

//#endregion
}
Loading

0 comments on commit 7ab7087

Please sign in to comment.