@@ -229,26 +229,25 @@ function buildRequestUrl(profile: Profile, command: CliCommand, flags: Record<st
229229 const value = flags [ opt . name ] ;
230230 if ( value !== undefined ) {
231231 const token = `{${ opt . name } }` ;
232- pathValue = pathValue . replace ( token , encodeURIComponent ( value ) ) ;
232+ pathValue = pathValue . replace ( token , serializePathParameter ( opt , value ) ) ;
233233 }
234234 } ) ;
235235
236- const baseUrl = profile . apiBaseUrl . replace ( / \/ + $ / , "" ) ;
237- let url = `${ baseUrl } ${ pathValue } ` ;
236+ const baseUrl = ( command . serverUrl ?? profile . apiBaseUrl ) . replace ( / \/ + $ / , "" ) ;
237+ let url = baseUrl ? `${ baseUrl } ${ pathValue } ` : pathValue ;
238238
239- const queryParams = new URLSearchParams ( ) ;
239+ const queryParts : string [ ] = [ ] ;
240240 command . options
241241 . filter ( ( opt ) => opt . location === "query" )
242242 . forEach ( ( opt ) => {
243243 const value = flags [ opt . name ] ;
244244 if ( value !== undefined ) {
245- queryParams . set ( opt . name , value ) ;
245+ queryParts . push ( ... serializeQueryParameter ( opt , value ) ) ;
246246 }
247247 } ) ;
248248
249- const queryString = queryParams . toString ( ) ;
250- if ( queryString ) {
251- url += url . includes ( "?" ) ? `&${ queryString } ` : `?${ queryString } ` ;
249+ if ( queryParts . length > 0 ) {
250+ url += url . includes ( "?" ) ? `&${ queryParts . join ( "&" ) } ` : `?${ queryParts . join ( "&" ) } ` ;
252251 }
253252
254253 return url ;
@@ -351,6 +350,129 @@ function buildRequestPayload(
351350 return { } ;
352351}
353352
353+ function serializePathParameter ( option : CliCommandOption , rawValue : string ) : string {
354+ const value = parseStructuredParameterValue ( option , rawValue ) ;
355+
356+ if ( Array . isArray ( value ) ) {
357+ const encoded = value . map ( ( item ) => encodeURIComponent ( String ( item ) ) ) ;
358+ const style = option . style ?? "simple" ;
359+ const explode = option . explode ?? false ;
360+
361+ if ( style === "label" ) {
362+ return explode ? `.${ encoded . join ( "." ) } ` : `.${ encoded . join ( "," ) } ` ;
363+ }
364+
365+ if ( style === "matrix" ) {
366+ return explode
367+ ? encoded . map ( ( item ) => `;${ encodeURIComponent ( option . name ) } =${ item } ` ) . join ( "" )
368+ : `;${ encodeURIComponent ( option . name ) } =${ encoded . join ( "," ) } ` ;
369+ }
370+
371+ return encoded . join ( "," ) ;
372+ }
373+
374+ if ( value && typeof value === "object" ) {
375+ const entries = Object . entries ( value as Record < string , unknown > ) . map (
376+ ( [ key , item ] ) => [ encodeURIComponent ( key ) , encodeURIComponent ( String ( item ) ) ] as const
377+ ) ;
378+ const style = option . style ?? "simple" ;
379+ const explode = option . explode ?? false ;
380+
381+ if ( style === "label" ) {
382+ return explode
383+ ? `.${ entries . map ( ( [ key , item ] ) => `${ key } =${ item } ` ) . join ( "." ) } `
384+ : `.${ entries . flat ( ) . join ( "," ) } ` ;
385+ }
386+
387+ if ( style === "matrix" ) {
388+ return explode
389+ ? entries . map ( ( [ key , item ] ) => `;${ key } =${ item } ` ) . join ( "" )
390+ : `;${ encodeURIComponent ( option . name ) } =${ entries . flat ( ) . join ( "," ) } ` ;
391+ }
392+
393+ return explode
394+ ? entries . map ( ( [ key , item ] ) => `${ key } =${ item } ` ) . join ( "," )
395+ : entries . flat ( ) . join ( "," ) ;
396+ }
397+
398+ return encodeURIComponent ( String ( value ) ) ;
399+ }
400+
401+ function serializeQueryParameter ( option : CliCommandOption , rawValue : string ) : string [ ] {
402+ const value = parseStructuredParameterValue ( option , rawValue ) ;
403+ const encodedName = encodeURIComponent ( option . name ) ;
404+
405+ if ( Array . isArray ( value ) ) {
406+ const encodedValues = value . map ( ( item ) => encodeURIComponent ( String ( item ) ) ) ;
407+
408+ if ( option . collectionFormat === "multi" ) {
409+ return encodedValues . map ( ( item ) => `${ encodedName } =${ item } ` ) ;
410+ }
411+
412+ const joiner = option . collectionFormat === "ssv"
413+ ? " "
414+ : option . collectionFormat === "tsv"
415+ ? "\t"
416+ : option . collectionFormat === "pipes"
417+ ? "|"
418+ : option . style === "spaceDelimited"
419+ ? " "
420+ : option . style === "pipeDelimited"
421+ ? "|"
422+ : "," ;
423+
424+ const explode = option . collectionFormat
425+ ? option . collectionFormat === "multi"
426+ : option . explode ?? true ;
427+
428+ if ( explode && joiner === "," ) {
429+ return encodedValues . map ( ( item ) => `${ encodedName } =${ item } ` ) ;
430+ }
431+
432+ return [ `${ encodedName } =${ encodedValues . join ( encodeURIComponent ( joiner ) ) } ` ] ;
433+ }
434+
435+ if ( value && typeof value === "object" ) {
436+ const entries = Object . entries ( value as Record < string , unknown > ) . map (
437+ ( [ key , item ] ) => [ encodeURIComponent ( key ) , encodeURIComponent ( String ( item ) ) ] as const
438+ ) ;
439+ const style = option . style ?? "form" ;
440+ const explode = option . explode ?? true ;
441+
442+ if ( style === "deepObject" ) {
443+ return entries . map ( ( [ key , item ] ) => `${ encodedName } %5B${ key } %5D=${ item } ` ) ;
444+ }
445+
446+ if ( explode ) {
447+ return entries . map ( ( [ key , item ] ) => `${ key } =${ item } ` ) ;
448+ }
449+
450+ return [ `${ encodedName } =${ entries . flat ( ) . join ( "," ) } ` ] ;
451+ }
452+
453+ return [ `${ encodedName } =${ encodeURIComponent ( String ( value ) ) } ` ] ;
454+ }
455+
456+ function parseStructuredParameterValue ( option : CliCommandOption , rawValue : string ) : unknown {
457+ if ( option . schemaType === "array" ) {
458+ const trimmed = rawValue . trim ( ) ;
459+ if ( trimmed . startsWith ( "[" ) ) {
460+ return parseBodyFlagValue ( rawValue ) ;
461+ }
462+ return rawValue . split ( "," ) . map ( ( item ) => item . trim ( ) ) . filter ( ( item ) => item . length > 0 ) ;
463+ }
464+
465+ if ( option . schemaType === "object" ) {
466+ const trimmed = rawValue . trim ( ) ;
467+ if ( ! trimmed . startsWith ( "{" ) ) {
468+ throw new Error ( `Object parameter --${ option . name } expects JSON object value` ) ;
469+ }
470+ return parseBodyFlagValue ( rawValue ) ;
471+ }
472+
473+ return rawValue ;
474+ }
475+
354476export async function run ( argv : string [ ] , options ? : RunOptions ) : Promise < void > {
355477 const cwd = options ?. cwd ?? process . cwd ( ) ;
356478 const configLocator = options ?. configLocator ?? new ConfigLocator ( ) ;
@@ -411,7 +533,11 @@ export async function run(argv: string[], options?: RunOptions): Promise<void> {
411533
412534 const addProfileOptions = ( y : ReturnType < typeof yargs > ) =>
413535 y
414- . option ( "api-base-url" , { type : "string" , demandOption : true } )
536+ . option ( "api-base-url" , {
537+ type : "string" ,
538+ demandOption : true ,
539+ description : "Base URL for API requests." ,
540+ } )
415541 . option ( "openapi-spec" , { type : "string" , demandOption : true } )
416542 . option ( "api-basic-auth" , { type : "string" , default : "" } )
417543 . option ( "api-bearer-token" , { type : "string" , default : "" } )
0 commit comments