Skip to content

Commit e2daa82

Browse files
UzlopakFdawgsEomm
authored
make it possible to passthrough functions to the swagger-initializer.js (#37)
* make it possible to passthrough functions to the swagger-initializer.js * Apply suggestions from code review Co-authored-by: Frazer Smith <[email protected]> * add unit tests, add Map and Set support * improve typings and fix readme.md * Update README.md * Update README.md Co-authored-by: Frazer Smith <[email protected]> * add comments ot initOAuth * simplify swaggerInitializer * Update lib/routes.js Co-authored-by: Manuel Spigolon <[email protected]> * unnamed symbols should also be serializable * improve documentation regarding uiConfig * add more tests * rename file --------- Co-authored-by: Frazer Smith <[email protected]> Co-authored-by: Manuel Spigolon <[email protected]>
1 parent 96d715e commit e2daa82

File tree

10 files changed

+741
-262
lines changed

10 files changed

+741
-262
lines changed

README.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ await fastify.ready()
108108
| transformStaticCSP | undefined | Synchronous function to transform CSP header for static resources if the header has been previously set. |
109109
| transformSpecification | undefined | Synchronous function to transform the swagger document. |
110110
| transformSpecificationClone| true | Provide a deepcloned swaggerObject to transformSpecification |
111-
| uiConfig | {} | Configuration options for [Swagger UI](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md). Must be literal values, see [#5710](https://github.com/swagger-api/swagger-ui/issues/5710). |
111+
| uiConfig | {} | Configuration options for [Swagger UI](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md). |
112112
| uiHooks | {} | Additional hooks for the documentation's routes. You can provide the `onRequest` and `preHandler` hooks with the same [route's options](https://www.fastify.io/docs/latest/Routes/#options) interface.|
113113
| logLevel | info | Allow to define route log level. |
114114

@@ -121,6 +121,32 @@ The plugin will expose the documentation with the following APIs:
121121
| `'/documentation/'` | The swagger UI |
122122
| `'/documentation/*'` | External files that you may use in `$ref` |
123123

124+
#### uiConfig
125+
126+
To configure Swagger UI, you need to modify the `uiConfig` option.
127+
It's important to ensure that functions are self-contained. Keep in mind that
128+
you cannot modify the backend code within the `uiConfig` functions, as these
129+
functions are processed only by the browser. You can reference the Swagger UI
130+
element using `ui`, which is assigned to `window.ui`.
131+
132+
##### Example
133+
```js
134+
const fastify = require('fastify')()
135+
136+
await fastify.register(require('@fastify/swagger'))
137+
138+
await fastify.register(require('@fastify/swagger-ui'), {
139+
uiConfig: {
140+
onComplete: function () {
141+
alert('ui has type of ' + typeof ui) // 'ui has type of object'
142+
alert('fastify has type of ' + typeof fastify) // 'fastify has type of undefined'
143+
alert('window has type of ' + typeof window) // 'window has type of object'
144+
alert('global has type of ' + typeof global) // 'global has type of undefined'
145+
}
146+
}
147+
})
148+
```
149+
124150
#### transformSpecification
125151

126152
There can be use cases, where you want to modify the swagger definition on request. E.g. you want to modify the server

lib/routes.js

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const path = require('path')
44
const yaml = require('yaml')
55
const fastifyStatic = require('@fastify/static')
66
const rfdc = require('rfdc')()
7+
const swaggerInitializer = require('./swagger-initializer')
78

89
// URI prefix to separate static assets for swagger UI
910
const staticPrefix = '/static'
@@ -74,23 +75,17 @@ function fastifySwagger (fastify, opts, done) {
7475
}
7576
})
7677

77-
fastify.route({
78-
url: '/uiConfig',
79-
method: 'GET',
80-
schema: { hide: true },
81-
...hooks,
82-
handler: (req, reply) => {
83-
reply.send(opts.uiConfig)
84-
}
85-
})
78+
const swaggerInitializerContent = swaggerInitializer(opts)
8679

8780
fastify.route({
88-
url: '/initOAuth',
81+
url: `${staticPrefix}/swagger-initializer.js`,
8982
method: 'GET',
9083
schema: { hide: true },
9184
...hooks,
9285
handler: (req, reply) => {
93-
reply.send(opts.initOAuth)
86+
reply
87+
.header('content-type', 'application/javascript; charset=utf-8')
88+
.send(swaggerInitializerContent)
9489
}
9590
})
9691

