Skip to content

Commit 404e844

Browse files
Alan-ChaErikWittern
authored andcommitted
Support x-www-form-urlencoded request bodies
Signed-off-by: Alan Cha <[email protected]>
1 parent 2c41dc9 commit 404e844

11 files changed

+274
-72
lines changed

packages/openapi-to-graphql/lib/oas_3_tools.js

+23-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/openapi-to-graphql/lib/oas_3_tools.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/openapi-to-graphql/lib/resolver_builder.js

+14-14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/openapi-to-graphql/lib/resolver_builder.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/openapi-to-graphql/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"@types/request": "^2.48.1",
6868
"debug": "^4.1.0",
6969
"deep-equal": "^1.0.1",
70+
"form-urlencoded": "^4.1.1",
7071
"graphql-type-json": "^0.2.1",
7172
"jsonpath-plus": "^0.18.0",
7273
"oas-validator": "^3.1.0",

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

+33-21
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ export function getRequestBodyObject(
522522
oas
523523
) as RequestBodyObject
524524
} else {
525-
requestBodyObject = (requestBodyObject as any) as RequestBodyObject
525+
requestBodyObject = requestBodyObject as RequestBodyObject
526526
}
527527

528528
if (typeof requestBodyObject.content === 'object') {
@@ -534,6 +534,13 @@ export function getRequestBodyObject(
534534
payloadContentType: 'application/json',
535535
requestBodyObject
536536
}
537+
} else if (
538+
Object.keys(content).includes('application/x-www-form-urlencoded')
539+
) {
540+
return {
541+
payloadContentType: 'application/x-www-form-urlencoded',
542+
requestBodyObject
543+
}
537544
} else {
538545
// Pick first (random) content type
539546
const randomContentType = Object.keys(content)[0]
@@ -587,11 +594,16 @@ export function getRequestSchemaAndNames(
587594
: false
588595

589596
/**
590-
* Edge case: if request body content-type is not application/json, do not
591-
* parse. Instead, treat the request body as a black box (allowing it to be
592-
* defined as a string) and sending it with the appropriate content-type
597+
* Edge case: if request body content-type is not application/json or
598+
* application/x-www-form-urlencoded, do not parse it.
599+
*
600+
* Instead, treat the request body as a black box and send it as a string
601+
* with the proper content-type header
593602
*/
594-
if (payloadContentType !== 'application/json') {
603+
if (
604+
payloadContentType !== 'application/json' &&
605+
payloadContentType !== 'application/x-www-form-urlencoded'
606+
) {
595607
const saneContentTypeName = uncapitalize(
596608
payloadContentType.split('/').reduce((name, term) => {
597609
return name + capitalize(term)
@@ -602,7 +614,7 @@ export function getRequestSchemaAndNames(
602614
fromPath: saneContentTypeName
603615
}
604616

605-
let description = payloadContentType + ' request placeholder object'
617+
let description = `String represents payload of content type '${payloadContentType}'`
606618

607619
if (
608620
'description' in payloadSchema &&
@@ -651,7 +663,7 @@ export function getResponseObject(
651663
oas
652664
) as ResponseObject
653665
} else {
654-
responseObject = (responseObject as any) as ResponseObject
666+
responseObject = responseObject as ResponseObject
655667
}
656668

657669
if (
@@ -719,13 +731,12 @@ export function getResponseSchemaAndNames(
719731
}
720732

721733
/**
722-
* Edge case: if request body content-type is not application/json, do not
723-
* parse. Instead, treat the request body as a black box (allowing it to be
724-
* defined as a string) and sending it with the appropriate content-type
734+
* Edge case: if response body content-type is not application/json, do not
735+
* parse.
725736
*/
726737
if (responseContentType !== 'application/json') {
727738
let description =
728-
'Placeholder object to access non-application/json ' + 'response bodies'
739+
'Placeholder to access non-application/json response bodies'
729740

730741
if (
731742
'description' in responseSchema &&
@@ -748,10 +759,11 @@ export function getResponseSchemaAndNames(
748759
}
749760
} else {
750761
/**
751-
* GraphQL requires that objects must have some properties. To allow some
752-
* operations (such as those with a 204 HTTP code) to be included in the
753-
* GraphQL interface, we added the fillEmptyResponses option, which will
754-
* simply create a placeholder object with a placeholder property.
762+
* GraphQL requires that objects must have some properties.
763+
*
764+
* To allow some operations (such as those with a 204 HTTP code) to be
765+
* included in the GraphQL interface, we added the fillEmptyResponses
766+
* option, which will simply create a placeholder to allow access.
755767
*/
756768
if (options.fillEmptyResponses) {
757769
return {
@@ -761,7 +773,7 @@ export function getResponseSchemaAndNames(
761773
responseContentType: 'application/json',
762774
responseSchema: {
763775
description:
764-
'Placeholder object to support operations with no response schema',
776+
'Placeholder to support operations with no response schema',
765777
type: 'string'
766778
}
767779
}
@@ -841,7 +853,7 @@ export function getEndpointLinks(
841853
}
842854

843855
// Here, we can be certain we have a ResponseObject:
844-
response = (response as any) as ResponseObject
856+
response = response as ResponseObject
845857

846858
if (typeof response.links === 'object') {
847859
const epLinks: LinksObject = response.links
@@ -852,7 +864,7 @@ export function getEndpointLinks(
852864
if (typeof (link as ReferenceObject).$ref === 'string') {
853865
link = resolveRef(link['$ref'], oas)
854866
} else {
855-
link = (link as any) as LinkObject
867+
link = link as LinkObject
856868
}
857869
links[linkKey] = link
858870
}
@@ -892,7 +904,7 @@ export function getParameters(
892904
return resolveRef(p['$ref'], oas) as ParameterObject
893905
} else {
894906
// Here we know we have a parameter object:
895-
return (p as any) as ParameterObject
907+
return p as ParameterObject
896908
}
897909
})
898910
parameters = parameters.concat(pathItemParameters)
@@ -909,7 +921,7 @@ export function getParameters(
909921
return resolveRef(p['$ref'], oas) as ParameterObject
910922
} else {
911923
// Here we know we have a parameter object:
912-
return (p as any) as ParameterObject
924+
return p as ParameterObject
913925
}
914926
})
915927
parameters = parameters.concat(opParameters)
@@ -983,7 +995,7 @@ export function getSecuritySchemes(
983995
) as SecuritySchemeObject
984996
} else {
985997
// We already have a SecuritySchemeObject:
986-
securitySchemes[schemeKey] = (obj as any) as SecuritySchemeObject
998+
securitySchemes[schemeKey] = obj as SecuritySchemeObject
987999
}
9881000
}
9891001
}

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

+20-19
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
*/
99

1010
// Type imports:
11-
import { Oas3, SchemaObject } from './types/oas3'
12-
import { Operation, DataDefinition } from './types/operation'
11+
import { SchemaObject } from './types/oas3'
12+
import { Operation } from './types/operation'
1313
import { ResolveFunction } from './types/graphql'
1414
import { PreprocessingData } from './types/preprocessing_data'
1515
import * as NodeRequest from 'request'
@@ -20,6 +20,7 @@ import * as querystring from 'querystring'
2020
import * as JSONPath from 'jsonpath-plus'
2121
import { debug } from 'debug'
2222
import { GraphQLError } from 'graphql'
23+
import formurlencoded from 'form-urlencoded'
2324

2425
const translationLog = debug('translation')
2526
const httpLog = debug('http')
@@ -247,29 +248,29 @@ export function getResolver({
247248
* lookup here
248249
*/
249250
resolveData.usedPayload = undefined
250-
if (payloadName && typeof payloadName === 'string') {
251+
if (typeof payloadName === 'string') {
251252
// The option genericPayloadArgName will change the payload name to "requestBody"
252253
const sanePayloadName = data.options.genericPayloadArgName
253254
? 'requestBody'
254255
: Oas3Tools.sanitize(payloadName, Oas3Tools.CaseStyle.camelCase)
255256

256-
if (sanePayloadName in args) {
257-
if (typeof args[sanePayloadName] === 'object') {
258-
// We need to desanitize the payload so the API understands it:
259-
const rawPayload = JSON.stringify(
260-
Oas3Tools.desanitizeObjectKeys(args[sanePayloadName], data.saneMap)
261-
)
262-
263-
options.body = rawPayload
264-
resolveData.usedPayload = rawPayload
265-
} else {
266-
// Payload is not an object (stored as an application/json)
267-
const rawPayload = args[sanePayloadName]
268-
269-
options.body = rawPayload
270-
resolveData.usedPayload = rawPayload
271-
}
257+
let rawPayload
258+
if (operation.payloadContentType === 'application/json') {
259+
rawPayload = JSON.stringify(
260+
Oas3Tools.desanitizeObjectKeys(args[sanePayloadName], data.saneMap)
261+
)
262+
} else if (
263+
operation.payloadContentType === 'application/x-www-form-urlencoded'
264+
) {
265+
rawPayload = formurlencoded(
266+
Oas3Tools.desanitizeObjectKeys(args[sanePayloadName], data.saneMap)
267+
)
268+
} else {
269+
// Payload is not an object
270+
rawPayload = args[sanePayloadName]
272271
}
272+
options.body = rawPayload
273+
resolveData.usedPayload = rawPayload
273274
}
274275

275276
/**

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import * as openAPIToGraphQL from '../lib/index'
1313
import { startServer, stopServer } from './example_api5_server'
1414

1515
const oas = require('./fixtures/example_oas5.json')
16-
const PORT = 3005
16+
const PORT = 3007
1717
// Update PORT for this test case:
1818
oas.servers[0].variables.port.default = String(PORT)
1919

0 commit comments

Comments
 (0)