generate-operation-ids.ts 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. import SwaggerParser from '@apidevtools/swagger-parser';
  2. import type { OpenAPI3, OperationObject, PathItemObject } from 'openapi-typescript';
  3. const toPascal = (s: string): string => s.split('-').map(w => w[0]?.toUpperCase() + w.slice(1)).join('');
  4. const createParamSuffix = (params: string[]): string => {
  5. return params.length > 0
  6. ? params.reverse().map(param => `By${toPascal(param.slice(1, -1))}`).join('')
  7. : '';
  8. };
  9. /**
  10. * Generates a PascalCase operation name based on the HTTP method and path.
  11. *
  12. * e.g.
  13. * - `GET /foo` -> `getFoo`
  14. * - `POST /bar` -> `postBar`
  15. * - `Get /foo/bar` -> `getBarForFoo`
  16. * - `GET /foo/{id}` -> `getFooById`
  17. * - `GET /foo/{id}/bar` -> `getBarByIdForFoo`
  18. * - `GET /foo/{id}/{page}/bar` -> `getBarByPageByIdForFoo`
  19. *
  20. */
  21. function createOperationId(method: string, path: string): string {
  22. const segments = path.split('/').filter(Boolean);
  23. const params = segments.filter(s => s.startsWith('{'));
  24. const paths = segments.filter(s => !s.startsWith('{'));
  25. const paramSuffix = createParamSuffix(params);
  26. if (paths.length <= 1) {
  27. return `${method.toLowerCase()}${toPascal(paths[0] || 'root')}${paramSuffix}`;
  28. }
  29. const [resource, ...context] = paths.reverse();
  30. return `${method.toLowerCase()}${toPascal(resource)}${paramSuffix}For${context.reverse().map(toPascal).join('')}`;
  31. }
  32. export async function generateOperationIds(inputFile: string, opts?: { overwriteExisting: boolean }): Promise<string> {
  33. const api = await SwaggerParser.parse(inputFile) as OpenAPI3;
  34. Object.entries(api.paths || {}).forEach(([path, pathItem]) => {
  35. const item = pathItem as PathItemObject;
  36. (['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'] as const)
  37. .forEach((method) => {
  38. const operation = item[method] as OperationObject | undefined;
  39. if (operation == null || (operation.operationId != null && !opts?.overwriteExisting)) {
  40. return;
  41. }
  42. operation.operationId = createOperationId(method, path);
  43. });
  44. });
  45. const output = JSON.stringify(api, null, 2);
  46. if (output == null) {
  47. throw new Error(`Failed to generate operation IDs for ${inputFile}`);
  48. }
  49. return output;
  50. }