Skip to content

Commit

Permalink
Merge pull request #1140 from shockey/ft/oas3-style-explode
Browse files Browse the repository at this point in the history
OAS3: Style/Explode parameter serialization
  • Loading branch information
shockey committed Sep 29, 2017
2 parents c9c8810 + 91b3a35 commit a864beb
Show file tree
Hide file tree
Showing 12 changed files with 2,971 additions and 381 deletions.
261 changes: 51 additions & 210 deletions src/execute.js → src/execute/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ import isPlainObject from 'lodash/isPlainObject'
import isArray from 'lodash/isArray'
import btoa from 'btoa'
import url from 'url'
import http, {mergeInQueryOrForm} from './http'
import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod, isOAS3} from './helpers'
import createError from './specmap/lib/create-error'
import http, {mergeInQueryOrForm} from '../http'
import createError from '../specmap/lib/create-error'

import SWAGGER2_PARAMETER_BUILDERS from './swagger2/parameter-builders'
import OAS3_PARAMETER_BUILDERS from './oas3/parameter-builders'
import oas3BuildRequest from './oas3/build-request'
import swagger2BuildRequest from './swagger2/build-request'
import {
getOperationRaw,
idFromPathMethod,
legacyIdFromPathMethod,
isOAS3
} from '../helpers'

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

// These functions will update the request.
// They'll be given {req, value, paramter, spec, operation}.


export const PARAMETER_BUILDERS = {
body: bodyBuilder,
header: headerBuilder,
query: queryBuilder,
path: pathBuilder,
formData: formDataBuilder
}

