diff --git a/README.md b/README.md
index 6e1af48..a28d686 100644
--- a/README.md
+++ b/README.md
@@ -111,6 +111,7 @@ await fastify.ready()
| baseDir | undefined | Specify the directory where all spec files that are included in the main one using $ref will be located. By default, this is the directory where the main spec file is located. Provided value should be an absolute path without trailing slash. |
| initOAuth | {} | Configuration options for [Swagger UI initOAuth](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/). |
| routePrefix | '/documentation' | Overwrite the default Swagger UI route prefix. |
+ | noRedirect | false | Switches from redirection to `static/index.html` to serving the UI directly at the route prefix for a cleaner url.
| staticCSP | false | Enable CSP header for static resources. |
| transformStaticCSP | undefined | Synchronous function to transform CSP header for static resources if the header has been previously set. |
| transformSpecification | undefined | Synchronous function to transform the swagger document. |
diff --git a/index.js b/index.js
index b0435ea..363110d 100644
--- a/index.js
+++ b/index.js
@@ -22,6 +22,7 @@ async function fastifySwaggerUi (fastify, opts) {
hooks: opts.uiHooks,
theme: opts.theme || {},
logo: logoContent,
+ noRedirect: opts.noRedirect,
...opts
})
}
diff --git a/lib/index-html.js b/lib/index-html.js
index 518c4a6..ab8d9c0 100644
--- a/lib/index-html.js
+++ b/lib/index-html.js
@@ -1,29 +1,30 @@
'use strict'
function indexHtml (opts) {
+ const staticPrefix = opts.noRedirect ? opts.staticPrefix : ''
return `
${opts.theme?.title || 'Swagger UI'}
-
-
- ${opts.theme && opts.theme.css ? opts.theme.css.map(css => ` \n`).join('') : ''}
+
+
+ ${opts.theme && opts.theme.css ? opts.theme.css.map(css => ` \n`).join('') : ''}
${opts.theme && opts.theme.favicon
-? opts.theme.favicon.map(favicon => ` \n`).join('')
+? opts.theme.favicon.map(favicon => ` \n`).join('')
: `
-
-
+
+
`}
-
-
-
- ${opts.theme && opts.theme.js ? opts.theme.js.map(js => `\n`).join('') : ''}
+
+
+
+ ${opts.theme && opts.theme.js ? opts.theme.js.map(js => `\n`).join('') : ''}
`
diff --git a/lib/routes.js b/lib/routes.js
index a179ff8..73748fd 100644
--- a/lib/routes.js
+++ b/lib/routes.js
@@ -66,15 +66,43 @@ function fastifySwagger (fastify, opts, done) {
}
}
- fastify.route({
- url: '/',
- method: 'GET',
- schema: { hide: true },
- ...hooks,
- handler: (req, reply) => {
- reply.redirect(getRedirectPathForTheRootRoute(req.raw.url))
- }
- })
+ const indexHtmlContent = indexHtml({ ...opts, staticPrefix })
+
+ if (opts.noRedirect) {
+ fastify.route({
+ url: '/',
+ method: 'GET',
+ schema: { hide: true },
+ ...hooks,
+ handler: (req, reply) => {
+ reply
+ .header('content-type', 'text/html; charset=utf-8')
+ .send(indexHtmlContent)
+ }
+ })
+ } else {
+ fastify.route({
+ url: '/',
+ method: 'GET',
+ schema: { hide: true },
+ ...hooks,
+ handler: (req, reply) => {
+ reply.redirect(getRedirectPathForTheRootRoute(req.raw.url))
+ }
+ })
+
+ fastify.route({
+ url: `${staticPrefix}/index.html`,
+ method: 'GET',
+ schema: { hide: true },
+ ...hooks,
+ handler: (req, reply) => {
+ reply
+ .header('content-type', 'text/html; charset=utf-8')
+ .send(indexHtmlContent)
+ }
+ })
+ }
if (opts.theme) {
const themePrefix = `${staticPrefix}/theme`
@@ -127,20 +155,6 @@ function fastifySwagger (fastify, opts, done) {
}
}
- const indexHtmlContent = indexHtml(opts)
-
- fastify.route({
- url: `${staticPrefix}/index.html`,
- method: 'GET',
- schema: { hide: true },
- ...hooks,
- handler: (req, reply) => {
- reply
- .header('content-type', 'text/html; charset=utf-8')
- .send(indexHtmlContent)
- }
- })
-
const swaggerInitializerContent = swaggerInitializer(opts)
fastify.route({
diff --git a/test/route.test.js b/test/route.test.js
index 56e41aa..0ddc44b 100644
--- a/test/route.test.js
+++ b/test/route.test.js
@@ -122,6 +122,29 @@ test('/documentation should redirect to ./documentation/static/index.html', asyn
t.equal(typeof res.payload, 'string')
})
+test('/documentation should display index html when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { noRedirect: true })
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', schemaQuerystring, () => {})
+ fastify.post('/example', schemaBody, () => {})
+ fastify.get('/parameters/:id', schemaParams, () => {})
+ fastify.get('/example1', schemaSecurity, () => {})
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/documentation'
+ })
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
+ t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
+})
+
test('/documentation/ should redirect to ./static/index.html', async (t) => {
t.plan(3)
const fastify = Fastify()
@@ -144,6 +167,29 @@ test('/documentation/ should redirect to ./static/index.html', async (t) => {
t.equal(typeof res.payload, 'string')
})
+test('/documentation/ should display index html when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { noRedirect: true })
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', schemaQuerystring, () => {})
+ fastify.post('/example', schemaBody, () => {})
+ fastify.get('/parameters/:id', schemaParams, () => {})
+ fastify.get('/example1', schemaSecurity, () => {})
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/documentation'
+ })
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
+ t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
+})
+
test('/v1/documentation should redirect to ./documentation/static/index.html', async (t) => {
t.plan(3)
const fastify = Fastify()
@@ -166,6 +212,29 @@ test('/v1/documentation should redirect to ./documentation/static/index.html', a
t.equal(typeof res.payload, 'string')
})
+test('/v1/documentation should display index html when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { routePrefix: '/v1/documentation', noRedirect: true })
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', schemaQuerystring, () => {})
+ fastify.post('/example', schemaBody, () => {})
+ fastify.get('/parameters/:id', schemaParams, () => {})
+ fastify.get('/example1', schemaSecurity, () => {})
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/v1/documentation'
+ })
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
+ t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
+})
+
test('/v1/documentation/ should redirect to ./static/index.html', async (t) => {
t.plan(3)
const fastify = Fastify()
@@ -188,6 +257,29 @@ test('/v1/documentation/ should redirect to ./static/index.html', async (t) => {
t.equal(typeof res.payload, 'string')
})
+test('/v1/documentation/ should display index html when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { routePrefix: '/v1/documentation', noRedirect: true })
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', schemaQuerystring, () => {})
+ fastify.post('/example', schemaBody, () => {})
+ fastify.get('/parameters/:id', schemaParams, () => {})
+ fastify.get('/example1', schemaSecurity, () => {})
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/v1/documentation/'
+ })
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
+ t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
+})
+
test('/v1/foobar should redirect to ./foobar/static/index.html - in plugin', async (t) => {
t.plan(3)
const fastify = Fastify()
@@ -213,6 +305,32 @@ test('/v1/foobar should redirect to ./foobar/static/index.html - in plugin', asy
t.equal(typeof res.payload, 'string')
})
+test('/v1/foobar should display index html when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+
+ fastify.register(async function (fastify, options) {
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { routePrefix: '/foobar', noRedirect: true })
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', schemaQuerystring, () => {})
+ fastify.post('/example', schemaBody, () => {})
+ fastify.get('/parameters/:id', schemaParams, () => {})
+ fastify.get('/example1', schemaSecurity, () => {})
+ }, { prefix: '/v1' })
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/v1/foobar'
+ })
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
+ t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
+})
+
test('/v1/foobar/ should redirect to ./static/index.html - in plugin', async (t) => {
t.plan(3)
const fastify = Fastify()
@@ -238,6 +356,32 @@ test('/v1/foobar/ should redirect to ./static/index.html - in plugin', async (t)
t.equal(typeof res.payload, 'string')
})
+test('/v1/foobar/ should display index html when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+
+ fastify.register(async function (fastify, options) {
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { routePrefix: '/foobar', noRedirect: true })
+
+ fastify.get('/', () => {})
+ fastify.post('/', () => {})
+ fastify.get('/example', schemaQuerystring, () => {})
+ fastify.post('/example', schemaBody, () => {})
+ fastify.get('/parameters/:id', schemaParams, () => {})
+ fastify.get('/example1', schemaSecurity, () => {})
+ }, { prefix: '/v1' })
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/v1/foobar/'
+ })
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
+ t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
+})
+
test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t) => {
t.plan(3)
const fastify = Fastify()
@@ -256,6 +400,25 @@ test('with routePrefix: \'/\' should redirect to ./static/index.html', async (t)
t.equal(typeof res.payload, 'string')
})
+test('with routePrefix: \'/\' should display index html when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { routePrefix: '/', noRedirect: true })
+
+ fastify.get('/foo', () => {})
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/'
+ })
+ t.equal(res.statusCode, 200)
+ t.equal(res.headers.location, undefined)
+ t.equal(typeof res.payload, 'string')
+ t.equal('text/html; charset=utf-8', res.headers['content-type'])
+})
+
test('/documentation/static/:file should send back the correct file', async (t) => {
t.plan(21)
const fastify = Fastify()
@@ -543,3 +706,37 @@ test('should return empty log level of route /documentation', async (t) => {
t.equal(res.statusCode, 302)
t.equal(res.headers.location, './static/index.html')
})
+
+test('/documentation/static/index.html should display index html with correct asset urls', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] } })
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/documentation/static/index.html'
+ })
+
+ t.equal(res.payload.includes('href="./static/index.css"'), false)
+ t.equal(res.payload.includes('src="./static/theme/theme-js.js"'), false)
+ t.equal(res.payload.includes('href="./index.css"'), true)
+ t.equal(res.payload.includes('src="./theme/theme-js.js"'), true)
+})
+
+test('/documentation should display index html with correct asset urls when noRedirect is true', async (t) => {
+ t.plan(4)
+ const fastify = Fastify()
+ await fastify.register(fastifySwagger, swaggerOption)
+ await fastify.register(fastifySwaggerUi, { noRedirect: true, theme: { js: [{ filename: 'theme-js.js' }] } })
+
+ const res = await fastify.inject({
+ method: 'GET',
+ url: '/documentation'
+ })
+
+ t.equal(res.payload.includes('href="./static/index.css"'), true)
+ t.equal(res.payload.includes('src="./static/theme/theme-js.js"'), true)
+ t.equal(res.payload.includes('href="./index.css"'), false)
+ t.equal(res.payload.includes('src="./theme/theme-js.js"'), false)
+})