Skip to content

Commit

Permalink
feat: onDecorateDefinition plugin option (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthyk authored Feb 10, 2024
1 parent 1327c0c commit 1893321
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 10 deletions.
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
})

Expand Down Expand Up @@ -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).
23 changes: 18 additions & 5 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,23 @@ interface RouteItem {
source?: OverviewStructureSource,
}

export interface OverviewStructure<T = {}> {
export interface Decorators {
instance?: Record<string, unknown>
request?: Record<string, unknown>
reply?: Record<string, unknown>
}

type ExtractDecoratorType<T extends Record<PropertyKey, unknown>, K extends keyof Decorators> = T extends Decorators ? T[K] : T

export interface OverviewStructure<T = {}, D extends Record<PropertyKey, unknown> = {}> {
id: Number,
name: string,
source?: OverviewStructureSource,
children?: OverviewStructure<T>[],
decorators?: {
decorate: OverviewStructureDecorator[],
decorateRequest: OverviewStructureDecorator[],
decorateReply: OverviewStructureDecorator[]
decorate: (Omit<OverviewStructureDecorator, keyof ExtractDecoratorType<D, 'instance'>> & ExtractDecoratorType<D, 'instance'>)[],
decorateRequest: (Omit<OverviewStructureDecorator, keyof ExtractDecoratorType<D, 'request'>> & ExtractDecoratorType<D, 'request'>)[],
decorateReply: (Omit<OverviewStructureDecorator, keyof ExtractDecoratorType<D, 'reply'>> & ExtractDecoratorType<D, 'reply'>)[]
},
hooks?: OverviewStructureHooks,
routes?: (Omit<RouteItem, keyof T> & T)[]
Expand Down Expand Up @@ -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<string, unknown>

/**
* Customise which information from decorators should be added to the overview
*/
onDecorateDefinition?: (type: 'decorate' | 'decorateRequest' | 'decorateReply', name: string, value: unknown) => Record<string, unknown>
}

export interface FastifyOverviewDecoratorOptions {
Expand All @@ -103,7 +116,7 @@ export interface FastifyOverviewDecoratorOptions {

declare module 'fastify' {
export interface FastifyInstance {
overview: <T = {}>(opts?: FastifyOverviewDecoratorOptions) => OverviewStructure<T>;
overview: <T = {}, D extends Record<PropertyKey, unknown> = {}>(opts?: FastifyOverviewDecoratorOptions) => OverviewStructure<T, D>;
}
}

Expand Down
4 changes: 2 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
Expand Down
53 changes: 53 additions & 0 deletions test/decorator.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
63 changes: 62 additions & 1 deletion test/types/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expectType } from 'tsd'
import { expectAssignable, expectError, expectType } from 'tsd'

import fastify, { HTTPMethods } from 'fastify'
import fastifyOverview, { OverviewStructure } from '../../index'
Expand Down Expand Up @@ -49,3 +49,64 @@ app
expectType<number>(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<OverviewStructure<{}, { embedded: string[] }>>(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<OverviewStructure<{}, { instance: { embedded: string[] }, request: { recursiveNum: number } }>>(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()

0 comments on commit 1893321

Please sign in to comment.