@@ -20,6 +20,7 @@ import type {
2020 EndpointOverrides ,
2121 GenerationOptions ,
2222 OperationDefinition ,
23+ OperationIdTransformer ,
2324 ParameterDefinition ,
2425 ParameterMatcher ,
2526 TextMatcher ,
@@ -38,8 +39,47 @@ function defaultIsDataResponse(code: string, includeDefault: boolean) {
3839 return ! Number . isNaN ( parsedCode ) && parsedCode >= 200 && parsedCode < 300 ;
3940}
4041
41- function getOperationName ( { verb, path, operation } : Pick < OperationDefinition , 'verb' | 'path' | 'operation' > ) {
42- return _getOperationName ( verb , path , operation . operationId ) ;
42+ /**
43+ * Resolves the generated endpoint name for an operation by applying the
44+ * configured {@linkcode OperationIdTransformer}.
45+ *
46+ * - `"camelCase"` *(default)* — delegates to `oazapfts` `getOperationName`,
47+ * which applies lodash `camelCase` and falls back to a verb+path derived
48+ * name when `operationId` is absent.
49+ * - `"none"` — returns `operation.operationId` verbatim.
50+ * - `(operationId: string) => string` — calls the provided function with `operation.operationId`.
51+ *
52+ * For `"none"` and function transformers, a missing `operationId` throws an
53+ * {@linkcode Error} with the offending HTTP method and path in the message.
54+ *
55+ * @param operationDefinition - The operation to resolve a name for.
56+ * @param operationIdTransformer - How to transform the `operationId`.
57+ * @returns The resolved endpoint name string.
58+ * @throws An {@linkcode Error} When `operationId` is `undefined` and transformer is not `"camelCase"`.
59+ *
60+ * @since 2.3.0
61+ */
62+ export function resolveOperationName (
63+ operationDefinition : Pick < OperationDefinition , 'verb' | 'path' | 'operation' > ,
64+ operationIdTransformer : OperationIdTransformer = 'camelCase'
65+ ) : string {
66+ const { verb, path, operation } = operationDefinition ;
67+
68+ if ( operationIdTransformer === 'camelCase' ) {
69+ return _getOperationName ( verb , path , operation . operationId ) ;
70+ }
71+
72+ if ( operation . operationId === undefined ) {
73+ throw new Error (
74+ `operationIdTransformer: "${ typeof operationIdTransformer === 'function' ? 'function' : operationIdTransformer } " requires all operations to have an operationId, but found a missing operationId at ${ verb . toUpperCase ( ) } ${ path } `
75+ ) ;
76+ }
77+
78+ if ( operationIdTransformer === 'none' ) {
79+ return operation . operationId ;
80+ }
81+
82+ return operationIdTransformer ( operation . operationId ) ;
4383}
4484
4585function getTags ( { verb, pathItem } : Pick < OperationDefinition , 'verb' | 'pathItem' > ) : string [ ] {
@@ -56,11 +96,11 @@ function patternMatches(pattern?: TextMatcher) {
5696 } ;
5797}
5898
59- function operationMatches ( pattern ?: EndpointMatcher ) {
99+ function operationMatches ( pattern ?: EndpointMatcher , operationIdTransformer : OperationIdTransformer = 'camelCase' ) {
60100 const checkMatch = typeof pattern === 'function' ? pattern : patternMatches ( pattern ) ;
61101 return function matcher ( operationDefinition : OperationDefinition ) {
62102 if ( ! pattern ) return true ;
63- const operationName = getOperationName ( operationDefinition ) ;
103+ const operationName = resolveOperationName ( operationDefinition , operationIdTransformer ) ;
64104 return checkMatch ( operationName , operationDefinition ) ;
65105 } ;
66106}
@@ -139,9 +179,10 @@ function generateRegexConstantsForType(
139179
140180export function getOverrides (
141181 operation : OperationDefinition ,
142- endpointOverrides ?: EndpointOverrides [ ]
182+ endpointOverrides ?: EndpointOverrides [ ] ,
183+ operationIdTransformer : OperationIdTransformer = 'camelCase'
143184) : EndpointOverrides | undefined {
144- return endpointOverrides ?. find ( ( override ) => operationMatches ( override . pattern ) ( operation ) ) ;
185+ return endpointOverrides ?. find ( ( override ) => operationMatches ( override . pattern , operationIdTransformer ) ( operation ) ) ;
145186}
146187
147188export async function generateApi (
@@ -170,6 +211,7 @@ export async function generateApi(
170211 useUnknown = false ,
171212 esmExtensions = false ,
172213 outputRegexConstants = false ,
214+ operationIdTransformer = 'camelCase' ,
173215 } : GenerationOptions
174216) {
175217 const v3Doc = ( v3DocCache [ spec ] ??= await getV3Doc ( spec , httpResolverOptions ) ) ;
@@ -186,7 +228,9 @@ export async function generateApi(
186228 apiGen . preprocessComponents ( apiGen . spec . components . schemas ) ;
187229 }
188230
189- const operationDefinitions = getOperationDefinitions ( v3Doc ) . filter ( operationMatches ( filterEndpoints ) ) ;
231+ const operationDefinitions = getOperationDefinitions ( v3Doc ) . filter (
232+ operationMatches ( filterEndpoints , operationIdTransformer )
233+ ) ;
190234
191235 const resultFile = ts . createSourceFile (
192236 'someFileName.ts' ,
@@ -239,7 +283,7 @@ export async function generateApi(
239283 operationDefinitions . map ( ( operationDefinition ) =>
240284 generateEndpoint ( {
241285 operationDefinition,
242- overrides : getOverrides ( operationDefinition , endpointOverrides ) ,
286+ overrides : getOverrides ( operationDefinition , endpointOverrides , operationIdTransformer ) ,
243287 } )
244288 ) ,
245289 true
@@ -278,6 +322,7 @@ export async function generateApi(
278322 endpointOverrides,
279323 config : hooks ,
280324 operationNameSuffix,
325+ operationIdTransformer,
281326 } ) ,
282327 ]
283328 : [ ] ) ,
@@ -314,7 +359,7 @@ export async function generateApi(
314359 operation,
315360 operation : { responses, requestBody } ,
316361 } = operationDefinition ;
317- const operationName = getOperationName ( { verb, path, operation } ) ;
362+ const operationName = resolveOperationName ( { verb, path, operation } , operationIdTransformer ) ;
318363 const tags = tag ? getTags ( { verb, pathItem } ) : undefined ;
319364 const isQuery = testIsQuery ( verb , overrides ) ;
320365
0 commit comments