generate-operation-ids.ts 2.3 KB

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