diff --git a/lib/src/apigateway/openapi-gateway-lambda.test.ts b/lib/src/apigateway/openapi-gateway-lambda.test.ts index d07d96e..5637375 100644 --- a/lib/src/apigateway/openapi-gateway-lambda.test.ts +++ b/lib/src/apigateway/openapi-gateway-lambda.test.ts @@ -9,9 +9,14 @@ import { import z from 'zod'; import type { oas30, oas31 } from 'openapi3-ts'; import { Converter } from '@apiture/openapi-down-convert'; -import { EndpointType } from 'aws-cdk-lib/aws-apigateway'; +import { + AccessLogFormat, + EndpointType, + LogGroupLogDestination, + MethodLoggingLevel, +} from 'aws-cdk-lib/aws-apigateway'; import { RetentionDays } from 'aws-cdk-lib/aws-logs'; -import { App, Stack } from 'aws-cdk-lib/core'; +import { App, Duration, Stack } from 'aws-cdk-lib/core'; import { Template } from 'aws-cdk-lib/assertions'; import { Construct } from 'constructs'; import { CfnFunction } from 'aws-cdk-lib/aws-lambda'; @@ -27,6 +32,7 @@ import { generateOpenapiDocWithExtensions, generateOperationId, getPropsWithDefaults, + addLogGroupForTracing, } from './openapi-gateway-lambda'; extendZodWithOpenApi(z); @@ -475,6 +481,127 @@ describe('openapi-gateway-lambda', () => { Principal: 'apigateway.amazonaws.com', }); }); + + it('should correctly construct deployOptionsAccessLog without provided deploy options', () => { + const stack = new Stack(); + const props: OpenApiGatewayLambdaProps = { + stage: 'dev', + openapiBasic: { + openapi: '3.0.3', + info: { + title: 'test api', + version: 'v1', + }, + }, + openapiOperations: [], + accessLogEnable: true, + }; + + const { deployOptions, logGroupAccessLog } = addLogGroupForTracing(stack, props); + + if (!logGroupAccessLog) { + throw new Error('logGroupAccessLog should be defined'); + } + expect(deployOptions.accessLogDestination).toStrictEqual( + new LogGroupLogDestination(logGroupAccessLog), + ); + expect(deployOptions.accessLogFormat).toStrictEqual(AccessLogFormat.jsonWithStandardFields()); + expect(deployOptions.loggingLevel).toBe(MethodLoggingLevel.INFO); + expect(deployOptions.dataTraceEnabled).toBe(false); + expect(deployOptions.tracingEnabled).toBe(true); + expect(deployOptions.metricsEnabled).toBe(true); + expect(logGroupAccessLog).toStrictEqual(logGroupAccessLog); + }); + + it('should correctly construct deployOptionsAccessLog with provided deployOptions', () => { + const stack = new Stack(); + const props: OpenApiGatewayLambdaProps = { + stage: 'dev', + openapiBasic: { + openapi: '3.0.3', + info: { + title: 'test api', + version: 'v1', + }, + }, + openapiOperations: [], + accessLogEnable: true, + deployOptions: { + loggingLevel: MethodLoggingLevel.ERROR, + dataTraceEnabled: true, + stageName: 'dev', + cachingEnabled: true, + methodOptions: { + '/messages/GET': { + cachingEnabled: true, + cacheTtl: Duration.seconds(3600), + }, + }, + }, + }; + + const { deployOptions, logGroupAccessLog } = addLogGroupForTracing(stack, props); + + if (!logGroupAccessLog) { + throw new Error('logGroupAccessLog should be defined'); + } + expect(deployOptions.accessLogDestination).toStrictEqual( + new LogGroupLogDestination(logGroupAccessLog), + ); + expect(deployOptions.accessLogFormat).toStrictEqual(AccessLogFormat.jsonWithStandardFields()); + expect(deployOptions.loggingLevel).toBe(MethodLoggingLevel.ERROR); + expect(deployOptions.dataTraceEnabled).toBe(true); + expect(deployOptions.tracingEnabled).toBe(true); + expect(deployOptions.metricsEnabled).toBe(true); + expect(deployOptions.stageName).toBe('dev'); + expect(deployOptions.cachingEnabled).toBe(true); + expect(deployOptions.methodOptions).toStrictEqual({ + '/messages/GET': { + cachingEnabled: true, + cacheTtl: Duration.seconds(3600), + }, + }); + expect(logGroupAccessLog).toStrictEqual(logGroupAccessLog); + }); + + it('should correctly merge lambdaOp.integrationOptions into x-amazon-apigateway-integration', () => { + const awsRegion = 'eu-west-1'; + const lambdaArn = 'arn:aws:lambda:eu-west-1:123123123:function:my-function:live'; + const gatewayProps = { + ...testGatewayProps(), + openapiOperations: [ + { + ...testGatewayProps().openapiOperations[0], + integrationOptions: { + cacheKeyParameters: [ + 'method.request.querystring.portal', + 'method.request.querystring.active', + ], + requestParameters: { + 'integration.request.querystring.portal': 'method.request.querystring.portal', + 'integration.request.querystring.active': 'method.request.querystring.active', + }, + }, + }, + ], + }; + const openapidoc = generateOpenapiDocWithExtensions(gatewayProps, awsRegion); + + // @ts-ignore + expect(openapidoc.paths['/users/{id}'].get['x-amazon-apigateway-integration']).toStrictEqual({ + uri: `arn:aws:apigateway:${awsRegion}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations`, + httpMethod: 'POST', + type: 'AWS_PROXY', + cacheKeyParameters: [ + 'method.request.querystring.portal', + 'method.request.querystring.active', + ], + requestParameters: { + 'integration.request.querystring.portal': 'method.request.querystring.portal', + 'integration.request.querystring.active': 'method.request.querystring.active', + }, + }); + }); }); const testUserGetOperation = (scope: Construct, lambdaConfig: BaseNodeJsProps): LambdaOperation => { diff --git a/lib/src/apigateway/openapi-gateway-lambda.ts b/lib/src/apigateway/openapi-gateway-lambda.ts index e404267..ca09042 100644 --- a/lib/src/apigateway/openapi-gateway-lambda.ts +++ b/lib/src/apigateway/openapi-gateway-lambda.ts @@ -166,14 +166,10 @@ const getRestApiExecutionArn = (scope: Construct, specRestApi: SpecRestApi): str /** * Setup log groups for access log */ -const addLogGroupForTracing = ( +export const addLogGroupForTracing = ( scope: Construct, props: OpenApiGatewayLambdaProps, ): { deployOptions: StageOptions; logGroupAccessLog?: LogGroup } => { - if (props.accessLogEnable && props.deployOptions) { - throw new Error("When 'accessLogEnable' is defined, 'deployOptions' cannot be defined"); - } - if (props.accessLogEnable) { const logGroupAccessLog = new LogGroup(scope, 'AccessLogGroup', { logGroupName: `apigateway-accesslogs-${scope.node.id}`, @@ -182,11 +178,13 @@ const addLogGroupForTracing = ( const deployOptionsAccessLog: StageOptions = { ...props.deployOptions, - accessLogDestination: new LogGroupLogDestination(logGroupAccessLog), - accessLogFormat: AccessLogFormat.jsonWithStandardFields(), - loggingLevel: MethodLoggingLevel.INFO, - dataTraceEnabled: false, - tracingEnabled: true, + accessLogDestination: + props.deployOptions?.accessLogDestination ?? new LogGroupLogDestination(logGroupAccessLog), + accessLogFormat: + props.deployOptions?.accessLogFormat ?? AccessLogFormat.jsonWithStandardFields(), + loggingLevel: props.deployOptions?.loggingLevel ?? MethodLoggingLevel.INFO, + dataTraceEnabled: props.deployOptions?.dataTraceEnabled ?? false, + tracingEnabled: props.deployOptions?.tracingEnabled ?? true, metricsEnabled: props.deployOptions?.metricsEnabled ?? true, }; @@ -307,6 +305,7 @@ export const generateOpenapiDocWithExtensions = ( // https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions-integration.html // @ts-ignore jsonObj['x-amazon-apigateway-integration'] = { + ...lambdaOp.integrationOptions, // We need to use a api gw lambda invocation function to invocate the lambda we are integrating // https://stackoverflow.com/a/50696321 uri: `arn:aws:apigateway:${awsRegion}:lambda:path/2015-03-31/functions/${lambdaOp.lambdaAlias.functionArn}/invocations`, diff --git a/lib/src/apigateway/types.ts b/lib/src/apigateway/types.ts index a594529..86eaf95 100644 --- a/lib/src/apigateway/types.ts +++ b/lib/src/apigateway/types.ts @@ -1,4 +1,4 @@ -import { EndpointType, SpecRestApiProps } from 'aws-cdk-lib/aws-apigateway'; +import { EndpointType, IntegrationOptions, SpecRestApiProps } from 'aws-cdk-lib/aws-apigateway'; import { RetentionDays } from 'aws-cdk-lib/aws-logs'; import { RouteConfig } from '@asteasolutions/zod-to-openapi'; import { IAlias } from 'aws-cdk-lib/aws-lambda'; @@ -46,4 +46,5 @@ export type LambdaOperation = { routeConfig: RouteConfig; lambdaAlias: LambdaAlias; awsExtensions?: AwsExtensions; + integrationOptions?: IntegrationOptions; };