diff --git a/README.md b/README.md index c51dc0d..63ab525 100644 --- a/README.md +++ b/README.md @@ -176,11 +176,19 @@ app.register(require('fastify-overview'), { exposeRouteOptions: { method: 'POST', // default: 'GET' url: '/customUrl', // default: '/json-overview' - }, - onRouteDefinition: (opts) => { + }, + onRouteDefinition: (opts) => { return { schema: opts.schema } + }, + onDecorateDefinition: (decoratorType, decoratorName, decoratorValue) => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + return { + staticData: true + } + } + return { utilityFunction: true } } }) @@ -318,6 +326,37 @@ override the `source` property. ``` In this example, the `url` property is overridden and the `url` length is returned instead of the `url`. +### onDecorateDefinition + +Similar to `onRouteDefinition`, this option allows you to control which information about decorators is included in the overview. +The passed function is called for `instance`, `request` and `reply` decorators but the decorator type is passed to the function as parameter. +The default properties `name` and `type` can also be overwritten here. See the table below for an overview of exactly +how the function `onDecorateDefinition(decoratorType, decoratorName, decoratorValue)` is called for the different decorators. + +| Decorator | decoratorType | decoratorName | decoratorValue | +|:-----------------------------------------:|-----------------|---------------|-------------------| +| `app.decorate('db', {query: () => {}})` | decorate | db | {query: () => {}} | +| `app.decorateRequest('verify', () => {})` | decorateRequest | verify | () => {} | +| `app.decorateReply('num', 42)` | decorateReply | num | 42 | + +As an example, the function below returns the nested properties for object values. +```js +onDecorateDefinition: (type, name, value) => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + return { + recursive: Object.entries(value).map(([key, val]) => { + return { + name: key, + type: Array.isArray(val) ? 'array' : typeof val + } + }) + } + } else { + return {} + } +} +``` + ## License Copyright [Manuel Spigolon](https://github.com/Eomm), Licensed under [MIT](./LICENSE). diff --git a/index.d.ts b/index.d.ts index e16fce9..f8d6a61 100644 --- a/index.d.ts +++ b/index.d.ts @@ -50,15 +50,23 @@ interface RouteItem { source?: OverviewStructureSource, } -export interface OverviewStructure { +export interface Decorators { + instance?: Record + request?: Record + reply?: Record +} + +type ExtractDecoratorType, K extends keyof Decorators> = T extends Decorators ? T[K] : T + +export interface OverviewStructure = {}> { id: Number, name: string, source?: OverviewStructureSource, children?: OverviewStructure[], decorators?: { - decorate: OverviewStructureDecorator[], - decorateRequest: OverviewStructureDecorator[], - decorateReply: OverviewStructureDecorator[] + decorate: (Omit> & ExtractDecoratorType)[], + decorateRequest: (Omit> & ExtractDecoratorType)[], + decorateReply: (Omit> & ExtractDecoratorType)[] }, hooks?: OverviewStructureHooks, routes?: (Omit & T)[] @@ -87,6 +95,11 @@ export interface FastifyOverviewOptions { * Customise which properties of the route options will be included in the overview */ onRouteDefinition?: (routeOptions: RouteOptions & { routePath: string; path: string; prefix: string }) => Record + + /** + * Customise which information from decorators should be added to the overview + */ + onDecorateDefinition?: (type: 'decorate' | 'decorateRequest' | 'decorateReply', name: string, value: unknown) => Record } export interface FastifyOverviewDecoratorOptions { @@ -103,7 +116,7 @@ export interface FastifyOverviewDecoratorOptions { declare module 'fastify' { export interface FastifyInstance { - overview: (opts?: FastifyOverviewDecoratorOptions) => OverviewStructure; + overview: = {}>(opts?: FastifyOverviewDecoratorOptions) => OverviewStructure; } } diff --git a/index.js b/index.js index 536aab1..c1c4596 100644 --- a/index.js +++ b/index.js @@ -167,10 +167,10 @@ function wrapFastify (instance, pluginOpts) { } } -function wrapDecorator (instance, type, { addSource }) { +function wrapDecorator (instance, type, { addSource, onDecorateDefinition }) { const originalDecorate = instance[type] instance[type] = function wrapDecorate (name, value) { - const decoratorNode = getDecoratorNode(name, value) + const decoratorNode = Object.assign(getDecoratorNode(name, value), onDecorateDefinition?.(type, name, value)) if (addSource) { decoratorNode.source = getSource()[0] } diff --git a/test/decorator.test.js b/test/decorator.test.js index bb92b1b..dd658aa 100644 --- a/test/decorator.test.js +++ b/test/decorator.test.js @@ -63,3 +63,56 @@ test('decorator', async t => { t.same(reg3.decorators.decorateRequest, [{ name: 'sub-object', type: 'object' }]) t.same(reg3.decorators.decorateReply, [{ name: 'sub', type: 'number' }]) }) + +test('onDecorateDefinition', async t => { + const app = fastify() + await app.register(plugin, { + onDecorateDefinition: (type, name, value) => { + if (value && typeof value === 'object' && !Array.isArray(value)) { + return { + recursive: Object.entries(value).map(([key, val]) => { + return { + name: key, + type: Array.isArray(val) ? 'array' : typeof val + } + }) + } + } else { + return {} + } + } + }) + + app.decorate('emptyObj', {}) + app.decorate('obj1', { + run: () => {} + }) + app.decorateRequest('emptyObj', {}) + app.decorateReply('obj2', { + test: 'str' + }) + + app.register(async function child1 (instance) { + instance.decorate('encapsulatedObj', { + a: () => {}, + b: 'str', + c: false, + d: 42 + }) + }) + + await app.ready() + + const root = app.overview() + + t.equal(root.children.length, 1) + t.same(root.decorators.decorate, [{ name: 'emptyObj', type: 'object', recursive: [] }, { name: 'obj1', type: 'object', recursive: [{ name: 'run', type: 'function' }] }]) + t.same(root.decorators.decorateReply, [{ name: 'obj2', type: 'object', recursive: [{ name: 'test', type: 'string' }] }]) + t.same(root.decorators.decorateRequest, [{ name: 'emptyObj', type: 'object', recursive: [] }]) + + t.equal(root.children[0].name, 'child1') + const child1 = root.children[0] + t.same(child1.decorators.decorate, [{ name: 'encapsulatedObj', type: 'object', recursive: [{ name: 'a', type: 'function' }, { name: 'b', type: 'string' }, { name: 'c', type: 'boolean' }, { name: 'd', type: 'number' }] }]) + t.equal(child1.decorators.decorateRequest.length, 0) + t.equal(child1.decorators.decorateReply.length, 0) +}) diff --git a/test/types/index.test-d.ts b/test/types/index.test-d.ts index 13de188..9a2179b 100644 --- a/test/types/index.test-d.ts +++ b/test/types/index.test-d.ts @@ -1,4 +1,4 @@ -import { expectType } from 'tsd' +import { expectAssignable, expectError, expectType } from 'tsd' import fastify, { HTTPMethods } from 'fastify' import fastifyOverview, { OverviewStructure } from '../../index' @@ -49,3 +49,64 @@ app expectType(data.routes![0].url) }) .ready() + +app + .register(fastifyOverview, { + onDecorateDefinition: (type, name, value) => { + if (typeof value === 'object' && !Array.isArray(value)) { + return { + embedded: Object.keys(value ?? {}) + } + } else { + return {} + } + } + }) + .after((_) => { + const data = app.overview<{}, { embedded: string[] }>() + + expectType>(data) + expectAssignable<{ type: string, name: string, embedded: string[] }>(data.decorators!.decorate[0]!) + expectAssignable<{ type: string, name: string, embedded: string[] }>(data.decorators!.decorateRequest[0]!) + expectAssignable<{ type: string, name: string, embedded: string[] }>(data.decorators!.decorateReply[0]!) + }) + .ready() + +app + .register(fastifyOverview, { + onDecorateDefinition: (type, name, value) => { + if (type === 'decorate') { + if (typeof value === 'object' && !Array.isArray(value)) { + return { + embedded: Object.keys(value ?? {}) + } + } else { + return {} + } + } else if (type === 'decorateRequest') { + if (typeof value === 'object' && !Array.isArray(value)) { + return { + recursiveNum: Object.keys(value ?? {}).length + } + } else { + return {} + } + } else { + return {} + } + } + }) + .after((_) => { + const data = app.overview<{}, { instance: { embedded: string[] }, request: { recursiveNum: number } }>() + + expectType>(data) + + expectAssignable<{ type: string, name: string, embedded: string[] }>(data.decorators!.decorate[0]!) + expectError<{ type: string, name: string, embedded: string[] }>(data.decorators!.decorateRequest[0]!) + expectError<{ type: string, name: string, embedded: string[] }>(data.decorators!.decorateReply[0]!) + + expectAssignable<{ type: string, name: string, recursiveNum: number }>(data.decorators!.decorateRequest[0]!) + expectError<{ type: string, name: string, recursiveNum: number }>(data.decorators!.decorate[0]!) + expectError<{ type: string, name: string, recursiveNum: number }>(data.decorators!.decorateReply[0]!) + }) + .ready()