From bc8b5ee6fd8dbb0d697c4b1fd19f4dfb76c348a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Wed, 4 Dec 2024 21:51:59 +0100 Subject: [PATCH 1/3] feat: update NextArgs with isOptional extract PlanMetadata interface as well --- src/container/container.ts | 35 ++++++++++-- src/interfaces/interfaces.ts | 1 + src/planning/planner.ts | 60 ++++++++++----------- src/test/planning/planner.test.ts | 70 +++++++++++------------- src/test/resolution/resolver.test.ts | 80 +++++++++++++++++++++------- 5 files changed, 150 insertions(+), 96 deletions(-) diff --git a/src/container/container.ts b/src/container/container.ts index e0c0cc58..a45a06b4 100644 --- a/src/container/container.ts +++ b/src/container/container.ts @@ -8,6 +8,7 @@ import { createMockRequest, getBindingDictionary, plan, + PlanMetadata, } from '../planning/planner'; import { resolve } from '../resolution/resolver'; import { BindingToSyntax } from '../syntax/binding_to_syntax'; @@ -319,8 +320,13 @@ class Container implements interfaces.Container { const request: interfaces.Request = createMockRequest( this, serviceIdentifier, - key, - value, + { + customTag: { + key, + value, + }, + isMultiInject: false, + }, ); bound = bindings.some((b: interfaces.Binding) => b.constraint(request)); } @@ -862,6 +868,27 @@ class Container implements interfaces.Container { return getNotAllArgs; } + private _getPlanMetadataFromNextArgs( + args: interfaces.NextArgs, + ): PlanMetadata { + const planMetadata: PlanMetadata = { + isMultiInject: args.isMultiInject, + }; + + if (args.key !== undefined) { + planMetadata.customTag = { + key: args.key, + value: args.value, + }; + } + + if (args.isOptional === true) { + planMetadata.isOptional = true; + } + + return planMetadata; + } + // Planner creates a plan and Resolver resolves a plan // one of the jobs of the Container is to links the Planner // with the Resolver and that is what this function is about @@ -875,11 +902,9 @@ class Container implements interfaces.Container { let context: interfaces.Context = plan( this._metadataReader, this, - args.isMultiInject, args.targetType, args.serviceIdentifier, - args.key, - args.value, + this._getPlanMetadataFromNextArgs(args), args.avoidConstraints, ); diff --git a/src/interfaces/interfaces.ts b/src/interfaces/interfaces.ts index 3f7747fc..7f640c1c 100644 --- a/src/interfaces/interfaces.ts +++ b/src/interfaces/interfaces.ts @@ -143,6 +143,7 @@ namespace interfaces { avoidConstraints: boolean; contextInterceptor: (contexts: Context) => Context; isMultiInject: boolean; + isOptional?: boolean; targetType: TargetType; serviceIdentifier: interfaces.ServiceIdentifier; key?: string | number | symbol | undefined; diff --git a/src/planning/planner.ts b/src/planning/planner.ts index c3c8bd36..2fa2d1db 100644 --- a/src/planning/planner.ts +++ b/src/planning/planner.ts @@ -28,7 +28,7 @@ import { } from './reflection_utils'; import { Request } from './request'; -function getBindingDictionary( +export function getBindingDictionary( cntnr: interfaces.Container, ): interfaces.Lookup> { return ( @@ -39,18 +39,13 @@ function getBindingDictionary( } function _createTarget( - isMultiInject: boolean, targetType: interfaces.TargetType, serviceIdentifier: interfaces.ServiceIdentifier, - name: string, - key?: string | number | symbol, - value?: unknown, + metadata: PlanMetadata, ): interfaces.Target { const metadataList: Metadata[] = _getTargetMetadata( - isMultiInject, serviceIdentifier, - key, - value, + metadata, ); const classElementMetadata: ClassElementMetadata = @@ -60,7 +55,7 @@ function _createTarget( throw new Error('Unexpected metadata when creating target'); } - const target: Target = new TargetImpl(name, classElementMetadata, targetType); + const target: Target = new TargetImpl('', classElementMetadata, targetType); return target; } @@ -122,12 +117,10 @@ function _getActiveBindings( } function _getTargetMetadata( - isMultiInject: boolean, serviceIdentifier: interfaces.ServiceIdentifier, - key: string | number | symbol | undefined, - value: unknown, + metadata: PlanMetadata, ): Metadata[] { - const metadataKey: string = isMultiInject + const metadataKey: string = metadata.isMultiInject ? METADATA_KEY.MULTI_INJECT_TAG : METADATA_KEY.INJECT_TAG; @@ -135,8 +128,14 @@ function _getTargetMetadata( new Metadata(metadataKey, serviceIdentifier), ]; - if (key !== undefined) { - metadataList.push(new Metadata(key, value)); + if (metadata.customTag !== undefined) { + metadataList.push( + new Metadata(metadata.customTag.key, metadata.customTag.value), + ); + } + + if (metadata.isOptional === true) { + metadataList.push(new Metadata(METADATA_KEY.OPTIONAL_TAG, true)); } return metadataList; @@ -312,24 +311,28 @@ function getBindings( return bindings; } -function plan( +export interface PlanMetadata { + isMultiInject: boolean; + isOptional?: boolean; + customTag?: { + key: string | number | symbol; + value?: unknown; + }; +} + +export function plan( metadataReader: interfaces.MetadataReader, container: interfaces.Container, - isMultiInject: boolean, targetType: interfaces.TargetType, serviceIdentifier: interfaces.ServiceIdentifier, - key?: string | number | symbol, - value?: unknown, + metadata: PlanMetadata, avoidConstraints: boolean = false, ): interfaces.Context { const context: Context = new Context(container); const target: interfaces.Target = _createTarget( - isMultiInject, targetType, serviceIdentifier, - '', - key, - value, + metadata, ); try { @@ -350,17 +353,14 @@ function plan( } } -function createMockRequest( +export function createMockRequest( container: interfaces.Container, serviceIdentifier: interfaces.ServiceIdentifier, - key: string | number | symbol, - value: unknown, + metadata: PlanMetadata, ): interfaces.Request { const metadataList: Metadata[] = _getTargetMetadata( - false, serviceIdentifier, - key, - value, + metadata, ); const classElementMetadata: ClassElementMetadata = @@ -382,5 +382,3 @@ function createMockRequest( ); return request; } - -export { plan, createMockRequest, getBindingDictionary }; diff --git a/src/test/planning/planner.test.ts b/src/test/planning/planner.test.ts index 271ee5d2..d6d9f601 100644 --- a/src/test/planning/planner.test.ts +++ b/src/test/planning/planner.test.ts @@ -79,9 +79,11 @@ describe('Planner', () => { const actualPlan: Plan = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ).plan; const actualNinjaRequest: interfaces.Request = actualPlan.rootRequest; const actualKatanaRequest: interfaces.Request | undefined = @@ -233,9 +235,11 @@ describe('Planner', () => { const actualPlan: Plan = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ).plan; expect(actualPlan.rootRequest.serviceIdentifier).eql(ninjaId); @@ -285,9 +289,11 @@ describe('Planner', () => { const actualPlan: Plan = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ).plan; // root request has no target @@ -399,13 +405,9 @@ describe('Planner', () => { container.bind(shurikenId).to(Shuriken); const throwFunction: () => void = () => { - plan( - new MetadataReader(), - container, - false, - TargetTypeEnum.Variable, - ninjaId, - ); + plan(new MetadataReader(), container, TargetTypeEnum.Variable, ninjaId, { + isMultiInject: false, + }); }; expect(throwFunction).to.throw(`${ERROR_MSGS.NOT_REGISTERED} Katana`); @@ -444,13 +446,9 @@ describe('Planner', () => { container.bind(shurikenId).to(Shuriken); const throwFunction: () => void = () => { - plan( - new MetadataReader(), - container, - false, - TargetTypeEnum.Variable, - ninjaId, - ); + plan(new MetadataReader(), container, TargetTypeEnum.Variable, ninjaId, { + isMultiInject: false, + }); }; expect(throwFunction).to.throw(`${ERROR_MSGS.AMBIGUOUS_MATCH} Katana`); @@ -493,9 +491,11 @@ describe('Planner', () => { const actualPlan: Plan = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ).plan; // root request has no target @@ -534,13 +534,9 @@ describe('Planner', () => { container.bind('Weapon').to(Katana); const throwFunction: () => void = () => { - plan( - new MetadataReader(), - container, - false, - TargetTypeEnum.Variable, - 'Weapon', - ); + plan(new MetadataReader(), container, TargetTypeEnum.Variable, 'Weapon', { + isMultiInject: false, + }); }; expect(throwFunction).not.to.throw(); @@ -561,13 +557,9 @@ describe('Planner', () => { container.bind(Ninja).toSelf(); const throwFunction: () => void = () => { - plan( - new MetadataReader(), - container, - false, - TargetTypeEnum.Variable, - Ninja, - ); + plan(new MetadataReader(), container, TargetTypeEnum.Variable, Ninja, { + isMultiInject: false, + }); }; expect(throwFunction).to.throw( @@ -623,9 +615,11 @@ describe('Planner', () => { plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, 'Warrior', + { + isMultiInject: false, + }, ); }; @@ -659,13 +653,9 @@ describe('Planner', () => { container.bind('Factory').to(Katana); const throwFunction: () => void = () => { - plan( - new MetadataReader(), - container, - false, - TargetTypeEnum.Variable, - 'Ninja', - ); + plan(new MetadataReader(), container, TargetTypeEnum.Variable, 'Ninja', { + isMultiInject: false, + }); }; expect(throwFunction).to.throw( diff --git a/src/test/resolution/resolver.test.ts b/src/test/resolution/resolver.test.ts index 7493d8ee..8f7506a8 100644 --- a/src/test/resolution/resolver.test.ts +++ b/src/test/resolution/resolver.test.ts @@ -96,9 +96,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -176,9 +178,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const katanaBinding: interfaces.Binding | undefined = @@ -230,9 +234,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const throwFunction: () => void = () => { @@ -304,9 +310,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const katanaBinding: interfaces.Binding | undefined = @@ -378,9 +386,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -462,9 +472,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -546,9 +558,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -639,9 +653,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -699,9 +715,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -753,9 +771,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -813,9 +833,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -868,9 +890,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -887,9 +911,11 @@ describe('Resolve', () => { const context2: interfaces.Context = plan( new MetadataReader(), container2, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja2: Ninja = resolveTyped(context2); @@ -943,9 +969,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = await resolveTyped>(context); @@ -964,9 +992,11 @@ describe('Resolve', () => { const context2: interfaces.Context = plan( new MetadataReader(), container2, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja2: Ninja = await resolveTyped>(context2); @@ -1011,9 +1041,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, crazyInjectableId, + { + isMultiInject: false, + }, ); const crazyInjectable: CrazyInjectable = await resolveTyped>(context); @@ -1081,9 +1113,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -1158,9 +1192,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -1217,9 +1253,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); @@ -1292,9 +1330,11 @@ describe('Resolve', () => { const context: interfaces.Context = plan( new MetadataReader(), container, - false, TargetTypeEnum.Variable, ninjaId, + { + isMultiInject: false, + }, ); const ninja: Ninja = resolveTyped(context); From b8dcd4f675a92782bf4110b7ffd4bf53d972e07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Wed, 4 Dec 2024 21:52:09 +0100 Subject: [PATCH 2/3] docs: update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72e6e4d8..3b3613d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +- Updated `interfaces.NextArgs` with optional `isOptional` param. ### Fixed From e38e7bc2f61972adbdc6909121599e0302306940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roberto=20Pintos=20L=C3=B3pez?= Date: Wed, 4 Dec 2024 22:02:15 +0100 Subject: [PATCH 3/3] test: add test case --- src/test/planning/planner.test.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/planning/planner.test.ts b/src/test/planning/planner.test.ts index d6d9f601..9a1b125d 100644 --- a/src/test/planning/planner.test.ts +++ b/src/test/planning/planner.test.ts @@ -124,6 +124,28 @@ describe('Planner', () => { expect(actualShurikenRequest?.target.serviceIdentifier).eql(shurikenId); }); + it('Should be able to create a basic plan with optional metadata', () => { + const ninjaId: string = 'Ninja'; + + const container: Container = new Container(); + + // Actual + const actualPlan: Plan = plan( + new MetadataReader(), + container, + TargetTypeEnum.Variable, + ninjaId, + { + isMultiInject: false, + isOptional: true, + }, + ).plan; + const actualNinjaRequest: interfaces.Request = actualPlan.rootRequest; + + expect(actualNinjaRequest.serviceIdentifier).eql(ninjaId); + expect(actualNinjaRequest.bindings).to.have.length(0); + }); + it('Should throw when circular dependencies found', () => { @injectable() class D {