Skip to content

Commit a864beb

Browse files
authored
Merge pull request #1140 from shockey/ft/oas3-style-explode
OAS3: Style/Explode parameter serialization
2 parents c9c8810 + 91b3a35 commit a864beb

File tree

12 files changed

+2971
-381
lines changed

12 files changed

+2971
-381
lines changed

src/execute.js renamed to src/execute/index.js

Lines changed: 51 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,19 @@ import isPlainObject from 'lodash/isPlainObject'
44
import isArray from 'lodash/isArray'
55
import btoa from 'btoa'
66
import url from 'url'
7-
import http, {mergeInQueryOrForm} from './http'
8-
import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod, isOAS3} from './helpers'
9-
import createError from './specmap/lib/create-error'
7+
import http, {mergeInQueryOrForm} from '../http'
8+
import createError from '../specmap/lib/create-error'
9+
10+
import SWAGGER2_PARAMETER_BUILDERS from './swagger2/parameter-builders'
11+
import OAS3_PARAMETER_BUILDERS from './oas3/parameter-builders'
12+
import oas3BuildRequest from './oas3/build-request'
13+
import swagger2BuildRequest from './swagger2/build-request'
14+
import {
15+
getOperationRaw,
16+
idFromPathMethod,
17+
legacyIdFromPathMethod,
18+
isOAS3
19+
} from '../helpers'
1020