// Execute request, with the given operationId and parameters
// pathName/method or operationId is optional
export function execute({
Expand Down Expand Up @@ -69,15 +67,39 @@ export function execute({
}

// Build a request, which can be handled by the `http.js` implementation.
export function buildRequest({
spec, operationId, parameters, securities, requestContentType,
responseContentType, parameterBuilders, scheme,
requestInterceptor, responseInterceptor, contextUrl, userFetch,
requestBody, server, serverVariables
}) {
export function buildRequest(options) {
const {
spec,
operationId,
securities,
requestContentType,
responseContentType,
scheme,
requestInterceptor,
responseInterceptor,
contextUrl,
userFetch,
requestBody,
server,
serverVariables
} = options

let {
parameters,
parameterBuilders
} = options

const specIsOAS3 = isOAS3(spec)

parameterBuilders = parameterBuilders || PARAMETER_BUILDERS
if (!parameterBuilders) {
// user did not provide custom parameter builders
if (specIsOAS3) {
parameterBuilders = OAS3_PARAMETER_BUILDERS
}
else {
parameterBuilders = SWAGGER2_PARAMETER_BUILDERS
}
}

// Base Template
let req = {
Expand Down Expand Up @@ -165,78 +187,15 @@ export function buildRequest({
}
})

const requestBodyDef = operation.requestBody || {}
const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {})

// for OAS3: set the Content-Type
if (specIsOAS3 && requestBody) {
// does the passed requestContentType appear in the requestBody definition?
const isExplicitContentTypeValid = requestContentType
&& requestBodyMediaTypes.indexOf(requestContentType) > -1

if (requestContentType && isExplicitContentTypeValid) {
req.headers['Content-Type'] = requestContentType
}
else if (!requestContentType) {
const firstMediaType = requestBodyMediaTypes[0]
if (firstMediaType) {
req.headers['Content-Type'] = firstMediaType
requestContentType = firstMediaType
}
}
}
// Do version-specific tasks, then return those results.
const versionSpecificOptions = {...options, operation}

// for OAS3: add requestBody to request
if (specIsOAS3 && requestBody) {
if (requestContentType) {
if (requestBodyMediaTypes.indexOf(requestContentType) > -1) {
// only attach body if the requestBody has a definition for the
// contentType that has been explicitly set
if (requestContentType === 'application/x-www-form-urlencoded') {
if (typeof requestBody === 'object') {
req.form = {}
Object.keys(requestBody).forEach((k) => {
const val = requestBody[k]
req.form[k] = {
value: val
}
})
}
else {
req.form = requestBody
}
}
else {
req.body = requestBody
}
}
}
else {
req.body = requestBody
}
if (specIsOAS3) {
req = oas3BuildRequest(versionSpecificOptions, req)
}

// Add securities, which are applicable
// REVIEW: OAS3: what changed in securities?
req = applySecurities({request: req, securities, operation, spec})

if (!specIsOAS3 && (req.body || req.form)) {
// all following conditionals are Swagger2 only
if (requestContentType) {
req.headers['Content-Type'] = requestContentType
}
else if (Array.isArray(operation.consumes)) {
req.headers['Content-Type'] = operation.consumes[0]
}
else if (Array.isArray(spec.consumes)) {
req.headers['Content-Type'] = spec.consumes[0]
}
else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) {
req.headers['Content-Type'] = 'multipart/form-data'
}
else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) {
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
}
else {
// If not OAS3, then treat as Swagger2.
req = swagger2BuildRequest(versionSpecificOptions, req)
}

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

// Add the body to the request
export function bodyBuilder({req, value, specIsOAS3}) {
if (specIsOAS3) {
return
}
req.body = value
}

// Add a form data object.
export function formDataBuilder({req, value, parameter}) {
// REVIEW: OAS3: check for any parameter changes that affect the builder
req.form = req.form || {}
if (value || parameter.allowEmptyValue) {
req.form[parameter.name] = {
value,
allowEmptyValue: parameter.allowEmptyValue,
collectionFormat: parameter.collectionFormat,
}
}
}

// Add a header to the request
export function headerBuilder({req, parameter, value}) {
// REVIEW: OAS3: check for any parameter changes that affect the builder
req.headers = req.headers || {}
if (typeof value !== 'undefined') {
req.headers[parameter.name] = value
}
}

// Replace path paramters, with values ( ie: the URL )
export function pathBuilder({req, value, parameter}) {
// REVIEW: OAS3: check for any parameter changes that affect the builder
req.url = req.url.replace(`{${parameter.name}}`, encodeURIComponent(value))
}

// Add a query to the `query` object, which will later be stringified into the URL's search
export function queryBuilder({req, value, parameter}) {
// REVIEW: OAS3: check for any parameter changes that affect the builder
req.query = req.query || {}

if (value === false && parameter.type === 'boolean') {
value = 'false'
}

if (value === 0 && ['number', 'integer'].indexOf(parameter.type) > -1) {
value = '0'
}

if (value) {
req.query[parameter.name] = {
collectionFormat: parameter.collectionFormat,
value
}
}
else if (parameter.allowEmptyValue) {
const paramName = parameter.name
req.query[paramName] = req.query[paramName] || {}
req.query[paramName].allowEmptyValue = true
}
}

const stripNonAlpha = str => (str ? str.replace(/\W/g, '') : null)

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

return ''
}


// Add security values, to operations - that declare their need on them
export function applySecurities({request, securities = {}, operation = {}, spec}) {
const result = assign({}, request)
const {authorized = {}, specSecurity = []} = securities
const security = operation.security || specSecurity
const isAuthorized = authorized && !!Object.keys(authorized).length
const securityDef = spec.securityDefinitions

result.headers = result.headers || {}
result.query = result.query || {}

if (!Object.keys(securities).length || !isAuthorized || !security ||
(Array.isArray(operation.security) && !operation.security.length)) {
return request
}

security.forEach((securityObj, index) => {
for (const key in securityObj) {
const auth = authorized[key]
if (!auth) {
continue
}

const token = auth.token
const value = auth.value || auth
const schema = securityDef[key]
const {type} = schema
const accessToken = token && token.access_token
const tokenType = token && token.token_type

if (auth) {
if (type === 'apiKey') {
const inType = schema.in === 'query' ? 'query' : 'headers'
result[inType] = result[inType] || {}
result[inType][schema.name] = value
}
else if (type === 'basic') {
if (value.header) {
result.headers.authorization = value.header
}
else {
value.base64 = btoa(`${value.username}:${value.password}`)
result.headers.authorization = `Basic ${value.base64}`
}
}
else if (type === 'oauth2' && accessToken) {
result.headers.authorization = `${tokenType || 'Bearer'} ${accessToken}`
}
}
}
})

return result
}
66 changes: 66 additions & 0 deletions src/execute/oas3/build-request.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// This function runs after the common function,
// `src/execute/index.js#buildRequest`

export default function (options, req) {
const {
operation,
requestBody
} = options

let {
requestContentType
} = options

const requestBodyDef = operation.requestBody || {}
const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {})

// for OAS3: set the Content-Type
if (requestBody) {
// does the passed requestContentType appear in the requestBody definition?
const isExplicitContentTypeValid = requestContentType
&& requestBodyMediaTypes.indexOf(requestContentType) > -1

if (requestContentType && isExplicitContentTypeValid) {
req.headers['Content-Type'] = requestContentType
}
else if (!requestContentType) {
const firstMediaType = requestBodyMediaTypes[0]
if (firstMediaType) {
req.headers['Content-Type'] = firstMediaType
requestContentType = firstMediaType
}
}
}

// for OAS3: add requestBody to request
if (requestBody) {
if (requestContentType) {
if (requestBodyMediaTypes.indexOf(requestContentType) > -1) {
// only attach body if the requestBody has a definition for the
// contentType that has been explicitly set
if (requestContentType === 'application/x-www-form-urlencoded') {
if (typeof requestBody === 'object') {
req.form = {}
Object.keys(requestBody).forEach((k) => {
const val = requestBody[k]
req.form[k] = {
value: val
}
})
}
else {
req.form = requestBody
}
}
else {
req.body = requestBody
}
}
}
else {
req.body = requestBody
}
}

return req
}
Loading

0 comments on commit a864beb

Please sign in to comment.