Skip to content

Commit

Permalink
feature: Fixes fastify#180 and others
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidTanner committed Nov 6, 2024
1 parent a5d6ed7 commit 3d7c312
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 74 deletions.
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,20 @@ await fastify.ready()

#### Options

| Option | Default | Description |
| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
| 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. |
| 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. |
| transformSpecificationClone| true | Provide a deepcloned swaggerObject to transformSpecification |
| uiConfig | {} | Configuration options for [Swagger UI](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md). |
| uiHooks | {} | Additional hooks for the documentation's routes. You can provide the `onRequest` and `preHandler` hooks with the same [route's options](https://fastify.dev/docs/latest/Reference/Routes/#routes-options) interface.|
| theme | {} | Add custom JavaScript and CSS to the Swagger UI web page |
| logLevel | info | Allow to define route log level. |
| Option | Default | Description |
| ------------------ | --------------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 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. |
| indexPrefix | '' | Add an additional prefix. This is for when the Fastify server is behind path based routing. ex. NGINX |
| 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. |
| transformSpecificationClone| true | Provide a deepcloned swaggerObject to transformSpecification |
| uiConfig | {} | Configuration options for [Swagger UI](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md). |
| uiHooks | {} | Additional hooks for the documentation's routes. You can provide the `onRequest` and `preHandler` hooks with the same [route's options](https://fastify.dev/docs/latest/Reference/Routes/#routes-options) interface. |
| theme | {} | Add custom JavaScript and CSS to the Swagger UI web page |
| logLevel | info | Allow to define route log level. |

The plugin will expose the documentation with the following APIs:

Expand Down
7 changes: 5 additions & 2 deletions lib/index-html.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
'use strict'

function indexHtml (opts) {
const hasLeadingSlash = /^\//.test(opts.prefix)
let routePrefix = opts.prefix
if (opts.indexPrefix) {
routePrefix = `${opts.indexPrefix.replace(/\/$/, '')}/${opts.prefix.replace(/^\//, '')}`
}
return (url) => {
const hasTrailingSlash = /\/$/.test(url)
const prefix = hasTrailingSlash ? `.${opts.staticPrefix}` : `${hasLeadingSlash ? '.' : ''}${opts.prefix}${opts.staticPrefix}`
const prefix = hasTrailingSlash ? `.${opts.staticPrefix}` : `${routePrefix}${opts.staticPrefix}`
return `<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
Expand Down
151 changes: 92 additions & 59 deletions test/route.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict'

const t = require('node:test')
const test = t.test
const nodeTest = require('node:test')
const test = nodeTest.test
const Fastify = require('fastify')
const Swagger = require('@apidevtools/swagger-parser')
const yaml = require('yaml')
Expand All @@ -16,6 +16,7 @@ const {
} = require('../examples/options')

const resolve = require('node:path').resolve
const join = require('node:path').join
const readFileSync = require('node:fs').readFileSync

const schemaParamsWithoutDesc = {
Expand Down Expand Up @@ -538,8 +539,35 @@ test('should return empty log level of route /documentation', async (t) => {
t.assert.deepStrictEqual(res.headers['content-type'], 'text/html; charset=utf-8')
})

const assertIndexUrls = (t, indexHtml, prefix) => {
t.assert.deepStrictEqual(indexHtml.includes(`href="${prefix}/static/index.css"`), true)
t.assert.deepStrictEqual(indexHtml.includes(`src="${prefix}/static/theme/theme-js.js"`), true)
t.assert.deepStrictEqual(indexHtml.includes(`href="${prefix}/index.css"`), false)
t.assert.deepStrictEqual(indexHtml.includes(`src="${prefix}/theme/theme-js.js"`), false)
}

const validateIndexUrls = async (t, fastify, indexHtml, prefix = '') => {
const hrefs = indexHtml.matchAll(/href="([^"]*)"/g)
for (const [, path] of hrefs) {
const res = await fastify.inject({
method: 'GET',
url: join(prefix, path)
})

t.assert.equal(res.statusCode, 200)
}
const srcs = indexHtml.matchAll(/src="([^"]*)"/g)
for (const [, path] of srcs) {
const res = await fastify.inject({
method: 'GET',
url: join(prefix, path)
})
t.assert.equal(res.statusCode, 200)
}
}

test('/documentation should display index html with correct asset urls', async (t) => {
t.plan(6)
t.plan(13)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] } })
Expand All @@ -548,59 +576,66 @@ test('/documentation should display index html with correct asset urls', async (
method: 'GET',
url: '/documentation'
})
t.assert.equal(res.statusCode, 200)

t.assert.deepStrictEqual(res.payload.includes('href="./documentation/static/index.css"'), true)
t.assert.deepStrictEqual(res.payload.includes('src="./documentation/static/theme/theme-js.js"'), true)
t.assert.deepStrictEqual(res.payload.includes('href="./documentation/index.css"'), false)
t.assert.deepStrictEqual(res.payload.includes('src="./documentation/theme/theme-js.js"'), false)
assertIndexUrls(t, res.payload, '/documentation')
await validateIndexUrls(t, fastify, res.payload)
})

let cssRes = await fastify.inject({
method: 'GET',
url: '/documentation/static/index.css'
})
t.assert.equal(cssRes.statusCode, 200)
cssRes = await fastify.inject({
method: 'GET',
url: './documentation/static/index.css'
/**
* This emulates when the server is inside an NGINX application that routes by path
*/
const testCases = [
['/swagger-app', undefined],
['/swagger-app/', undefined],
['/swagger-app', 'documentation']
]
testCases.forEach(([prefix, pluginPrefix]) => {
test(`${prefix} ${pluginPrefix} should display index html with correct asset urls when nested`, async (t) => {
t.plan(13)
const fastify = Fastify()
await fastify.register(
async (childFastify) => {
await childFastify.register(fastifySwagger, swaggerOption)
await childFastify.register(fastifySwaggerUi, { indexPrefix: prefix, routePrefix: pluginPrefix, theme: { js: [{ filename: 'theme-js.js' }] } })
},
{
prefix: '/swagger-app'
}
)

const res = await fastify.inject({
method: 'GET',
url: '/swagger-app/documentation'
})
t.assert.equal(res.statusCode, 200)

assertIndexUrls(t, res.payload, '/swagger-app/documentation')

await validateIndexUrls(t, fastify, res.payload)
})
t.assert.equal(cssRes.statusCode, 200)
})

/**
* This emulates when the server is inside an NGINX application that routes by path
*/
test('/documentation should display index html with correct asset urls when nested', async (t) => {
t.plan(5)
test('/api/v1/docs should display index html with correct asset urls', async (t) => {
t.plan(13)
const fastify = Fastify()
await fastify.register(
async () => {
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] } })
},
{
prefix: '/swagger-app'
}
)
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { prefix: '/api/v1/docs', theme: { js: [{ filename: 'theme-js.js' }] } })

const res = await fastify.inject({
method: 'GET',
url: '/swagger-app/documentation'
url: '/api/v1/docs'
})

t.assert.deepStrictEqual(res.payload.includes('href="./documentation/static/index.css"'), true)
t.assert.deepStrictEqual(res.payload.includes('src="./documentation/static/theme/theme-js.js"'), true)
t.assert.deepStrictEqual(res.payload.includes('href="./documentation/index.css"'), false)
t.assert.deepStrictEqual(res.payload.includes('src="./documentation/theme/theme-js.js"'), false)

const cssRes = await fastify.inject({
method: 'GET',
url: '/swagger-app/documentation/static/index.css'
})
t.assert.equal(cssRes.statusCode, 200)
t.assert.equal(res.statusCode, 200)
assertIndexUrls(t, res.payload, '/api/v1/docs')
await validateIndexUrls(t, fastify, res.payload)
})

test('/documentation/ should display index html with correct asset urls', async (t) => {
t.plan(4)
t.plan(13)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] } })
Expand All @@ -609,15 +644,14 @@ test('/documentation/ should display index html with correct asset urls', async
method: 'GET',
url: '/documentation/'
})
t.assert.equal(res.statusCode, 200)

t.assert.strictEqual(res.payload.includes('href="./static/index.css"'), true)
t.assert.strictEqual(res.payload.includes('src="./static/theme/theme-js.js"'), true)
t.assert.strictEqual(res.payload.includes('href="./index.css"'), false)
t.assert.strictEqual(res.payload.includes('src="./theme/theme-js.js"'), false)
assertIndexUrls(t, res.payload, '.')
await validateIndexUrls(t, fastify, res.payload, '/documentation/')
})

test('/docs should display index html with correct asset urls when documentation prefix is set', async (t) => {
t.plan(4)
t.plan(13)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] }, routePrefix: '/docs' })
Expand All @@ -626,11 +660,10 @@ test('/docs should display index html with correct asset urls when documentation
method: 'GET',
url: '/docs'
})
t.assert.equal(res.statusCode, 200)

t.assert.strictEqual(res.payload.includes('href="./docs/static/index.css"'), true)
t.assert.strictEqual(res.payload.includes('src="./docs/static/theme/theme-js.js"'), true)
t.assert.strictEqual(res.payload.includes('href="./docs/index.css"'), false)
t.assert.strictEqual(res.payload.includes('src="./docs/theme/theme-js.js"'), false)
assertIndexUrls(t, res.payload, '/docs')
await validateIndexUrls(t, fastify, res.payload)
})

test('/docs should display index html with correct asset urls when documentation prefix is set with no leading slash', async (t) => {
Expand Down Expand Up @@ -668,7 +701,7 @@ test('/docs/ should display index html with correct asset urls when documentatio
})

test('/documentation/ should display index html with correct asset urls', async (t) => {
t.plan(4)
t.plan(13)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] } })
Expand All @@ -677,15 +710,15 @@ test('/documentation/ should display index html with correct asset urls', async
method: 'GET',
url: '/documentation/'
})
t.assert.equal(res.statusCode, 200)

t.assert.strictEqual(res.payload.includes('href="./static/index.css"'), true)
t.assert.strictEqual(res.payload.includes('src="./static/theme/theme-js.js"'), true)
t.assert.strictEqual(res.payload.includes('href="./index.css"'), false)
t.assert.strictEqual(res.payload.includes('src="./theme/theme-js.js"'), false)
assertIndexUrls(t, res.payload, '.')

await validateIndexUrls(t, fastify, res.payload, '/documentation')
})

test('/docs should display index html with correct asset urls when documentation prefix is set', async (t) => {
t.plan(4)
t.plan(13)
const fastify = Fastify()
await fastify.register(fastifySwagger, swaggerOption)
await fastify.register(fastifySwaggerUi, { theme: { js: [{ filename: 'theme-js.js' }] }, routePrefix: '/docs' })
Expand All @@ -694,11 +727,11 @@ test('/docs should display index html with correct asset urls when documentation
method: 'GET',
url: '/docs'
})
t.assert.equal(res.statusCode, 200)

assertIndexUrls(t, res.payload, '/docs')

t.assert.strictEqual(res.payload.includes('href="./docs/static/index.css"'), true)
t.assert.strictEqual(res.payload.includes('src="./docs/static/theme/theme-js.js"'), true)
t.assert.strictEqual(res.payload.includes('href="./docs/index.css"'), false)
t.assert.strictEqual(res.payload.includes('src="./docs/theme/theme-js.js"'), false)
await validateIndexUrls(t, fastify, res.payload)
})

test('/docs/ should display index html with correct asset urls when documentation prefix is set', async (t) => {
Expand Down
4 changes: 4 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ declare namespace fastifySwaggerUi {
* @default /documentation
*/
routePrefix?: string;
/**
* Add an index prefix. This is for when the Fastify server is behind path based routing. ex. NGINX
*/
indexPrefix?: string;
/**
* Make it explicit that this plugin overrides the prefix value
*/
Expand Down

0 comments on commit 3d7c312

Please sign in to comment.