@@ -31,7 +31,47 @@ function runtimeExpressionToUrlPrefix(expression: string): string {
3131 return '${' + t + '}' ;
3232}
3333
34- const hasParam = ( path : string ) : boolean => / [ ^ { ] * { [ \w . * _ - ] * } .* / . test ( path ) ;
34+ // Characters allowed in an OpenAPI path-parameter name inside `{...}`
35+ // (`{petId}`, `{user_id}`, `{scope.id}`, `{kebab-case}`, `{path*}`).
36+ const PATH_PARAM_NAME_CHARS = String . raw `[\w.*_-]` ;
37+ const HAS_PATH_PARAM_REGEX = new RegExp ( `[^{]*{${ PATH_PARAM_NAME_CHARS } *}.*` ) ;
38+ const PATH_PARAM_SEGMENT_REGEX = new RegExp (
39+ `([^{]*){?(${ PATH_PARAM_NAME_CHARS } *)}?(.*)` ,
40+ ) ;
41+
42+ export const hasPathParam = ( path : string ) : boolean =>
43+ HAS_PATH_PARAM_REGEX . test ( path ) ;
44+
45+ /**
46+ * Sanitizes an OpenAPI path-parameter name for emission in generated code:
47+ * keeps word characters, underscores, dashes and dots, strips everything
48+ * else, and prefixes ES5 keywords with an underscore.
49+ */
50+ export const sanitizePathParamName = ( name : string ) : string =>
51+ sanitize ( name , { es5keyword : true , underscore : true , dash : true , dot : true } ) ;
52+
53+ /**
54+ * Converts every `{param}` in a path segment to `:param` (Hono/MSW style
55+ * routes). `formatParamName` maps the raw OpenAPI parameter name to the
56+ * emitted one (e.g. `sanitizePathParamName`, composed with `camel` or not).
57+ */
58+ export function toColonRoutePath (
59+ path : string ,
60+ formatParamName : ( rawName : string ) => string ,
61+ ) : string {
62+ const matches = PATH_PARAM_SEGMENT_REGEX . exec ( path ) ;
63+ if ( ! matches ?. length ) return path ;
64+
65+ const [ , prev , rawName , rest ] = matches ;
66+ const param = formatParamName ( rawName ) ;
67+ const next = hasPathParam ( rest )
68+ ? toColonRoutePath ( rest , formatParamName )
69+ : rest ;
70+
71+ return hasPathParam ( path )
72+ ? `${ prev } :${ param } ${ next } `
73+ : `${ prev } ${ param } ${ next } ` ;
74+ }
3575
3676const esc = ( str : string ) => jsesc ( str , { quotes : 'backtick' , wrap : false } ) ;
3777
@@ -45,26 +85,21 @@ const getRoutePath = (path: string): string => {
4585 if ( closeIdx === - 1 ) return esc ( path ) ;
4686 const before = esc ( path . slice ( 0 , closeIdx + 1 ) ) ;
4787 const rest = path . slice ( closeIdx + 1 ) ;
48- return hasParam ( rest )
88+ return hasPathParam ( rest )
4989 ? `${ before } ${ getRoutePath ( rest ) } `
5090 : `${ before } ${ esc ( rest ) } ` ;
5191 }
5292
53- const matches = / ( [ ^ { ] * ) { ? ( [ \w . * _ - ] * ) } ? ( . * ) / . exec ( path ) ;
93+ const matches = PATH_PARAM_SEGMENT_REGEX . exec ( path ) ;
5494 if ( ! matches ?. length ) return esc ( path ) ;
5595
5696 const prev = matches [ 1 ] ;
5797 const rawParam = matches [ 2 ] ;
5898 const rest = matches [ 3 ] ;
59- const param = sanitize ( camel ( rawParam ) , {
60- es5keyword : true ,
61- underscore : true ,
62- dash : true ,
63- dot : true ,
64- } ) ;
65- const next = hasParam ( rest ) ? getRoutePath ( rest ) : esc ( rest ) ;
66-
67- return hasParam ( path )
99+ const param = sanitizePathParamName ( camel ( rawParam ) ) ;
100+ const next = hasPathParam ( rest ) ? getRoutePath ( rest ) : esc ( rest ) ;
101+
102+ return hasPathParam ( path )
68103 ? `${ esc ( prev ) } \${${ param } }${ next } `
69104 : `${ esc ( prev ) } ${ param } ${ next } ` ;
70105} ;
0 commit comments