diff --git a/README.md b/README.md index 23b57a1..981fb67 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ async function run() { // read your application structure when fastify is ready app.addHook('onReady', function showStructure (done) { - const appStructure = app.overview() + const appStructure = app.overview() // 🦄 Here is the magic! console.log(JSON.stringify(appStructure, null, 2)) done(null) }) @@ -166,7 +166,7 @@ You can see the previous code output running it on RunKit: [![runkit](https://im ## Options -You can pass the following options to the plugin: +You can pass the following options to the plugin or to the decorator: ```js app.register(require('fastify-overview'), { @@ -177,6 +177,10 @@ app.register(require('fastify-overview'), { url: '/customUrl', // default: '/json-overview' } }) + +const appStructure = app.overview({ + hideEmpty: true, // default: false +}) ``` ### addSource @@ -201,6 +205,64 @@ Here an example of the structure with the `addSource` option: } ``` +### hideEmpty + +To keep the structure light and clean, you can hide empty properties by providing this option to the `overview` decorator. +For example, if you do not have any decorator, the `decorators` property will not be present in the structure. + +The properties that can be hidden are: +- `decorators` and/or its children +- `hooks` and/or its children +- `routes` + +You can get both the structure by calling the `overview` method twice: + +```js +const fullStructure = app.overview() +const lightStructure = app.overview({ + hideEmpty: true, // default: false +}) +``` + +Here an example of the cleaned output: + +```json +{ + "id": 0.38902288100060645, + "name": "fastify -> fastify-overview", + "children": [ + { + "id": 0.7086786379705781, + "name": "function (instance, opts, next) { next() }" + }, + { + "id": 0.6405610832733726, + "name": "async function (instance, opts) { -- instance.register(async function (instance, opts) {", + "children": [ + { + "id": 0.8200459678409413, + "name": "async function (instance, opts) { -- instance.decorateReply('oneRep', {})", + "decorators": { + "decorateReply": [ + { "name": "oneRep" } + ] + } + } + ] + } + ], + "hooks": { + "onRequest": [ + { + "name": "hook1", + "hash": "31d31d981f412085927efb5e9f36be8ba905516a" + } + ] + } +} +``` + + ### exposeRoute Optionally, you can expose a route that will return the JSON structure. diff --git a/index.js b/index.js index fa2f9e7..ba8b5d1 100644 --- a/index.js +++ b/index.js @@ -53,10 +53,33 @@ function fastifyOverview (fastify, options, next) { done(null) }) - fastify.decorate('overview', function getOverview () { + fastify.decorate('overview', function getOverview (opts) { if (!structure) { throw new Error('Fastify must be in ready status to access the overview') } + + if (opts && opts.hideEmpty) { + const filterStructure = JSON.stringify(structure, (key, value) => { + switch (key) { + case 'decorators': + case 'hooks': + if (Object.entries(value).every(([, v]) => { + return Array.isArray(v) && v.length === 0 + })) { + return undefined + } + break + default: + if (Array.isArray(value) && value.length === 0) { + return undefined + } + } + + return value + }) + return JSON.parse(filterStructure) + } + return structure }) @@ -67,6 +90,7 @@ function fastifyOverview (fastify, options, next) { const routeConfig = Object.assign( { method: 'GET', + exposeHeadRoute: false, url: '/json-overview' }, opts.exposeRouteOptions, diff --git a/test/index.test.js b/test/index.test.js index 5a53f9c..2fb1814 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -92,3 +92,57 @@ test('register', async t => { t.equal(reg1.children[2].name, 'register4') t.equal(reg1.hooks.onRequest.length, 1) }) + +test('hide empty', async t => { + const app = fastify() + await app.register(plugin) + + app.decorate('emptyObject', {}) + app.decorate('emptyArray', []) + app.decorateRequest('oneReqDecor', []) + + app.addHook('onRequest', function hook1 () {}) + app.addHook('preParsing', function hook2 () {}) + app.addHook('preValidation', function hook3 () {}) + app.addHook('onError', function hookSix () {}) + + app.register(function (instance, opts, next) { next() }) + app.register(async function (instance, opts) { + instance.register(async function (instance, opts) { + instance.decorateReply('oneRep', {}) + }) + }) + + await app.ready() + const structure = app.overview({ hideEmpty: true }) + + t.strictSame(structure.decorators, { + decorate: [ + { name: 'emptyObject' }, + { name: 'emptyArray' } + ], + decorateRequest: [ + { name: 'oneReqDecor' } + ] + }) + + t.strictSame(structure.hooks, { + onRequest: [{ name: 'hook1', hash: '31d31d981f412085927efb5e9f36be8ba905516a' }], + preParsing: [{ name: 'hook2', hash: '07f8fc52f2a92adc80881b4c11ee61ab56ea42d1' }], + preValidation: [{ name: 'hook3', hash: '92b002434cd5d8481e7e5562b51df679e2f8d586' }], + onError: [{ name: 'hookSix', hash: '9398f5df01879094095221d86a544179e62cee12' }] + }) + + t.equal(structure.children.length, 2) + + delete structure.children[0].id + t.same(structure.children[0], { + name: 'function (instance, opts, next) { next() }' + }, 'should have only the name') + + t.strictSame(structure.children[1].children[0].decorators, { + decorateReply: [ + { name: 'oneRep' } + ] + }) +})