Skip to content

Commit 75cab7a

Browse files
committed
Merge branch 'master' of github.com:swagger-api/swagger-js into bug/swagger-ui-3511
# Conflicts: # src/execute.js
2 parents 55b501c + 87f31a4 commit 75cab7a

File tree

19 files changed

+1727
-39
lines changed

19 files changed

+1727
-39
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Swagger-JS
55

66
## New!
77

8-
**This is the new version of swagger-js, 3.x. Want to learn more? Check out our [FAQ](http://swagger.io/new-ui-faq/).**
8+
**This is the new version of swagger-js, 3.x. Want to learn more? Check out our [FAQ](https://github.com/swagger-api/swagger-js/blob/master/docs/MIGRATION_2_X.md).**
99

1010
For the older version of swagger-js, refer to the [*2.x branch*](https://github.com/swagger-api/swagger-js/tree/2.x).
1111

@@ -75,7 +75,8 @@ const request = {
7575
body,
7676
headers,
7777
requestInterceptor,
78-
responseInterceptor
78+
responseInterceptor,
79+
userFetch
7980
}
8081

8182
Swagger.http(request)
@@ -98,6 +99,11 @@ Swagger.http({
9899
responseInterceptor: (res: Response) => Response
99100
})
100101

102+
// Custom Fetch
103+
Swagger.http({
104+
userFetch: (url: String, options: Object) => Promise
105+
})
106+
101107
```
102108

103109
Swagger Specification Resolver
@@ -132,6 +138,7 @@ const params = {
132138
responseContentType,
133139

134140
(http), // You can also override the HTTP client completely
141+
(userFetch), // Alternatively you can just override the fetch method (if you want to use request.js or some other HttpAgent)
135142
}
136143

137144
// Creates a request object compatible with HTTP client interface.

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

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "swagger-client",
3-
"version": "3.0.19",
3+
"version": "3.1.1",
44
"description": "SwaggerJS - a collection of interfaces for OAI specs",
55
"main": "dist/index.js",
66
"repository": "[email protected]:swagger-api/swagger-js.git",
@@ -16,11 +16,11 @@
1616
"deps_check_dir": ".deps_check"
1717
},
1818
"scripts": {
19-
"build": "NODE_ENV=production webpack -p --config ./webpack.config.js",
20-
"build-bundle": "NODE_ENV=production webpack -p --config ./webpack.bundle.config.js",
19+
"build": "cross-env NODE_ENV=production webpack -p --config ./webpack.config.js",
20+
"build-bundle": "cross-env NODE_ENV=production webpack -p --config ./webpack.bundle.config.js",
2121
"watch": "webpack --config webpack.config.js --watch --progress",
2222
"test": "npm run just-test && npm run lint",
23-
"just-test": "NODE_ENV=test node ./node_modules/.bin/_mocha --recursive --compilers js:babel-core/register",
23+
"just-test": "cross-env NODE_ENV=test node ./node_modules/.bin/_mocha --recursive --compilers js:babel-core/register",
2424
"test:watch": "npm run test -- -w",
2525
"lint": "eslint src/ test/",
2626
"deps-license": "license-checker --production --csv --out $npm_package_config_deps_check_dir/licenses.csv && license-checker --development --csv --out $npm_package_config_deps_check_dir/licenses-dev.csv",
@@ -44,6 +44,7 @@
4444
"babel-plugin-transform-runtime": "6.15.0",
4545
"babel-preset-es2015": "^6.22.0",
4646
"clone": "^2.1.0",
47+
"cross-env": "^5.0.5",
4748
"deepmerge": "^1.3.0",
4849
"eslint": "^3.18.0",
4950
"eslint-config-airbnb-base": "^11.1.1",

src/execute.js

Lines changed: 147 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) => {
@@ -28,6 +28,8 @@ export const self = {
2828

2929
// These functions will update the request.
3030
// They'll be given {req, value, paramter, spec, operation}.
31+
32+
3133
export const PARAMETER_BUILDERS = {
3234
body: bodyBuilder,
3335
header: headerBuilder,
@@ -70,13 +72,16 @@ export function execute({
7072
export function buildRequest({
7173
spec, operationId, parameters, securities, requestContentType,
7274
responseContentType, parameterBuilders, scheme,
73-
requestInterceptor, responseInterceptor, contextUrl
75+
requestInterceptor, responseInterceptor, contextUrl, userFetch,
76+
requestBody, server, serverVariables
7477
}) {
78+
const specIsOAS3 = isOAS3(spec)
79+
7580
parameterBuilders = parameterBuilders || PARAMETER_BUILDERS
7681

7782
// Base Template
7883
let req = {
79-
url: baseUrl({spec, scheme, contextUrl}),
84+
url: baseUrl({spec, scheme, contextUrl, server, serverVariables}),
8085
credentials: 'same-origin',
8186
headers: {
8287
// This breaks CORSs... removing this line... probably breaks oAuth. Need to address that
@@ -91,6 +96,9 @@ export function buildRequest({
9196
if (responseInterceptor) {
9297
req.responseInterceptor = responseInterceptor
9398
}
99+
if (userFetch) {
100+
req.userFetch = userFetch
101+
}
94102

95103
// Mostly for testing
96104
if (!operationId) {
@@ -118,6 +126,10 @@ export function buildRequest({
118126
.concat(arrayOrEmpty(operation.parameters)) // operation parameters
119127
.concat(arrayOrEmpty(path.parameters)) // path parameters
120128

129+
// REVIEW: OAS3: have any key names or parameter shapes changed?
130+
// Any new features that need to be plugged in here?
131+
132+
121133
// Add values to request
122134
combinedParameters.forEach((parameter) => {
123135
const builder = parameterBuilders[parameter.in]
@@ -152,24 +164,77 @@ export function buildRequest({
152164
}
153165
})
154166

167+
const requestBodyDef = operation.requestBody || {}
168+
const requestBodyMediaTypes = Object.keys(requestBodyDef.content || {})
169+
170+
// for OAS3: set the Content-Type
171+
if (specIsOAS3 && requestBody) {
172+
// does the passed requestContentType appear in the requestBody definition?
173+
const isExplicitContentTypeValid = requestContentType
174+
&& requestBodyMediaTypes.indexOf(requestContentType) > -1
175+
176+
if (requestContentType && isExplicitContentTypeValid) {
177+
req.headers['Content-Type'] = requestContentType
178+
}
179+
else if (!requestContentType) {
180+
const firstMediaType = requestBodyMediaTypes[0]
181+
if (firstMediaType) {
182+
req.headers['Content-Type'] = firstMediaType
183+
requestContentType = firstMediaType
184+
}
185+
}
186+
}
187+
188+
// for OAS3: add requestBody to request
189+
if (specIsOAS3 && requestBody) {
190+
if (requestContentType) {
191+
if (requestBodyMediaTypes.indexOf(requestContentType) > -1) {
192+
// only attach body if the requestBody has a definition for the
193+
// contentType that has been explicitly set
194+
if (requestContentType === 'application/x-www-form-urlencoded') {
195+
if (typeof requestBody === 'object') {
196+
req.form = {}
197+
Object.keys(requestBody).forEach((k) => {
198+
const val = requestBody[k]
199+
req.form[k] = {
200+
value: val
201+
}
202+
})
203+
}
204+
else {
205+
req.form = requestBody
206+
}
207+
}
208+
else {
209+
req.body = requestBody
210+
}
211+
}
212+
}
213+
else {
214+
req.body = requestBody
215+
}
216+
}
217+
155218
// Add securities, which are applicable
219+
// REVIEW: OAS3: what changed in securities?
156220
req = applySecurities({request: req, securities, operation, spec})
157221

158-
if (req.body || req.form) {
222+
if (!specIsOAS3 && (req.body || req.form)) {
223+
// all following conditionals are Swagger2 only
159224
if (requestContentType) {
160-
req.headers['content-type'] = requestContentType
225+
req.headers['Content-Type'] = requestContentType
161226
}
162227
else if (Array.isArray(operation.consumes)) {
163-
req.headers['content-type'] = operation.consumes[0]
228+
req.headers['Content-Type'] = operation.consumes[0]
164229
}
165230
else if (Array.isArray(spec.consumes)) {
166-
req.headers['content-type'] = spec.consumes[0]
231+
req.headers['Content-Type'] = spec.consumes[0]
167232
}
168-
else if (operation.parameters.filter(p => p.type === 'file').length) {
169-
req.headers['content-type'] = 'multipart/form-data'
233+
else if (operation.parameters && operation.parameters.filter(p => p.type === 'file').length) {
234+
req.headers['Content-Type'] = 'multipart/form-data'
170235
}
171-
else if (operation.parameters.filter(p => p.in === 'formData').length) {
172-
req.headers['content-type'] = 'application/x-www-form-urlencoded'
236+
else if (operation.parameters && operation.parameters.filter(p => p.in === 'formData').length) {
237+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
173238
}
174239
}
175240

@@ -181,12 +246,16 @@ export function buildRequest({
181246
}
182247

183248
// Add the body to the request
184-
export function bodyBuilder({req, value}) {
249+
export function bodyBuilder({req, value, specIsOAS3}) {
250+
if (specIsOAS3) {
251+
return
252+
}
185253
req.body = value
186254
}
187255

188256
// Add a form data object.
189257
export function formDataBuilder({req, value, parameter}) {
258+
// REVIEW: OAS3: check for any parameter changes that affect the builder
190259
req.form = req.form || {}
191260
if (value || parameter.allowEmptyValue) {
192261
req.form[parameter.name] = {
@@ -199,6 +268,7 @@ export function formDataBuilder({req, value, parameter}) {
199268

200269
// Add a header to the request
201270
export function headerBuilder({req, parameter, value}) {
271+
// REVIEW: OAS3: check for any parameter changes that affect the builder
202272
req.headers = req.headers || {}
203273
if (typeof value !== 'undefined') {
204274
req.headers[parameter.name] = value
@@ -207,11 +277,13 @@ export function headerBuilder({req, parameter, value}) {
207277

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

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

217289
if (value === false && parameter.type === 'boolean') {
@@ -237,8 +309,69 @@ export function queryBuilder({req, value, parameter}) {
237309

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

312+
export function baseUrl(obj) {
313+
const specIsOAS3 = isOAS3(obj.spec)
314+
315+
return specIsOAS3 ? oas3BaseUrl(obj) : swagger2BaseUrl(obj)
316+
}
317+
318+
function oas3BaseUrl({spec, server, serverVariables = {}}) {
319+
const servers = spec.servers
320+
321+
let selectedServerUrl = ''
322+
let selectedServerObj = null
323+
324+
if (!servers || !Array.isArray(servers)) {
325+
return ''
326+
}
327+
328+
if (server) {
329+
const serverUrls = servers.map(srv => srv.url)
330+
331+
if (serverUrls.indexOf(server) > -1) {
332+
selectedServerUrl = server
333+
selectedServerObj = servers[serverUrls.indexOf(server)]
334+
}
335+
}
336+
337+
if (!selectedServerUrl) {
338+
// default to the first server if we don't have one by now
339+
selectedServerUrl = servers[0].url
340+
selectedServerObj = servers[0]
341+
}
342+
343+
if (selectedServerUrl.indexOf('{') > -1) {
344+
// do variable substitution
345+
const varNames = getVariableTemplateNames(selectedServerUrl)
346+
varNames.forEach((vari) => {
347+
if (selectedServerObj.variables && selectedServerObj.variables[vari]) {
348+
// variable is defined in server
349+
const variableDefinition = selectedServerObj.variables[vari]
350+
const variableValue = serverVariables[vari] || variableDefinition.default
351+
352+
const re = new RegExp(`{${vari}}`, 'g')
353+
selectedServerUrl = selectedServerUrl.replace(re, variableValue)
354+
}
355+
})
356+
}
357+
358+
return selectedServerUrl
359+
}
360+
361+
function getVariableTemplateNames(str) {
362+
const results = []
363+
const re = /{([^}]+)}/g
364+
let text
365+
366+
// eslint-disable-next-line no-cond-assign
367+
while (text = re.exec(str)) {
368+
results.push(text[1])
369+
}
370+
return results
371+
}
372+
240373
// Compose the baseUrl ( scheme + host + basePath )
241-
export function baseUrl({spec, scheme, contextUrl = ''}) {
374+
function swagger2BaseUrl({spec, scheme, contextUrl = ''}) {
242375
const parsedContextUrl = url.parse(contextUrl)
243376
const firstSchemeInSpec = Array.isArray(spec.schemes) ? spec.schemes[0] : null
244377

@@ -302,7 +435,7 @@ export function applySecurities({request, securities = {}, operation = {}, spec}
302435
result.headers.authorization = `Basic ${value.base64}`
303436
}
304437
}
305-
else if (type === 'oauth2') {
438+
else if (type === 'oauth2' && accessToken) {
306439
result.headers.authorization = `${tokenType || 'Bearer'} ${accessToken}`
307440
}
308441
}

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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export default function http(url, request = {}) {
3737
delete request.headers['Content-Type']
3838
}
3939

40-
return fetch(request.url, request).then((res) => { // eslint-disable-line no-undef
40+
// eslint-disable-next-line no-undef
41+
return (request.userFetch || fetch)(request.url, request).then((res) => {
4142
const serialized = self.serializeRes(res, url, request).then((_res) => {
4243
if (request.responseInterceptor) {
4344
_res = request.responseInterceptor(_res) || _res
@@ -134,6 +135,7 @@ function isFile(obj) {
134135
}
135136

136137
function formatValue({value, collectionFormat, allowEmptyValue}, skipEncoding) {
138+
// REVIEW: OAS3: usage of this fn for compatibility w/ new value formats
137139
const SEPARATORS = {
138140
csv: ',',
139141
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)