From 47f9357406287d64ce32f3357cab8c8c4718dbbe Mon Sep 17 00:00:00 2001 From: Ben Chidgey Date: Mon, 26 Aug 2024 18:22:42 +0100 Subject: [PATCH] Update decorator types to be correct - The code currently allows any value to be added as a decorator, but the types do not. This work allows any value to be added to a decorator by updating the types to reflect what the code doeis - Add relevant tests - Update inline docs - Create and export DecorationValue type - Fixes #4524 --- lib/types/plugin.d.ts | 7 ++- lib/types/server/server.d.ts | 16 +++---- test/server.js | 84 +++++++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/lib/types/plugin.d.ts b/lib/types/plugin.d.ts index 2be5b9cf3..0c715815c 100644 --- a/lib/types/plugin.d.ts +++ b/lib/types/plugin.d.ts @@ -248,7 +248,12 @@ export interface HandlerDecorationMethod { } /** - * The general case for decorators added via server.decorate. + * The general case for decorator values added via server.decorate. + */ +export type DecorationValue = DecorationMethod | any; + +/** + * Decorator function. */ export type DecorationMethod = (this: T, ...args: any[]) => any; diff --git a/lib/types/server/server.d.ts b/lib/types/server/server.d.ts index 0970b7c17..390525517 100644 --- a/lib/types/server/server.d.ts +++ b/lib/types/server/server.d.ts @@ -13,7 +13,7 @@ import { ServerRegisterPluginObject, ServerRegisterPluginObjectArray, DecorateName, - DecorationMethod, + DecorationValue, HandlerDecorationMethod, PluginProperties } from '../plugin'; @@ -310,13 +310,13 @@ export class Server { * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-serverdecoratetype-property-method-options) */ decorate(type: 'handler', property: DecorateName, method: HandlerDecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'request', property: DecorateName, method: (existing: ((...args: any[]) => any)) => (request: Request) => DecorationMethod, options: {apply: true, extend: true}): void; - decorate(type: 'request', property: DecorateName, method: (request: Request) => DecorationMethod, options: {apply: true, extend?: boolean | undefined}): void; - decorate(type: 'request', property: DecorateName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'toolkit', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationMethod, options: {apply?: boolean | undefined, extend: true}): void; - decorate(type: 'toolkit', property: DecorateName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; - decorate(type: 'server', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationMethod, options: {apply?: boolean | undefined, extend: true}): void; - decorate(type: 'server', property: DecorateName, method: DecorationMethod, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; + decorate(type: 'request', property: DecorateName, method: (existing: ((...args: any[]) => any)) => (request: Request) => DecorationValue, options: {apply: true, extend: true}): void; + decorate(type: 'request', property: DecorateName, method: (request: Request) => DecorationValue, options: {apply: true, extend?: boolean | undefined}): void; + decorate(type: 'request', property: DecorateName, method: DecorationValue, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; + decorate(type: 'toolkit', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationValue, options: {apply?: boolean | undefined, extend: true}): void; + decorate(type: 'toolkit', property: DecorateName, method: DecorationValue, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; + decorate(type: 'server', property: DecorateName, method: (existing: ((...args: any[]) => any)) => DecorationValue, options: {apply?: boolean | undefined, extend: true}): void; + decorate(type: 'server', property: DecorateName, method: DecorationValue, options?: {apply?: boolean | undefined, extend?: boolean | undefined}): void; /** * Used within a plugin to declare a required dependency on other plugins where: diff --git a/test/server.js b/test/server.js index 49d1247df..e28e0b2eb 100755 --- a/test/server.js +++ b/test/server.js @@ -294,7 +294,7 @@ describe('Server', () => { describe('decorate()', () => { - it('decorates request', async () => { + it('decorates request with function', async () => { const server = Hapi.server(); @@ -316,6 +316,25 @@ describe('Server', () => { expect(res.result).to.match(/^.*\:.*\:.*\:.*\:.*$/); }); + it('decorates request with object', async () => { + + const server = Hapi.server(); + + const customData = { id: '123' }; + + server.decorate('request', 'customData', customData); + + server.route({ + method: 'GET', + path: '/', + handler: (request) => request.customData + }); + + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.result).to.equal({ id: '123' }); + }); + it('decorates request (apply)', async () => { const server = Hapi.server(); @@ -366,6 +385,26 @@ describe('Server', () => { expect(res.result).to.match(/^.*\:.*\:.*\:.*\:.*!$/); }); + it('decorates request (extend) with an array', async () => { + + const server = Hapi.server(); + + const items = ['one', 'two', 'three']; + + server.decorate('request', 'items', items); + server.decorate('request', 'items', (existing) => [...existing, 'four'], { extend: true }); + + server.route({ + method: 'GET', + path: '/', + handler: (request) => request.items + }); + + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.result).to.equal([...items, 'four']); + }); + it('decorates request (apply + extend)', async () => { const server = Hapi.server(); @@ -444,6 +483,25 @@ describe('Server', () => { expect(res.result.status).to.equal('ok'); }); + it('decorates toolkit with boolean', async () => { + + const server = Hapi.server(); + + const isOk = true; + + server.decorate('toolkit', 'isOk', isOk); + + server.route({ + method: 'GET', + path: '/', + handler: (request, h) => h.isOk + }); + + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.result).to.equal(true); + }); + it('add new handler', async () => { const test = { @@ -559,6 +617,30 @@ describe('Server', () => { expect(res.result).to.equal('ok'); }); + it('decorates server with Map', async () => { + + const server = Hapi.server(); + + const plants = new Map(); + plants.set('mango', 'Mango'); + plants.set('banana', 'Banana'); + plants.set('apple', 'Apple'); + + server.decorate('server', 'plants', plants); + + server.route({ + method: 'GET', + path: '/', + handler: (request) => request.server.plants + }); + + const res = await server.inject('/'); + expect(res.statusCode).to.equal(200); + expect(res.result.get('mango')).to.equal('Mango'); + expect(res.result.get('banana')).to.equal('Banana'); + expect(res.result.get('apple')).to.equal('Apple'); + }); + it('throws on double server decoration', () => { const server = Hapi.server();