UNPKG

@aws/pdk

Version:

All documentation is located at: https://aws.github.io/aws-pdk

142 lines (124 loc) 4.17 kB
/*! Copyright [Amazon.com](http://amazon.com/), Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: Apache-2.0 */ import SwaggerParser from "@apidevtools/swagger-parser"; import { writeFile } from "projen/lib/util"; import { parse } from "ts-command-line-args"; /** * Arguments for generating an AsyncAPI specification */ interface Arguments { /** * Path to the input OpenAPI specification json file (.api.json). */ readonly specPath: string; /** * Path to write the output json AsyncAPI spec */ readonly outputPath: string; } interface AsyncOp { readonly routeKey: string; readonly schema?: any; readonly direction: 'client_to_server' | 'server_to_client' | 'bidirectional'; readonly description?: string; readonly tags?: string[]; } const extractAsyncOps = (openApiSpec: any): AsyncOp[] => { const operations: AsyncOp[] = []; Object.entries(openApiSpec.paths || {}).forEach(([p, pathOp]: [string, any]) => { Object.entries(pathOp ?? {}).forEach(([method, operation]: [string, any]) => { if (!operation?.['x-async']?.direction) { throw new Error(`Operation ${method} ${p} did not have the x-async vendor extension. Please supply a valid Type Safe WebSocket API specification.`); } operations.push({ routeKey: p.replace(/\//g, ''), schema: operation?.requestBody?.content?.['application/json']?.schema, direction: operation?.['x-async']?.direction, description: operation?.description, tags: operation?.tags, }); }); }); return operations; }; const generateAsyncApiSpec = (openApiSpec: any): any => { const operations = extractAsyncOps(openApiSpec); // One message per operation no matter the direction(s) const messages = Object.fromEntries(operations.map(op => [op.routeKey, { name: op.routeKey, title: op.routeKey, summary: op.description, tags: (op.tags ?? []).map(t => ({ name: t })), contentType: op.schema ? 'application/json' : undefined, payload: { type: 'object', properties: { route: { type: 'string', const: op.routeKey, }, payload: op.schema, }, required: ['route', ...(op.schema ? ["payload"] : [])], }, }], )); // One operation per direction each message is sent in. // Documentation is generated from a client perspective, so server -> client operations are prefixed with "on" const asyncApiOperations = Object.fromEntries(operations.flatMap(op => { const asyncApiOps: { action: string, title: string }[] = []; if (['bidirectional', 'client_to_server'].includes(op.direction)) { asyncApiOps.push({ action: 'send', title: op.routeKey, }); } if (['bidirectional', 'server_to_client'].includes(op.direction)) { asyncApiOps.push({ action: 'receive', title: `On${op.routeKey}`, }); } return asyncApiOps.map(asyncOp => [asyncOp.title, { ...asyncOp, channel: { $ref: '#/channels/default', }, messages: [ { $ref: `#/channels/default/messages/${op.routeKey}`, }, ], }]); })); return { asyncapi: '3.0.0', info: openApiSpec.info, channels: { // WebSocket APIs have a single channel, on which all messages are sent back and forth default: { address: '/', messages: Object.fromEntries(Object.keys(messages).map(messageId => [messageId, { $ref: `#/components/messages/${messageId}`, }])), }, }, operations: asyncApiOperations, components: { messages, schemas: openApiSpec.components?.schemas, }, }; }; export default async (argv: string[]) => { const args = parse<Arguments>({ specPath: { type: String, alias: "s" }, outputPath: { type: String, alias: "o" }, }, { argv }); const openApiSpec = (await SwaggerParser.bundle(args.specPath)) as any; const asyncApiSpec = generateAsyncApiSpec(openApiSpec); writeFile(args.outputPath, JSON.stringify(asyncApiSpec, null, 2), { readonly: true, }); };