diff --git a/README.md b/README.md index 770cf68..fd98588 100644 --- a/README.md +++ b/README.md @@ -307,6 +307,9 @@ Default: `json` Options: `html`, `json` Directory list can be in `html` format; in that case, `list.render` function is required. +Directory list in `json` format can also use `list.render` to customize the JSON response. +When `list.render` is omitted, the JSON response is controlled by `list.jsonFormat`. +The `list.render` function receives `(dirs, files, format)`, where `format` is `html` or `json`. This option can be overridden by the URL parameter `format`. Options are `html` and `json`. @@ -340,6 +343,26 @@ fastify.register(require('@fastify/static'), { }) ``` +JSON render example: + +```js +fastify.register(require('@fastify/static'), { + root: path.join(__dirname, 'public'), + prefix: '/public/', + list: { + format: 'json', + render: (dirs, files) => { + return { + dirs: dirs.map(dir => dir.name), + images: files + .filter(file => file.name.endsWith('.png')) + .map(file => ({ name: file.name, href: file.href })) + } + } + } +}) +``` + Request ```bash diff --git a/lib/dirList.js b/lib/dirList.js index f05588b..9fd0719 100644 --- a/lib/dirList.js +++ b/lib/dirList.js @@ -129,7 +129,13 @@ const dirList = { } const format = reply.request.query.format || options.format + const renderFormat = format === 'html' ? 'html' : 'json' if (format !== 'html') { + if (typeof options.render === 'function' && options.format !== 'html') { + await reply.send(dirList.render(entries, route, prefix, options, renderFormat)) + return + } + if (options.jsonFormat !== 'extended') { const nameEntries = { dirs: [], files: [] } entries.dirs.forEach(entry => nameEntries.dirs.push(entry.name)) @@ -142,12 +148,26 @@ const dirList = { return } - const html = options.render( - entries.dirs.map(entry => dirList.htmlInfo(entry, route, prefix, options)), - entries.files.map(entry => dirList.htmlInfo(entry, route, prefix, options))) + const html = dirList.render(entries, route, prefix, options, renderFormat) await reply.type('text/html').send(html) }, + /** + * render directory entries + * @param {ReturnType} entries directory list entries + * @param {string} route request route + * @param {string} prefix static prefix + * @param {(ListOptionsJsonFormat | ListOptionsHtmlFormat)} options + * @param {'html' | 'json'} format response format + * @return {unknown} + */ + render: function (entries, route, prefix, options, format) { + return options.render( + entries.dirs.map(entry => dirList.htmlInfo(entry, route, prefix, options)), + entries.files.map(entry => dirList.htmlInfo(entry, route, prefix, options)), + format) + }, + /** * provide the html information about entry and route, to get name and full route * @param entry file or dir name and stats diff --git a/test/dir-list.test.js b/test/dir-list.test.js index edaa2fd..4b6d96d 100644 --- a/test/dir-list.test.js +++ b/test/dir-list.test.js @@ -497,6 +497,50 @@ test('dir list json format - extended info', async t => { }) }) +test('dir list json format - render', async t => { + t.plan(1) + + const options = { + root: path.join(__dirname, '/static'), + prefix: '/public', + prefixAvoidTrailingSlash: true, + list: { + format: 'json', + names: ['index', 'index.json', '/'], + render (dirs, files) { + return { + dirs: dirs.map(dir => dir.name), + images: files + .filter(file => file.name.endsWith('.jpg')) + .map(file => ({ name: file.name, href: file.href })) + } + } + } + } + const route = '/public/shallow/' + const content = { + dirs: ['empty'], + images: [ + { + name: 'sample.jpg', + href: '/public/shallow/sample.jpg' + } + ] + } + + await helper.arrange(t, options, async (url) => { + await t.test(route, async t => { + t.plan(4) + + const response = await fetch(url + route) + t.assert.ok(response.ok) + t.assert.deepStrictEqual(response.status, 200) + t.assert.deepStrictEqual(await response.json(), content) + t.assert.ok(response.headers.get('content-type').includes('application/json')) + }) + }) +}) + test('json format with url parameter format', async t => { t.plan(12) @@ -506,8 +550,15 @@ test('json format with url parameter format', async t => { index: false, list: { format: 'json', - render () { - return 'html' + render (dirs, files, format) { + if (format === 'html') { + return 'html' + } + + return { + dirs: dirs.map(dir => dir.name), + files: files.map(file => file.name) + } } } } diff --git a/types/index.d.ts b/types/index.d.ts index d01b9d3..4f39107 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -44,8 +44,14 @@ declare namespace fastifyStatic { stats: Stats; } + export type ListFormat = 'html' | 'json' + export interface ListRender { - (dirs: ListDir[], files: ListFile[]): string; + (dirs: ListDir[], files: ListFile[], format?: ListFormat): string; + } + + export interface ListJsonRender { + (dirs: ListDir[], files: ListFile[], format?: ListFormat): unknown; } export interface ListOptions { @@ -57,7 +63,7 @@ declare namespace fastifyStatic { export interface ListOptionsJsonFormat extends ListOptions { format: 'json'; // Required when the URL parameter `format=html` exists - render?: ListRender; + render?: ListJsonRender; } export interface ListOptionsHtmlFormat extends ListOptions { diff --git a/types/index.tst.ts b/types/index.tst.ts index b47b285..0329411 100644 --- a/types/index.tst.ts +++ b/types/index.tst.ts @@ -88,6 +88,15 @@ expect() } }) +expect() + .type.toBeAssignableFrom({ + root: '', + list: { + format: 'json' as const, + render: () => ({ files: [] }) + } + }) + expect() .type.toBeAssignableFrom({ root: '',