@@ -11,6 +11,7 @@ import * as url from 'url';
1111import { dereferenceParameter , normalizeParameter } from './util' ;
1212import * as mediaTypeParser from 'media-typer' ;
1313import * as contentTypeParser from 'content-type' ;
14+ import { parse } from 'qs' ;
1415
1516type SchemaObject = OpenAPIV3 . SchemaObject ;
1617type ReferenceObject = OpenAPIV3 . ReferenceObject ;
@@ -67,6 +68,8 @@ export class RequestParameterMutator {
6768 url . parse ( req . originalUrl ) . query ,
6869 ) ;
6970
71+ req . query = this . handleBracketNotationQueryFields ( req . query ) ;
72+
7073 ( parameters || [ ] ) . forEach ( ( p ) => {
7174 const parameter = dereferenceParameter ( this . _apiDocs , p ) ;
7275 const { name, schema } = normalizeParameter ( this . ajv , parameter ) ;
@@ -96,14 +99,27 @@ export class RequestParameterMutator {
9699 if ( parameter . content ) {
97100 this . handleContent ( req , name , parameter ) ;
98101 } else if ( parameter . in === 'query' && this . isObjectOrXOf ( schema ) ) {
102+ // handle bracket notation and mutates query param
103+
104+
99105 if ( style === 'form' && explode ) {
100106 this . parseJsonAndMutateRequest ( req , parameter . in , name ) ;
101107 this . handleFormExplode ( req , name , < SchemaObject > schema , parameter ) ;
102108 } else if ( style === 'deepObject' ) {
103109 this . handleDeepObject ( req , queryString , name , schema ) ;
110+ } else if ( style === 'form' && ! explode && schema . type === 'object' ) {
111+ const value = req . query [ name ] ;
112+ if ( typeof value === 'string' ) {
113+ const kvPairs = this . csvToKeyValuePairs ( value ) ;
114+ if ( kvPairs ) {
115+ req . query [ name ] = kvPairs ;
116+ return ;
117+ }
118+ }
119+ this . parseJsonAndMutateRequest ( req , parameter . in , name ) ;
104120 } else {
105121 this . parseJsonAndMutateRequest ( req , parameter . in , name ) ;
106- }
122+ }
107123 } else if ( type === 'array' && ! explode ) {
108124 const delimiter = ARRAY_DELIMITER [ parameter . style ] ;
109125 this . validateArrayDelimiter ( delimiter , parameter ) ;
@@ -411,4 +427,52 @@ export class RequestParameterMutator {
411427 return m ;
412428 } , new Map < string , string [ ] > ( ) ) ;
413429 }
430+
431+ private csvToKeyValuePairs ( csvString : string ) : Record < string , string > | undefined {
432+ const hasBrace = csvString . split ( '{' ) . length > 1 ;
433+ const items = csvString . split ( ',' ) ;
434+
435+ if ( hasBrace ) {
436+ // if it has a brace, we assume its JSON and skip creating k v pairs
437+ // TODO improve json check, but ensure its cheap
438+ return ;
439+ }
440+
441+ if ( items . length % 2 !== 0 ) {
442+ // if the number of elements is not event,
443+ // then we do not have k v pairs, so return undefined
444+ return ;
445+ }
446+
447+ const result = { } ;
448+
449+ for ( let i = 0 ; i < items . length - 1 ; i += 2 ) {
450+ result [ items [ i ] ] = items [ i + 1 ] ;
451+ }
452+
453+ return result ;
454+ }
455+
456+ /**
457+ * Mutates and normalizes the req.query object by parsing braket notation query string key values pairs
458+ * into its corresponding key=<json-object> and update req.query with the parsed value
459+ * for instance, req.query that equals { filter[name]: test} is translated into { filter: { name: 'test' }, where
460+ * the query string field is set as filter and its value is the full javascript object (translated from bracket notation)
461+ * @param keys
462+ * @returns
463+ */
464+ private handleBracketNotationQueryFields ( query : { [ key : string ] : any } ) : {
465+ [ key : string ] : any ;
466+ } {
467+ Object . keys ( query ) . forEach ( ( key ) => {
468+ const bracketNotation = key . includes ( '[' ) ;
469+ if ( bracketNotation ) {
470+ const normalizedKey = key . split ( '[' ) [ 0 ] ;
471+ query [ normalizedKey ] = parse ( `${ key } =${ query [ key ] } ` ) [ normalizedKey ] ;
472+ delete query [ key ] ;
473+ }
474+ } ) ;
475+ return query ;
476+ }
477+
414478}
0 commit comments