Skip to content

Commit

Permalink
support http api in api gateway
Browse files Browse the repository at this point in the history
  • Loading branch information
james-hu committed Jun 12, 2023
1 parent 235fbde commit f492654
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 14 deletions.
29 changes: 20 additions & 9 deletions src/generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as fs from 'fs-extra';
import * as SwaggerUIDist from 'swagger-ui-dist';
import micromatch = require('micromatch');
import { withRetry } from '@handy-common-utils/aws-utils';
import { APIGateway } from 'aws-sdk';
import { APIGateway, ApiGatewayV2 } from 'aws-sdk';
import { HomePage } from './home-page';
import { Context } from './context';
import { Transformer } from './transformer';
Expand All @@ -15,6 +15,7 @@ export class Generator {
const transformer = new Transformer(this.context);

const apig = new APIGateway({ region: this.context.options.flags.region });
const apig2 = new ApiGatewayV2({ region: this.context.options.flags.region });
// eslint-disable-next-line unicorn/no-await-expression-member
const domainNameObjects = (await withRetry(() => apig.getDomainNames({ limit: 500 }).promise()))?.items;
if (domainNameObjects != null) {
Expand All @@ -26,27 +27,37 @@ export class Generator {
this.context.debug(`Found custom domain: ${domainName}`);
const supportHttps = domainNameObj.securityPolicy != null;
// eslint-disable-next-line unicorn/no-await-expression-member
const mappings = (await withRetry(() => apig.getBasePathMappings({ domainName, limit: 500 }).promise()))?.items;
const mappings = (await withRetry(() => apig2.getApiMappings({ DomainName: domainName, MaxResults: '500' }).promise()))?.Items;
if (mappings != null) {
await this.emptyDomainFolder(domainName);
let hasSpecFileWritten = false;
for (const mapping of mappings) {
const basePath = mapping.basePath === '(none)' ? '' : mapping.basePath!;
const basePath = mapping.ApiMappingKey === '(none)' ? '' : mapping.ApiMappingKey!;
const domainAndBasePath = `${domainName}/${basePath}`;
const shouldInclude = micromatch.isMatch(domainAndBasePath, this.context.options.flags.include);
const shouldExclude = this.context.options.flags.exclude == null ? false : micromatch.isMatch(domainAndBasePath, this.context.options.flags.exclude);
this.context.debug(`Found API: ${domainAndBasePath}, shouldInclude=${shouldInclude}, shouldExclude=${shouldExclude}`);
if (shouldInclude && !shouldExclude) {
const baseUrl = `${supportHttps ? 'https' : 'http'}://${domainAndBasePath}`;
this.context.info(`Generating OpenAPI spec for: ${baseUrl}`);
const exported = await withRetry(() => apig.getExport({
restApiId: mapping.restApiId!,
stageName: mapping.stage!,
const exportedForRestOrNull = withRetry(() => apig.getExport({
restApiId: mapping.ApiId!,
stageName: mapping.Stage!,
exportType: 'oas30',
parameters: {},
}).promise());
const specString = exported.body?.toString('utf8');
if (specString != null) {
}).promise())
.catch(() => null);
const exportedForHttpOrNull = withRetry(() => apig2.exportApi({
ApiId: mapping.ApiId!,
StageName: mapping.Stage!,
Specification: 'OAS30',
OutputType: 'JSON',
}).promise())
.catch(() => null);
const specString = (await exportedForRestOrNull ?? await exportedForHttpOrNull)?.body?.toString('utf8');
if (specString == null) {
this.context.info(`Can't find OpenAPI spec for: ${domainAndBasePath}`);
} else {
const specFile = this.context.specFile(domainName, basePath);
const specObj = JSON.parse(specString);
// write original version, then transform, then write transformed version
Expand Down
15 changes: 10 additions & 5 deletions src/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable unicorn/no-array-for-each */
import { Context } from './context';
import { OpenAPIV3, OpenAPIV2 } from 'openapi-types';
import { APIGateway } from 'aws-sdk';
import { APIGateway, ApiGatewayV2 } from 'aws-sdk';

export type OpenApiDocument = OpenAPIV3.Document & {
securityDefinitions?: OpenAPIV2.Document['securityDefinitions'];
Expand Down Expand Up @@ -83,12 +83,12 @@ export class Transformer {
* @param domainAndBasePath domain/path
* @returns void
*/
transform(doc: OpenApiDocument, _domainNameObj: APIGateway.DomainName, _basePathMapping: APIGateway.BasePathMapping, domainAndBasePath: string): void {
transform(doc: OpenApiDocument, _domainNameObj: APIGateway.DomainName, _basePathMapping: APIGateway.BasePathMapping|ApiGatewayV2.ApiMapping, domainAndBasePath: string): void {
// remove unnecessary basePath variable
if (doc.servers && doc.servers.length === 1) {
const server = doc.servers[0];
const defaultBasePath = server.variables?.basePath?.default;
if (defaultBasePath && server.url) {
if (defaultBasePath != null && server.url) {
server.url = (server.url as string).replace('/{basePath}', defaultBasePath);
this.context.debug(`Simplify server URL for ${domainAndBasePath}: ${server.url}`);
if (Object.keys(server.variables!).length === 1) {
Expand All @@ -97,6 +97,11 @@ export class Transformer {
}
}

// swagger-ui renders tags in an exaggerated way
if (doc.tags) {
delete doc.tags;
}

// if x-api-key header has been ever mentioned
let xApiKeyHeaderParameter: string|null = null;
// if api_key query parameter has been ever mentioned
Expand All @@ -111,14 +116,14 @@ export class Transformer {
.map(([key, value]) => { // not all values are actually OperationObject
const operationObject = value as OpenApiDocumentOperationObject;
if (['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes(key)) {
const operationLabel = `${key.toUpperCase()} ${domainAndBasePath}${path}`;
const operationLabel = `${key.toUpperCase()} ${domainAndBasePath}${domainAndBasePath.endsWith('/') && path.startsWith('/') ? path.slice(1) : path}`;
// make sure there is a parameters array
if (operationObject.parameters == null) {
this.context.debug(`Add empty parameter list for ${operationLabel}`);
operationObject.parameters = [];
}
// make sure there is a responses array
if (operationObject.responses == null) {
if (operationObject.responses == null || (Object.keys(operationObject.responses).length === 1 && (operationObject.responses.default as any)?.description?.startsWith('Default response for '))) {
this.context.debug(`Add default standard response list for ${operationLabel}`);
operationObject.responses = defaultStandardResponses;
} else {
Expand Down

0 comments on commit f492654

Please sign in to comment.