1121
const arrayOrEmpty = (ar) => {
1222
return Array.isArray(ar) ? ar : []
@@ -26,18 +36,6 @@ export const self = {
2636
buildRequest
2737
}
2838

29-
// These functions will update the request.
30-
// They'll be given {req, value, paramter, spec, operation}.
31-
32-
33-
export const PARAMETER_BUILDERS = {
34-
body: bodyBuilder,
35-
header: headerBuilder,
36-
query: queryBuilder,
37-
path: pathBuilder,
38-
formData: formDataBuilder
39-
}
40-
4139
// Execute request, with the given operationId and parameters
4240
// pathName/method or operationId is optional
4341
export function execute({
@@ -69,15 +67,39 @@ export function execute({
6967
}
7068

7169
// Build a request, which can be handled by the `http.js` implementation.
72-
export function buildRequest({
73-
spec, operationId, parameters, securities, requestContentType,
74-
responseContentType, parameterBuilders, scheme,
75-
requestInterceptor, responseInterceptor, contextUrl, userFetch,
76-
requestBody, server, serverVariables
77-
}) {
70+
export function buildRequest(options) {
71+
const {
72+
spec,
73+
operationId,
74+
securities,
75+
requestContentType,
76+
responseContentType,
77+
scheme,
78+
requestInterceptor,
79+
responseInterceptor,
80+
contextUrl,
81+
userFetch,
82+
requestBody,
83+
server,
84+
serverVariables
85+
} = options
86+
87+
let {
88+
parameters,
89+
parameterBuilders
90+
} = options
91+
7892
const specIsOAS3 = isOAS3(spec)
7993

80-
parameterBuilders = parameterBuilders || PARAMETER_BUILDERS
94+
if (!parameterBuilders) {
95+
// user did not provide custom parameter builders
96+
if (specIsOAS3) {
97+
parameterBuilders = OAS3_PARAMETER_BUILDERS
98+
}
99+
else {
100+
parameterBuilders = SWAGGER2_PARAMETER_BUILDERS
101+
}
102+
}
81103

82104
// Base Template
83105
let req = {
@@ -165,78 +187,15 @@ export function buildRequest({
165187
}
166188
})
167189

168-
const requestBodyDef = operation.requestBody || {}
169-
const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {})
170-
171-
// for OAS3: set the Content-Type
172-
if (specIsOAS3 && requestBody) {
173-
// does the passed requestContentType appear in the requestBody definition?
174-
const isExplicitContentTypeValid = requestContentType
175-
&& requestBodyMediaTypes.indexOf(requestContentType) > -1
176-
177-
if (requestContentType && isExplicitContentTypeValid) {
178-
req.headers['Content-Type'] = requestContentType
179-
}
180-
else if (!requestContentType) {
181-
const firstMediaType = requestBodyMediaTypes[0]
182-
if (firstMediaType) {
183-
req.headers['Content-Type'] = firstMediaType
184-
requestContentType = firstMediaType
185-
}
186-
}
187-
}
190+
// Do version-specific tasks, then return those results.
191+
const versionSpecificOptions = {...options, operation}
188192

189-
// for OAS3: add requestBody to request
190-
if (specIsOAS3 && requestBody) {
191-
if (requestContentType) {
192-
if (requestBodyMediaTypes.indexOf(requestContentType) > -1) {
193-
// only attach body if the requestBody has a definition for the
194-
// contentType that has been explicitly set
195-
if (requestContentType === 'application/x-www-form-urlencoded') {
196-
if (typeof requestBody === 'object') {
197-
req.form = {}
198-
Object.keys(requestBody).forEach((k) => {
199-
const val = requestBody[k]
200-
req.form[k] = {
201-
value: val
202-
}
203-
})
204-
}
205-
else {
206-
req.form = requestBody
207-
}
208-
}
209-
else {
210-
req.body = requestBody
211-
}
212-
}
213-
}
214-
else {
215-
req.body = requestBody
216-
}
193+
if (specIsOAS3) {
194+
req = oas3BuildRequest(versionSpecificOptions, req)
217195
}
218-
219-
// Add securities, which are applicable
220-
// REVIEW: OAS3: what changed in securities?
221-
req = applySecurities({request: req, securities, operation, spec})
222-
223-
if (!specIsOAS3 && (req.body || req.form)) {
224-
// all following conditionals are Swagger2 only
225-
if (requestContentType) {
226-
req.headers['Content-Type'] = requestContentType
227-
}
228-
else if (Array.isArray(operation.consumes)) {
229-
req.headers['Content-Type'] = operation.consumes[0]
230-
}
231-
else if (Array.isArray(spec.consumes)) {
232-
req.headers['Content-Type'] = spec.consumes[0]
233-
}
234-
else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) {
235-
req.headers['Content-Type'] = 'multipart/form-data'
236-
}
237-
else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) {
238-
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
239-
}
196+
else {
197+
// If not OAS3, then treat as Swagger2.
198+
req = swagger2BuildRequest(versionSpecificOptions, req)
240199
}
241200

242201
// Will add the query object into the URL, if it exists
@@ -246,68 +205,6 @@ export function buildRequest({
246205
return req
247206
}
248207

249-
// Add the body to the request
250-
export function bodyBuilder({req, value, specIsOAS3}) {
251-
if (specIsOAS3) {
252-
return
253-
}
254-
req.body = value
255-
}
256-
257-
// Add a form data object.
258-
export function formDataBuilder({req, value, parameter}) {
259-
// REVIEW: OAS3: check for any parameter changes that affect the builder
260-
req.form = req.form || {}
261-
if (value || parameter.allowEmptyValue) {
262-
req.form[parameter.name] = {
263-
value,
264-
allowEmptyValue: parameter.allowEmptyValue,
265-
collectionFormat: parameter.collectionFormat,
266-
}
267-
}
268-
}
269-
270-
// Add a header to the request
271-
export function headerBuilder({req, parameter, value}) {
272-
// REVIEW: OAS3: check for any parameter changes that affect the builder
273-
req.headers = req.headers || {}
274-
if (typeof value !== 'undefined') {
275-
req.headers[parameter.name] = value
276-
}
277-
}
278-
279-
// Replace path paramters, with values ( ie: the URL )
280-
export function pathBuilder({req, value, parameter}) {
281-
// REVIEW: OAS3: check for any parameter changes that affect the builder
282-
req.url = req.url.replace(`{${parameter.name}}`, encodeURIComponent(value))
283-
}
284-
285-
// Add a query to the `query` object, which will later be stringified into the URL's search
286-
export function queryBuilder({req, value, parameter}) {
287-
// REVIEW: OAS3: check for any parameter changes that affect the builder
288-
req.query = req.query || {}
289-
290-
if (value === false && parameter.type === 'boolean') {
291-
value = 'false'
292-
}
293-
294-
if (value === 0 && ['number', 'integer'].indexOf(parameter.type) > -1) {
295-
value = '0'
296-
}
297-
298-
if (value) {
299-
req.query[parameter.name] = {
300-
collectionFormat: parameter.collectionFormat,
301-
value
302-
}
303-
}
304-
else if (parameter.allowEmptyValue) {
305-
const paramName = parameter.name
306-
req.query[paramName] = req.query[paramName] || {}
307-
req.query[paramName].allowEmptyValue = true
308-
}
309-
}
310-
311208
const stripNonAlpha = str => (str ? str.replace(/\W/g, '') : null)
312209

313210
export function baseUrl(obj) {
@@ -389,59 +286,3 @@ function swagger2BaseUrl({spec, scheme, contextUrl = ''}) {
389286

390287
return ''
391288
}
392-
393-
394-
// Add security values, to operations - that declare their need on them
395-
export function applySecurities({request, securities = {}, operation = {}, spec}) {
396-
const result = assign({}, request)
397-
const {authorized = {}, specSecurity = []} = securities
398-
const security = operation.security || specSecurity
399-
const isAuthorized = authorized && !!Object.keys(authorized).length
400-
const securityDef = spec.securityDefinitions
401-
402-
result.headers = result.headers || {}
403-
result.query = result.query || {}
404-
405-
if (!Object.keys(securities).length || !isAuthorized || !security ||
406-
(Array.isArray(operation.security) && !operation.security.length)) {
407-
return request
408-
}
409-
410-
security.forEach((securityObj, index) => {
411-
for (const key in securityObj) {
412-
const auth = authorized[key]
413-
if (!auth) {
414-
continue
415-
}
416-
417-
const token = auth.token
418-
const value = auth.value || auth
419-
const schema = securityDef[key]
420-
const {type} = schema
421-
const accessToken = token && token.access_token
422-
const tokenType = token && token.token_type
423-
424-
if (auth) {
425-
if (type === 'apiKey') {
426-
const inType = schema.in === 'query' ? 'query' : 'headers'
427-
result[inType] = result[inType] || {}
428-
result[inType][schema.name] = value
429-
}
430-
else if (type === 'basic') {
431-
if (value.header) {
432-
result.headers.authorization = value.header
433-
}
434-
else {
435-
value.base64 = btoa(`${value.username}:${value.password}`)
436-
result.headers.authorization = `Basic ${value.base64}`
437-
}
438-
}
439-
else if (type === 'oauth2' && accessToken) {
440-
result.headers.authorization = `${tokenType || 'Bearer'} ${accessToken}`
441-
}
442-
}
443-
}
444-
})
445-
446-
return result
447-
}

src/execute/oas3/build-request.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// This function runs after the common function,
2+
// `src/execute/index.js#buildRequest`
3+
4+
export default function (options, req) {
5+
const {
6+
operation,
7+
requestBody
8+
} = options
9+
10+
let {
11+
requestContentType
12+
} = options
13+
14+
const requestBodyDef = operation.requestBody || {}
15+
const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {})
16+
17+
// for OAS3: set the Content-Type
18+
if (requestBody) {
19+
// does the passed requestContentType appear in the requestBody definition?
20+
const isExplicitContentTypeValid = requestContentType
21+
&& requestBodyMediaTypes.indexOf(requestContentType) > -1
22+
23+
if (requestContentType && isExplicitContentTypeValid) {
24+
req.headers['Content-Type'] = requestContentType
25+
}
26+
else if (!requestContentType) {
27+
const firstMediaType = requestBodyMediaTypes[0]
28+
if (firstMediaType) {
29+
req.headers['Content-Type'] = firstMediaType
30+
requestContentType = firstMediaType
31+
}
32+
}
33+
}
34+
35+
// for OAS3: add requestBody to request
36+
if (requestBody) {
37+
if (requestContentType) {
38+
if (requestBodyMediaTypes.indexOf(requestContentType) > -1) {
39+
// only attach body if the requestBody has a definition for the
40+
// contentType that has been explicitly set
41+
if (requestContentType === 'application/x-www-form-urlencoded') {
42+
if (typeof requestBody === 'object') {
43+
req.form = {}
44+
Object.keys(requestBody).forEach((k) => {
45+
const val = requestBody[k]
46+
req.form[k] = {
47+
value: val
48+
}
49+
})
50+
}
51+
else {
52+
req.form = requestBody
53+
}
54+
}
55+
else {
56+
req.body = requestBody
57+
}
58+
}
59+
}
60+
else {
61+
req.body = requestBody
62+
}
63+
}
64+
65+
return req
66+
}

0 commit comments

Comments
 (0)