@carlsberg/openapi-aws-extensions
Version:
A library that adds API Gateway extensions to OpenAPI specification files
159 lines (134 loc) • 4.13 kB
text/typescript
import {
ContentHandling,
IntegrationType,
PassthroughBehavior
} from '@aws-cdk/aws-apigateway'
import fs from 'fs'
import {METHODS} from 'http'
import {
OpenApiBuilder,
OpenAPIObject,
OperationObject,
PathItemObject
} from 'openapi3-ts'
import path from 'path'
import * as yaml from 'yaml'
export interface Options {
lambdaFunctionName?: string
outputFile?: string
outputFormat: 'json' | 'yaml'
cors?: {timeout: number}
}
function specFromJSON(str: string): OpenApiBuilder {
return new OpenApiBuilder(JSON.parse(str))
}
function specFromYaml(str: string): OpenApiBuilder {
return new OpenApiBuilder(yaml.parse(str))
}
function extensionForOperation(
method: string,
op: OperationObject,
options: Options
): object {
if (method === 'options') {
if (!options.cors) {
return {}
}
return {
type: IntegrationType.MOCK,
passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
timeoutInMillis: options.cors.timeout,
requestTemplates: {'application/json': '{"statusCode": 200}'},
responses: {
default: {
statusCode: 200,
responseParameters: {
'method.response.header.Access-Control-Allow-Methods':
"'GET,OPTIONS,POST'",
'method.response.header.Access-Control-Allow-Headers':
"'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
'method.response.header.Access-Control-Allow-Origin': "'*'"
}
}
}
}
}
return {
type: IntegrationType.AWS_PROXY,
httpMethod: 'POST',
uri: `arn:\${AWS::Partition}:apigateway:\${AWS::Region}:lambda:path/2015-03-31/functions/arn:\${AWS::Partition}:lambda:\${AWS::Region}:\${AWS::AccountId}:function:${options.lambdaFunctionName}/invocations`,
passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
contentHandling: ContentHandling.CONVERT_TO_TEXT,
responses: Object.keys(op.responses).reduce((responses, statusCode) => {
// const response: ResponseObject = op.responses[statusCode]
return {...responses, [statusCode]: {statusCode}}
}, {})
}
}
function addExtensionsToPath(
pathItem: PathItemObject,
options: Options
): PathItemObject {
for (const method of METHODS.map(m => m.toLowerCase())) {
const operation: OperationObject = pathItem[method]
if (!operation) {
continue
}
const extension = extensionForOperation(method, operation, options)
pathItem[method] = extension
? {
...operation,
'x-amazon-apigateway-integration': extension
}
: operation
}
return pathItem
}
export function addExtensions(
input: string | OpenAPIObject,
options: Options = {outputFormat: 'yaml'}
): string {
if (!options.lambdaFunctionName) {
throw new Error(
`openapi-aws-extensions currently only supports Lambda integrations. The \`lambdaFunctionName\` option is required.`
)
}
let builder: OpenApiBuilder
if (typeof input === 'string') {
if (fs.existsSync(input) === false) {
throw new Error(`No specification file at path: ${input}`)
}
const ext = path.extname(input).replace('.', '')
const fileContents = fs.readFileSync(input, 'utf-8')
switch (ext) {
case 'json':
builder = specFromJSON(fileContents)
break
case 'yml':
case 'yaml':
builder = specFromYaml(fileContents)
break
default:
throw new Error(`Unsupported specification file extension: "${ext}"`)
}
if (options.outputFormat !== ext) {
options.outputFormat = ext === 'yml' ? 'yaml' : ext
}
} else {
builder = new OpenApiBuilder(input)
}
for (const pathName of Object.keys(builder.rootDoc.paths)) {
builder.rootDoc.paths[pathName] = addExtensionsToPath(
builder.rootDoc.paths[pathName],
options
)
}
const result =
options.outputFormat === 'json'
? builder.getSpecAsJson()
: builder.getSpecAsYaml()
if (options.outputFile) {
fs.writeFileSync(options.outputFile, result, {encoding: 'utf-8'})
}
return result
}