lib/serialize.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict'
2+
3+
function serialize (value) {
4+
switch (typeof value) {
5+
case 'bigint':
6+
return value.toString() + 'n'
7+
case 'boolean':
8+
return value ? 'true' : 'false'
9+
case 'function':
10+
return value.toString()
11+
case 'number':
12+
return '' + value
13+
case 'object':
14+
if (value === null) {
15+
return 'null'
16+
} else if (Array.isArray(value)) {
17+
return serializeArray(value)
18+
} else if (value instanceof RegExp) {
19+
return `/${value.source}/${value.flags}`
20+
} else if (value instanceof Date) {
21+
return `new Date(${value.getTime()})`
22+
} else if (value instanceof Set) {
23+
return `new Set(${serializeArray(Array.from(value))})`
24+
} else if (value instanceof Map) {
25+
return `new Map(${serializeArray(Array.from(value))})`
26+
} else {
27+
return serializeObject(value)
28+
}
29+
case 'string':
30+
return JSON.stringify(value)
31+
case 'symbol':
32+
return serializeSymbol(value)
33+
case 'undefined':
34+
return 'undefined'
35+
}
36+
}
37+
const symbolRE = /Symbol\((.+)\)/
38+
function serializeSymbol (value) {
39+
return symbolRE.test(value.toString())
40+
? `Symbol("${value.toString().match(symbolRE)[1]}")`
41+
: 'Symbol()'
42+
}
43+
44+
function serializeArray (value) {
45+
let result = '['
46+
const il = value.length
47+
const last = il - 1
48+
for (let i = 0; i < il; ++i) {
49+
result += serialize(value[i])
50+
i !== last && (result += ',')
51+
}
52+
return result + ']'
53+
}
54+
55+
function serializeObject (value) {
56+
let result = '{'
57+
const keys = Object.keys(value)
58+
let i = 0
59+
const il = keys.length
60+
const last = il - 1
61+
for (; i < il; ++i) {
62+
const key = keys[i]
63+
result += `"${key}":${serialize(value[key])}`
64+
i !== last && (result += ',')
65+
}
66+
return result + '}'
67+
}
68+
69+
module.exports = serialize

lib/swagger-initializer.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
'use strict'
2+
3+
const serialize = require('./serialize')
4+
5+
function swaggerInitializer (opts) {
6+
return `window.onload = function () {
7+
function resolveUrl(url) {
8+
const anchor = document.createElement('a')
9+
anchor.href = url
10+
return anchor.href
11+
}
12+
13+
const config = ${serialize(opts.uiConfig)}
14+
const resConfig = Object.assign({}, {
15+
dom_id: '#swagger-ui',
16+
deepLinking: true,
17+
presets: [
18+
SwaggerUIBundle.presets.apis,
19+
SwaggerUIStandalonePreset
20+
],
21+
plugins: [
22+
SwaggerUIBundle.plugins.DownloadUrl
23+
],
24+
layout: "StandaloneLayout"
25+
}, config, {
26+
url: resolveUrl('./json').replace('static/json', 'json'),
27+
oauth2RedirectUrl: resolveUrl('./oauth2-redirect.html')
28+
});
29+
30+
const ui = SwaggerUIBundle(resConfig)
31+
window.ui = ui
32+
ui.initOAuth(${serialize(opts.initOAuth)})
33+
}`
34+
}
35+
36+
module.exports = swaggerInitializer

