Skip to content

Commit bd7ccc6

Browse files
committed
- Add query-string library as a dependency
- Add an optional parameter `useQueryString` to `RequestOptions` which allows callers to use the query string library when generating the resolvers - Add branching logic that uses either query-string or the previous custom logic Signed-off-by: Dani Penev <[email protected]>
1 parent bbcf3fd commit bd7ccc6

File tree

7 files changed

+5765
-5774
lines changed

7 files changed

+5765
-5774
lines changed

packages/openapi-to-graphql/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
"jsonpointer": "^5.0.0",
9595
"oas-validator": "^5.0.2",
9696
"pluralize": "^8.0.0",
97+
"query-string": "^7.1.1",
9798
"swagger2openapi": "^7.0.2",
9899
"tslib": "^2.3.0",
99100
"url-join": "4.0.1",
@@ -105,7 +106,7 @@
105106
"devDependencies": {
106107
"@types/deep-equal": "^1.0.1",
107108
"@types/graphql": "^14.0.3",
108-
"@types/graphql-upload": "^8.0.7",
109+
"@types/graphql-upload": "8.0.7",
109110
"@types/jest": "^26.0.14",
110111
"@types/node": "^16.3.3",
111112
"@types/url-join": "^4.0.1",

packages/openapi-to-graphql/src/resolver_builder.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import formurlencoded from 'form-urlencoded'
2828
import { PubSub } from 'graphql-subscriptions'
2929
import urljoin from 'url-join'
3030
import FormData from 'form-data'
31+
import * as querystring from 'query-string';
3132

3233
const pubsub = new PubSub()
3334

@@ -722,7 +723,11 @@ export function getResolver<TSource, TContext, TArgs>({
722723

723724
resolveData.usedRequestOptions = options
724725
resolveData.usedStatusCode = operation.statusCode
725-
setSearchParamsFromObj(url, qs, [])
726+
if (requestOptions.useQueryString) {
727+
setSearchFromObj(url, qs)
728+
} else {
729+
setSearchParamsFromObj(url, qs, [])
730+
}
726731
resolveData.url = url.toString().replace(url.search, '')
727732

728733
// Make the call
@@ -1411,6 +1416,12 @@ export function extractRequestDataFromArgs<TSource, TContext, TArgs>(
14111416
return { path, qs, headers }
14121417
}
14131418

1419+
// This can be extended in the future to take an optional object which controls
1420+
// the stringify options as listed here https://github.com/sindresorhus/query-string#stringifyobject-options
1421+
const setSearchFromObj = (url: URL, obj: any) => {
1422+
url.search = querystring.stringify(obj);
1423+
}
1424+
14141425
const setSearchParamsFromObj = (url: URL, obj: any, path: string[]) => {
14151426
for (const key in obj) {
14161427
const val = obj[key]

packages/openapi-to-graphql/src/types/options.ts

+3
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,16 @@ export type RequestHeadersFunction<TSource, TContext, TArgs> = (
6868
* function.
6969
*
7070
* Based on: https://github.com/request/request#requestoptions-callback
71+
*
72+
* useQueryString is an optional parameter to use a the query-string library
7173
*/
7274
export type RequestOptions<TSource, TContext, TArgs> = Omit<
7375
RequestInit,
7476
'headers'
7577
> & {
7678
headers?: HeadersInit | RequestHeadersFunction<TSource, TContext, TArgs>
7779
qs?: Record<string, string>
80+
useQueryString?: boolean
7881
}
7982

8083
/**

packages/openapi-to-graphql/test/example_api6.test.ts

+38
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,41 @@ test('Handle no response schema', () => {
396396
})
397397
})
398398
})
399+
400+
/**
401+
* GET /arrayInQueryParameters with and without the useQueryString requestOption
402+
*
403+
* The default behaviour is to add indexed parameters, i.e. a[0]=x&a[1]=y.
404+
* The querystring behaviour (which can be made configurable in the future) is a=x&a=y
405+
*/
406+
test('Optionally use queryString', () => {
407+
const query = `{
408+
arrayInQueryParameters(ids: ["a", "b"])
409+
}`
410+
411+
// Use default settings
412+
const promise = graphql(createdSchema, query).then((result) => {
413+
expect(result.data).toEqual({
414+
arrayInQueryParameters: encodeURI('ids[0]=a&ids[1]=b')
415+
})
416+
})
417+
418+
// Set useQueryString to true
419+
const options: Options<any, any, any> = {
420+
requestOptions: {
421+
useQueryString: true
422+
}
423+
}
424+
425+
const promise2 = openAPIToGraphQL
426+
.createGraphQLSchema(oas, options)
427+
.then(({ schema, report }) => {
428+
return graphql(schema, query).then((result) => {
429+
expect(result.data).toEqual({
430+
arrayInQueryParameters: encodeURI('ids=a&ids=b')
431+
})
432+
})
433+
})
434+
435+
return Promise.all([promise, promise2])
436+
})

packages/openapi-to-graphql/test/example_api6_server.js

+7
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ function startServer(PORT) {
5757
}
5858
)
5959

60+
app.get(
61+
'/api/arrayInQueryParameters',
62+
(req, res) => {
63+
res.send(req.originalUrl.split('?')[1])
64+
}
65+
)
66+
6067
function stringifyRussianDolls(russianDoll) {
6168
if (!typeof russianDoll.name === 'string') {
6269
return ''

packages/openapi-to-graphql/test/fixtures/example_oas6.json

+30
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,36 @@
416416
}
417417
}
418418
}
419+
},
420+
"/arrayInQueryParameters": {
421+
"get": {
422+
"description": "Takes an array as GET query parameter",
423+
"responses": {
424+
"200": {
425+
"description": "Success",
426+
"content": {
427+
"text/html": {
428+
"schema": {
429+
"type": "string"
430+
}
431+
}
432+
}
433+
}
434+
},
435+
"parameters": [
436+
{
437+
"name": "ids",
438+
"in": "query",
439+
"required": false,
440+
"schema": {
441+
"type": "array",
442+
"items": {
443+
"type": "string"
444+
}
445+
}
446+
}
447+
]
448+
}
419449
}
420450
},
421451
"components": {

0 commit comments

Comments
 (0)