Skip to content

Commit cbd72aa

Browse files
authored
Merge pull request #1127 from swagger-api/ft/oas3-tio
OAS3: Try-It-Out
2 parents ae58f61 + 430efb2 commit cbd72aa

File tree

13 files changed

+1495
-28
lines changed

13 files changed

+1495
-28
lines changed

examples/oas3.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// TODO: OAS3: provide examples
2+
// requestBody, etc

src/execute.js

Lines changed: 144 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import isArray from 'lodash/isArray'
55
import btoa from 'btoa'
66
import url from 'url'
77
import http, {mergeInQueryOrForm} from './http'
8-
import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod} from './helpers'
8+
import {getOperationRaw, idFromPathMethod, legacyIdFromPathMethod, isOAS3} from './helpers'
99
import createError from './specmap/lib/create-error'
1010

1111
const arrayOrEmpty = (ar) => {
@@ -24,6 +24,8 @@ export const self = {
2424

2525
// These functions will update the request.
2626
// They'll be given {req, value, paramter, spec, operation}.
27+
28+
2729
export const PARAMETER_BUILDERS = {
2830
body: bodyBuilder,
2931
header: headerBuilder,
@@ -66,13 +68,16 @@ export function execute({
6668
export function buildRequest({
6769
spec, operationId, parameters, securities, requestContentType,
6870
responseContentType, parameterBuilders, scheme,
69-
requestInterceptor, responseInterceptor, contextUrl, userFetch
71+
requestInterceptor, responseInterceptor, contextUrl, userFetch,
72+
requestBody, server, serverVariables
7073
}) {
74+
const specIsOAS3 = isOAS3(spec)
75+
7176
parameterBuilders = parameterBuilders || PARAMETER_BUILDERS
7277

7378
// Base Template
7479
let req = {
75-
url: baseUrl({spec, scheme, contextUrl}),
80+
url: baseUrl({spec, scheme, contextUrl, server, serverVariables}),
7681
credentials: 'same-origin',
7782
headers: {
7883
// This breaks CORSs... removing this line... probably breaks oAuth. Need to address that
@@ -120,6 +125,9 @@ export function buildRequest({
120125
const builder = parameterBuilders[parameter.in]
121126
let value
122127

128+
// REVIEW: OAS3: have any key names or parameter shapes changed?
129+
// Any new features that need to be plugged in here?
130+
123131
if (parameter.in === 'body' && parameter.schema && parameter.schema.properties) {
124132
value = parameters
125133
}
@@ -135,28 +143,82 @@ export function buildRequest({
135143
}
136144

137145
if (builder) {
138-
builder({req, parameter, value, operation, spec})
146+
builder({req, parameter, value, operation, spec, specIsOAS3})
139147
}
140148
})
141149

150+
const requestBodyDef = operation.requestBody || {}
151+
const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {})
152+
153+
// for OAS3: set the Content-Type
154+
if (specIsOAS3 && requestBody) {
155+
// does the passed requestContentType appear in the requestBody definition?
156+
const isExplicitContentTypeValid = requestContentType
157+
&& requestBodyMediaTypes.indexOf(requestContentType) > -1
158+
159+
if (requestContentType && isExplicitContentTypeValid) {
160+
req.headers['Content-Type'] = requestContentType
161+
}
162+
else if (!requestContentType) {
163+
const firstMediaType = requestBodyMediaTypes[0]
164+
if (firstMediaType) {
165+
req.headers['Content-Type'] = firstMediaType
166+
requestContentType = firstMediaType
167+
}
168+
}
169+
}
170+
171+
// for OAS3: add requestBody to request
172+
if (specIsOAS3 && requestBody) {
173+
if (requestContentType) {
174+
if (requestBodyMediaTypes.indexOf(requestContentType) > -1) {
175+
// only attach body if the requestBody has a definition for the
176+
// contentType that has been explicitly set
177+
if (requestContentType === 'application/x-www-form-urlencoded') {
178+
if (typeof requestBody === 'object') {
179+
req.form = {}
180+
Object.keys(requestBody).forEach((k) => {
181+
const val = requestBody[k]
182+
req.form[k] = {
183+
value: val
184+
}
185+
})
186+
}
187+
else {
188+
req.form = requestBody
189+
}
190+
}
191+
else {
192+
req.body = requestBody
193+
}
194+
}
195+
}
196+
else {
197+
req.body = requestBody
198+
}
199+
}
200+
201+
142202
// Add securities, which are applicable
203+
// REVIEW: OAS3: what changed in securities?
143204
req = applySecurities({request: req, securities, operation, spec})
144205

145-
if (req.body || req.form) {
206+
if (!specIsOAS3 && (req.body || req.form)) {
207+
// all following conditionals are Swagger2 only
146208
if (requestContentType) {
147-
req.headers['content-type'] = requestContentType
209+
req.headers['Content-Type'] = requestContentType
148210
}
149211
else if (Array.isArray(operation.consumes)) {
150-
req.headers['content-type'] = operation.consumes[0]
212+
req.headers['Content-Type'] = operation.consumes[0]
151213
}
152214
else if (Array.isArray(spec.consumes)) {
153-
req.headers['content-type'] = spec.consumes[0]
215+
req.headers['Content-Type'] = spec.consumes[0]
154216
}
155-
else if (operation.parameters.filter(p => p.type === 'file').length) {
156-
req.headers['content-type'] = 'multipart/form-data'
217+
else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) {
218+
req.headers['Content-Type'] = 'multipart/form-data'
157219
}
158-
else if (operation.parameters.filter(p => p.in === 'formData').length) {
159-
req.headers['content-type'] = 'application/x-www-form-urlencoded'
220+
else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) {
221+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
160222
}
161223
}
162224

@@ -168,12 +230,16 @@ export function buildRequest({
168230
}
169231

170232
// Add the body to the request
171-
export function bodyBuilder({req, value}) {
233+
export function bodyBuilder({req, value, specIsOAS3}) {
234+
if (specIsOAS3) {
235+
return
236+
}
172237
req.body = value
173238
}
174239

175240
// Add a form data object.
176241
export function formDataBuilder({req, value, parameter}) {
242+
// REVIEW: OAS3: check for any parameter changes that affect the builder
177243
req.form = req.form || {}
178244
if (value || parameter.allowEmptyValue) {
179245
req.form[parameter.name] = {
@@ -186,6 +252,7 @@ export function formDataBuilder({req, value, parameter}) {
186252

187253
// Add a header to the request
188254
export function headerBuilder({req, parameter, value}) {
255+
// REVIEW: OAS3: check for any parameter changes that affect the builder
189256
req.headers = req.headers || {}
190257
if (typeof value !== 'undefined') {
191258
req.headers[parameter.name] = value
@@ -194,11 +261,13 @@ export function headerBuilder({req, parameter, value}) {
194261

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

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

204273
if (value === false && parameter.type === 'boolean') {
@@ -224,8 +293,69 @@ export function queryBuilder({req, value, parameter}) {
224293

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

296+
export function baseUrl(obj) {
297+
const specIsOAS3 = isOAS3(obj.spec)
298+
299+
return specIsOAS3 ? oas3BaseUrl(obj) : swagger2BaseUrl(obj)
300+
}
301+
302+
function oas3BaseUrl({spec, server, serverVariables = {}}) {
303+
const servers = spec.servers
304+
305+
let selectedServerUrl = ''
306+
let selectedServerObj = null
307+
308+
if (!servers || !Array.isArray(servers)) {
309+
return ''
310+
}
311+
312+
if (server) {
313+
const serverUrls = servers.map(srv => srv.url)
314+
315+
if (serverUrls.indexOf(server) > -1) {
316+
selectedServerUrl = server
317+
selectedServerObj = servers[serverUrls.indexOf(server)]
318+
}
319+
}
320+
321+
if (!selectedServerUrl) {
322+
// default to the first server if we don't have one by now
323+
selectedServerUrl = servers[0].url
324+
selectedServerObj = servers[0]
325+
}
326+
327+
if (selectedServerUrl.indexOf('{') > -1) {
328+
// do variable substitution
329+
const varNames = getVariableTemplateNames(selectedServerUrl)
330+
varNames.forEach((vari) => {
331+
if (selectedServerObj.variables && selectedServerObj.variables[vari]) {
332+
// variable is defined in server
333+
const variableDefinition = selectedServerObj.variables[vari]
334+
const variableValue = serverVariables[vari] || variableDefinition.default
335+
336+
const re = new RegExp(`{${vari}}`, 'g')
337+
selectedServerUrl = selectedServerUrl.replace(re, variableValue)
338+
}
339+
})
340+
}
341+
342+
return selectedServerUrl
343+
}
344+
345+
function getVariableTemplateNames(str) {
346+
const results = []
347+
const re = /{([^}]+)}/g
348+
let text
349+
350+
// eslint-disable-next-line no-cond-assign
351+
while (text = re.exec(str)) {
352+
results.push(text[1])
353+
}
354+
return results
355+
}
356+
227357
// Compose the baseUrl ( scheme + host + basePath )
228-
export function baseUrl({spec, scheme, contextUrl = ''}) {
358+
function swagger2BaseUrl({spec, scheme, contextUrl = ''}) {
229359
const parsedContextUrl = url.parse(contextUrl)
230360
const firstSchemeInSpec = Array.isArray(spec.schemes) ? spec.schemes[0] : null
231361

src/helpers.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@ const escapeString = (str) => {
55
return str.replace(/[^\w]/gi, '_')
66
}
77

8+
// Spec version detection
9+
export function isOAS3(spec) {
10+
const oasVersion = spec.openapi
11+
if (!oasVersion) {
12+
return false
13+
}
14+
15+
return oasVersion.startsWith('3.0.0')
16+
}
17+
18+
export function isSwagger2(spec) {
19+
const swaggerVersion = spec.swagger
20+
if (!swaggerVersion) {
21+
return false
22+
}
23+
24+
return swaggerVersion.startsWith('2')
25+
}
26+
827
// Strategy for determining operationId
928
export function opId(operation, pathName, method = '') {
1029
const idWithoutWhitespace = (operation.operationId || '').replace(/\s/g, '')
@@ -86,6 +105,9 @@ export function eachOperation(spec, cb, find) {
86105
}
87106
}
88107

108+
// REVIEW: OAS3: identify normalization steps that need changes
109+
// ...maybe create `normalizeOAS3`?
110+
89111
export function normalizeSwagger(parsedSpec) {
90112
const {spec} = parsedSpec
91113
const {paths} = spec

src/http.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ function isFile(obj) {
135135
}
136136

137137
function formatValue({value, collectionFormat, allowEmptyValue}, skipEncoding) {
138+
// REVIEW: OAS3: usage of this fn for compatibility w/ new value formats
138139
const SEPARATORS = {
139140
csv: ',',
140141
ssv: '%20',

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Swagger.prototype = {
8181
Swagger.prototype.applyDefaults = function () {
8282
const spec = this.spec
8383
const specUrl = this.url
84+
// TODO: OAS3: support servers here
8485
if (specUrl && specUrl.startsWith('http')) {
8586
const parsed = Url.parse(specUrl)
8687
if (!spec.host) {

0 commit comments

Comments
 (0)