scripts/prepare-swagger-ui.js

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const filesToCopy = [
1515
'index.html',
1616
'index.css',
1717
'oauth2-redirect.html',
18-
'swagger-initializer.js',
1918
'swagger-ui-bundle.js',
2019
'swagger-ui-bundle.js.map',
2120
'swagger-ui-standalone-preset.js',
@@ -29,55 +28,6 @@ filesToCopy.forEach(filename => {
2928
fse.copySync(`${swaggerUiAssetPath}/${filename}`, resolve(`./static/${filename}`))
3029
})
3130

32-
fse.writeFileSync(resolve(`./${folderName}/swagger-initializer.js`), `window.onload = function () {
33-
function resolveUrl (url) {
34-
const anchor = document.createElement('a')
35-
anchor.href = url
36-
return anchor.href
37-
}
38-
39-
function resolveConfig (cb) {
40-
return fetch(
41-
resolveUrl('./uiConfig').replace('${folderName}/uiConfig', 'uiConfig')
42-
)
43-
.then(res => res.json())
44-
.then((config) => {
45-
const resConfig = Object.assign({}, {
46-
dom_id: '#swagger-ui',
47-
deepLinking: true,
48-
presets: [
49-
SwaggerUIBundle.presets.apis,
50-
SwaggerUIStandalonePreset
51-
],
52-
plugins: [
53-
SwaggerUIBundle.plugins.DownloadUrl
54-
],
55-
layout: "StandaloneLayout"
56-
}, config, {
57-
url: resolveUrl('./json').replace('${folderName}/json', 'json'),
58-
oauth2RedirectUrl: resolveUrl('./oauth2-redirect.html')
59-
});
60-
return cb(resConfig);
61-
})
62-
}
63-
64-
// Begin Swagger UI call region
65-
const buildUi = function (config) {
66-
const ui = SwaggerUIBundle(config)
67-
window.ui = ui
68-
69-
fetch(resolveUrl('./initOAuth').replace('${folderName}/initOAuth', 'initOAuth'))
70-
.then(res => res.json())
71-
.then((config) => {
72-
ui.initOAuth(config);
73-
});
74-
75-
}
76-
// End Swagger UI call region
77-
78-
resolveConfig(buildUi);
79-
}`)
80-
8131
const sha = {
8232
script: [],
8333
style: []

test/route.test.js

Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -74,62 +74,6 @@ test('/documentation/json route', async (t) => {
7474
t.pass('valid swagger object')
7575
})
7676

77-
test('/documentation/uiConfig route', async (t) => {
78-
t.plan(1)
79-
const fastify = Fastify()
80-
81-
const uiConfig = {
82-
docExpansion: 'full'
83-
}
84-
85-
await fastify.register(fastifySwagger, swaggerOption)
86-
await fastify.register(fastifySwaggerUi, { uiConfig })
87-
88-
fastify.get('/', () => {})
89-
fastify.post('/', () => {})
90-
fastify.get('/example', schemaQuerystring, () => {})
91-
fastify.post('/example', schemaBody, () => {})
92-
fastify.get('/parameters/:id', schemaParams, () => {})
93-
fastify.get('/example1', schemaSecurity, () => {})
94-
95-
const res = await fastify.inject({
96-
method: 'GET',
97-
url: '/documentation/uiConfig'
98-
})
99-
100-
const payload = JSON.parse(res.payload)
101-
102-
t.match(payload, uiConfig, 'uiConfig should be valid')
103-
})
104-
105-
test('/documentation/initOAuth route', async (t) => {
106-
t.plan(1)
107-
const fastify = Fastify()
108-
109-
const initOAuth = {
110-
scopes: ['openid', 'profile', 'email', 'offline_access']
111-
}
112-
113-
await fastify.register(fastifySwagger, swaggerOption)
114-
await fastify.register(fastifySwaggerUi, { initOAuth })
115-
116-
fastify.get('/', () => {})
117-
fastify.post('/', () => {})
118-
fastify.get('/example', schemaQuerystring, () => {})
119-
fastify.post('/example', schemaBody, () => {})
120-
fastify.get('/parameters/:id', schemaParams, () => {})
121-
fastify.get('/example1', schemaSecurity, () => {})
122-
123-
const res = await fastify.inject({
124-
method: 'GET',
125-
url: '/documentation/initOAuth'
126-
})
127-
128-
const payload = JSON.parse(res.payload)
129-
130-
t.match(payload, initOAuth, 'initOAuth should be valid')
131-
})
132-
13377
test('fastify.swagger should return a valid swagger yaml', async (t) => {
13478
t.plan(3)
13579
const fastify = Fastify()
@@ -313,7 +257,7 @@ test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t)
313257
})
314258

315259
test('/documentation/static/:file should send back the correct file', async (t) => {
316-
t.plan(22)
260+
t.plan(21)
317261
const fastify = Fastify()
318262

319263
await fastify.register(fastifySwagger, swaggerOption)
@@ -360,14 +304,7 @@ test('/documentation/static/:file should send back the correct file', async (t)
360304
url: '/documentation/static/swagger-initializer.js'
361305
})
362306
t.equal(typeof res.payload, 'string')
363-
t.equal(res.headers['content-type'], 'application/javascript; charset=UTF-8')
364-
t.equal(
365-
readFileSync(
366-
resolve(__dirname, '..', 'static', 'swagger-initializer.js'),
367-
'utf8'
368-
),
369-
res.payload
370-
)
307+
t.equal(res.headers['content-type'], 'application/javascript; charset=utf-8')
371308
t.ok(res.payload.indexOf('resolveUrl') !== -1)
372309
}
373310

0 commit comments

Comments
 (0)