Skip to content

Commit

Permalink
Merge pull request #21 from MarcioMeier/feat/validate-openapi-operations
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviostutz authored May 23, 2024
2 parents a3fbfc9 + e4b553d commit 0b16582
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 15 deletions.
50 changes: 35 additions & 15 deletions examples/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

101 changes: 101 additions & 0 deletions lib/src/apigateway/openapi-gateway-lambda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ describe('openapi-gateway-lambda', () => {
});
expect(rs).toMatch(/^abc123yza748rt-aaab-get.*/);
});

it('getPropsWithDefaults', async () => {
const props = getPropsWithDefaults('test-api', {
stage: 'tst',
Expand All @@ -237,6 +238,7 @@ describe('openapi-gateway-lambda', () => {
expect(props.restApiName).toBeUndefined();
expect(props.deploy).toBeTruthy();
});

it('addVPCEndpointConfig private endpoint', async () => {
const originProps = testGatewayProps();
const originDoc31 = testOpenpidoc31();
Expand Down Expand Up @@ -318,6 +320,105 @@ describe('openapi-gateway-lambda', () => {
};
expect(f).toThrow();
});

it('should throw error if no openapi operation is provided', async () => {
const app = new App();
const stack = new Stack(app);

const createRestApi = (): void => {
// eslint-disable-next-line no-new
new OpenApiGatewayLambda(stack, 'myapi', {
stage: 'tst',
openapiBasic: {
openapi: '3.0.3',
info: {
title: 'test api',
version: 'v1',
},
},
openapiOperations: [],
});
};

const expectedErrorMessage = `props.openapiOperations validation errors: ${JSON.stringify(
{
_errors: ['At least one operation is required'],
},
null,
2,
)}`;

expect(createRestApi).toThrow(new Error(expectedErrorMessage));
});

it('should throw error with invalid operations', async () => {
const app = new App();
const stack = new Stack(app);

const lambdaFunction = new BaseNodeJsFunction(stack, 'user-get-lambda', {
...defaultLambdaConfig,

// ? The lambda must have the alias
createLiveAlias: false,
});

const openapiOperations = [
{
lambdaAlias: lambdaFunction.liveAlias,
routeConfig: {
method: 'GET',
responses: testUserGetRouteConfig.responses,
deprecated: 1,
},
},
];

const createRestApi = (): void => {
// eslint-disable-next-line no-new
new OpenApiGatewayLambda(stack, 'myapi', {
stage: 'tst',
openapiBasic: {
openapi: '3.0.3',
info: {
title: 'test api',
version: 'v1',
},
},
// @ts-expect-error invalid operations
openapiOperations,
});
};

const expectedErrorMessage = `props.openapiOperations validation errors: ${JSON.stringify(
{
'0': {
_errors: [],
lambdaAlias: {
_errors: ['Required'],
},
routeConfig: {
_errors: [],
path: {
_errors: ['Required'],
},
method: {
_errors: [
"Invalid enum value. Expected 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head' | 'options' | 'trace', received 'GET'",
],
},
deprecated: {
_errors: ['Expected boolean, received number'],
},
},
},
_errors: [],
},
null,
2,
)}`;

expect(createRestApi).toThrow(new Error(expectedErrorMessage));
});
});

const testUserGetOperation = (scope: Construct, lambdaConfig: BaseNodeJsProps): LambdaOperation => {
Expand Down
12 changes: 12 additions & 0 deletions lib/src/apigateway/openapi-gateway-lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Converter } from '@apiture/openapi-down-convert';
import { lintOpenapiDocument } from '../utils/openapi-lint';
import { randomId } from '../utils/misc';

import { openapiOperationsSchema } from './schemas';
import { LambdaOperation, OpenApiGatewayLambdaProps } from './types';

/**
Expand All @@ -43,6 +44,8 @@ export class OpenApiGatewayLambda extends Construct {
constructor(scope: Construct, id: string, props: OpenApiGatewayLambdaProps) {
super(scope, id);

validateOpenapiOperations(props.openapiOperations);

const { region: awsRegion } = new ScopedAws(scope);

const propsWithDefaults = getPropsWithDefaults(id, props);
Expand Down Expand Up @@ -100,6 +103,15 @@ export class OpenApiGatewayLambda extends Construct {
}
}

const validateOpenapiOperations = (operations: LambdaOperation[]): void => {
const result = openapiOperationsSchema.safeParse(operations);

if (!result.success) {
const errorMessage = JSON.stringify(result.error.format(), null, 2);
throw new Error(`props.openapiOperations validation errors: ${errorMessage}`);
}
};

const addGatewayToLambdaPermissions = (
scope: Construct,
lambdaOperations: LambdaOperation[],
Expand Down
21 changes: 21 additions & 0 deletions lib/src/apigateway/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import z from 'zod';

const openapiOpearationSchema = z.object({
lambdaAlias: z.object({
aliasName: z.string(),
version: z.object({
version: z.string(),
functionArn: z.string(),
functionName: z.string(),
}),
}),
routeConfig: z.object({
path: z.string(),
method: z.enum(['get', 'post', 'put', 'delete', 'patch', 'head', 'options', 'trace']),
deprecated: z.boolean().optional(),
}),
});

export const openapiOperationsSchema = z
.array(openapiOpearationSchema)
.min(1, { message: 'At least one operation is required' });

0 comments on commit 0b16582

Please sign in to comment.