Skip to content

Commit fdbf854

Browse files
authored
Extract plan metadata interface (#1673)
* feat: update NextArgs with isOptional extract PlanMetadata interface as well * docs: update changelog * test: add test case
1 parent b391465 commit fdbf854

File tree

6 files changed

+173
-96
lines changed

6 files changed

+173
-96
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
### Changed
12+
- Updated `interfaces.NextArgs` with optional `isOptional` param.
1213

1314
### Fixed
1415

src/container/container.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
createMockRequest,
99
getBindingDictionary,
1010
plan,
11+
PlanMetadata,
1112
} from '../planning/planner';
1213
import { resolve } from '../resolution/resolver';
1314
import { BindingToSyntax } from '../syntax/binding_to_syntax';
@@ -319,8 +320,13 @@ class Container implements interfaces.Container {
319320
const request: interfaces.Request = createMockRequest(
320321
this,
321322
serviceIdentifier,
322-
key,
323-
value,
323+
{
324+
customTag: {
325+
key,
326+
value,
327+
},
328+
isMultiInject: false,
329+
},
324330
);
325331
bound = bindings.some((b: interfaces.Binding) => b.constraint(request));
326332
}
@@ -862,6 +868,27 @@ class Container implements interfaces.Container {
862868
return getNotAllArgs;
863869
}
864870

871+
private _getPlanMetadataFromNextArgs(
872+
args: interfaces.NextArgs<unknown>,
873+
): PlanMetadata {
874+
const planMetadata: PlanMetadata = {
875+
isMultiInject: args.isMultiInject,
876+
};
877+
878+
if (args.key !== undefined) {
879+
planMetadata.customTag = {
880+
key: args.key,
881+
value: args.value,
882+
};
883+
}
884+
885+
if (args.isOptional === true) {
886+
planMetadata.isOptional = true;
887+
}
888+
889+
return planMetadata;
890+
}
891+
865892
// Planner creates a plan and Resolver resolves a plan
866893
// one of the jobs of the Container is to links the Planner
867894
// with the Resolver and that is what this function is about
@@ -875,11 +902,9 @@ class Container implements interfaces.Container {
875902
let context: interfaces.Context = plan(
876903
this._metadataReader,
877904
this,
878-
args.isMultiInject,
879905
args.targetType,
880906
args.serviceIdentifier,
881-
args.key,
882-
args.value,
907+
this._getPlanMetadataFromNextArgs(args),
883908
args.avoidConstraints,
884909
);
885910

src/interfaces/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ namespace interfaces {
143143
avoidConstraints: boolean;
144144
contextInterceptor: (contexts: Context) => Context;
145145
isMultiInject: boolean;
146+
isOptional?: boolean;
146147
targetType: TargetType;
147148
serviceIdentifier: interfaces.ServiceIdentifier<T>;
148149
key?: string | number | symbol | undefined;

src/planning/planner.ts

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {
2828
} from './reflection_utils';
2929
import { Request } from './request';
3030

31-
function getBindingDictionary(
31+
export function getBindingDictionary(
3232
cntnr: interfaces.Container,
3333
): interfaces.Lookup<interfaces.Binding<unknown>> {
3434
return (
@@ -39,18 +39,13 @@ function getBindingDictionary(
3939
}
4040

4141
function _createTarget(
42-
isMultiInject: boolean,
4342
targetType: interfaces.TargetType,
4443
serviceIdentifier: interfaces.ServiceIdentifier,
45-
name: string,
46-
key?: string | number | symbol,
47-
value?: unknown,
44+
metadata: PlanMetadata,
4845
): interfaces.Target {
4946
const metadataList: Metadata[] = _getTargetMetadata(
50-
isMultiInject,
5147
serviceIdentifier,
52-
key,
53-
value,
48+
metadata,
5449
);
5550

5651
const classElementMetadata: ClassElementMetadata =
@@ -60,7 +55,7 @@ function _createTarget(
6055
throw new Error('Unexpected metadata when creating target');
6156
}
6257

63-
const target: Target = new TargetImpl(name, classElementMetadata, targetType);
58+
const target: Target = new TargetImpl('', classElementMetadata, targetType);
6459

6560
return target;
6661
}
@@ -122,21 +117,25 @@ function _getActiveBindings(
122117
}
123118

124119
function _getTargetMetadata(
125-
isMultiInject: boolean,
126120
serviceIdentifier: interfaces.ServiceIdentifier,
127-
key: string | number | symbol | undefined,
128-
value: unknown,
121+
metadata: PlanMetadata,
129122
): Metadata[] {
130-
const metadataKey: string = isMultiInject
123+
const metadataKey: string = metadata.isMultiInject
131124
? METADATA_KEY.MULTI_INJECT_TAG
132125
: METADATA_KEY.INJECT_TAG;
133126

134127
const metadataList: Metadata[] = [
135128
new Metadata(metadataKey, serviceIdentifier),
136129
];
137130

138-
if (key !== undefined) {
139-
metadataList.push(new Metadata(key, value));
131+
if (metadata.customTag !== undefined) {
132+
metadataList.push(
133+
new Metadata(metadata.customTag.key, metadata.customTag.value),
134+
);
135+
}
136+
137+
if (metadata.isOptional === true) {
138+
metadataList.push(new Metadata(METADATA_KEY.OPTIONAL_TAG, true));
140139
}
141140

142141
return metadataList;
@@ -312,24 +311,28 @@ function getBindings<T>(
312311
return bindings;
313312
}
314313

315-
function plan(
314+
export interface PlanMetadata {
315+
isMultiInject: boolean;
316+
isOptional?: boolean;
317+
customTag?: {
318+
key: string | number | symbol;
319+
value?: unknown;
320+
};
321+
}
322+
323+
export function plan(
316324
metadataReader: interfaces.MetadataReader,
317325
container: interfaces.Container,
318-
isMultiInject: boolean,
319326
targetType: interfaces.TargetType,
320327
serviceIdentifier: interfaces.ServiceIdentifier,
321-
key?: string | number | symbol,
322-
value?: unknown,
328+
metadata: PlanMetadata,
323329
avoidConstraints: boolean = false,
324330
): interfaces.Context {
325331
const context: Context = new Context(container);
326332
const target: interfaces.Target = _createTarget(
327-
isMultiInject,
328333
targetType,
329334
serviceIdentifier,
330-
'',
331-
key,
332-
value,
335+
metadata,
333336
);
334337

335338
try {
@@ -350,17 +353,14 @@ function plan(
350353
}
351354
}
352355

353-
function createMockRequest(
356+
export function createMockRequest(
354357
container: interfaces.Container,
355358
serviceIdentifier: interfaces.ServiceIdentifier,
356-
key: string | number | symbol,
357-
value: unknown,
359+
metadata: PlanMetadata,
358360
): interfaces.Request {
359361
const metadataList: Metadata[] = _getTargetMetadata(
360-
false,
361362
serviceIdentifier,
362-
key,
363-
value,
363+
metadata,
364364
);
365365

366366
const classElementMetadata: ClassElementMetadata =
@@ -382,5 +382,3 @@ function createMockRequest(
382382
);
383383
return request;
384384
}
385-
386-
export { plan, createMockRequest, getBindingDictionary };

src/test/planning/planner.test.ts

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,11 @@ describe('Planner', () => {
7979
const actualPlan: Plan = plan(
8080
new MetadataReader(),
8181
container,
82-
false,
8382
TargetTypeEnum.Variable,
8483
ninjaId,
84+
{
85+
isMultiInject: false,
86+
},
8587
).plan;
8688
const actualNinjaRequest: interfaces.Request = actualPlan.rootRequest;
8789
const actualKatanaRequest: interfaces.Request | undefined =
@@ -122,6 +124,28 @@ describe('Planner', () => {
122124
expect(actualShurikenRequest?.target.serviceIdentifier).eql(shurikenId);
123125
});
124126

127+
it('Should be able to create a basic plan with optional metadata', () => {
128+
const ninjaId: string = 'Ninja';
129+
130+
const container: Container = new Container();
131+
132+
// Actual
133+
const actualPlan: Plan = plan(
134+
new MetadataReader(),
135+
container,
136+
TargetTypeEnum.Variable,
137+
ninjaId,
138+
{
139+
isMultiInject: false,
140+
isOptional: true,
141+
},
142+
).plan;
143+
const actualNinjaRequest: interfaces.Request = actualPlan.rootRequest;
144+
145+
expect(actualNinjaRequest.serviceIdentifier).eql(ninjaId);
146+
expect(actualNinjaRequest.bindings).to.have.length(0);
147+
});
148+
125149
it('Should throw when circular dependencies found', () => {
126150
@injectable()
127151
class D {
@@ -233,9 +257,11 @@ describe('Planner', () => {
233257
const actualPlan: Plan = plan(
234258
new MetadataReader(),
235259
container,
236-
false,
237260
TargetTypeEnum.Variable,
238261
ninjaId,
262+
{
263+
isMultiInject: false,
264+
},
239265
).plan;
240266

241267
expect(actualPlan.rootRequest.serviceIdentifier).eql(ninjaId);
@@ -285,9 +311,11 @@ describe('Planner', () => {
285311
const actualPlan: Plan = plan(
286312
new MetadataReader(),
287313
container,
288-
false,
289314
TargetTypeEnum.Variable,
290315
ninjaId,
316+
{
317+
isMultiInject: false,
318+
},
291319
).plan;
292320

293321
// root request has no target
@@ -399,13 +427,9 @@ describe('Planner', () => {
399427
container.bind<Shuriken>(shurikenId).to(Shuriken);
400428

401429
const throwFunction: () => void = () => {
402-
plan(
403-
new MetadataReader(),
404-
container,
405-
false,
406-
TargetTypeEnum.Variable,
407-
ninjaId,
408-
);
430+
plan(new MetadataReader(), container, TargetTypeEnum.Variable, ninjaId, {
431+
isMultiInject: false,
432+
});
409433
};
410434

411435
expect(throwFunction).to.throw(`${ERROR_MSGS.NOT_REGISTERED} Katana`);
@@ -444,13 +468,9 @@ describe('Planner', () => {
444468
container.bind<Shuriken>(shurikenId).to(Shuriken);
445469

446470
const throwFunction: () => void = () => {
447-
plan(
448-
new MetadataReader(),
449-
container,
450-
false,
451-
TargetTypeEnum.Variable,
452-
ninjaId,
453-
);
471+
plan(new MetadataReader(), container, TargetTypeEnum.Variable, ninjaId, {
472+
isMultiInject: false,
473+
});
454474
};
455475

456476
expect(throwFunction).to.throw(`${ERROR_MSGS.AMBIGUOUS_MATCH} Katana`);
@@ -493,9 +513,11 @@ describe('Planner', () => {
493513
const actualPlan: Plan = plan(
494514
new MetadataReader(),
495515
container,
496-
false,
497516
TargetTypeEnum.Variable,
498517
ninjaId,
518+
{
519+
isMultiInject: false,
520+
},
499521
).plan;
500522

501523
// root request has no target
@@ -534,13 +556,9 @@ describe('Planner', () => {
534556
container.bind('Weapon').to(Katana);
535557

536558
const throwFunction: () => void = () => {
537-
plan(
538-
new MetadataReader(),
539-
container,
540-
false,
541-
TargetTypeEnum.Variable,
542-
'Weapon',
543-
);
559+
plan(new MetadataReader(), container, TargetTypeEnum.Variable, 'Weapon', {
560+
isMultiInject: false,
561+
});
544562
};
545563

546564
expect(throwFunction).not.to.throw();
@@ -561,13 +579,9 @@ describe('Planner', () => {
561579
container.bind(Ninja).toSelf();
562580

563581
const throwFunction: () => void = () => {
564-
plan(
565-
new MetadataReader(),
566-
container,
567-
false,
568-
TargetTypeEnum.Variable,
569-
Ninja,
570-
);
582+
plan(new MetadataReader(), container, TargetTypeEnum.Variable, Ninja, {
583+
isMultiInject: false,
584+
});
571585
};
572586

573587
expect(throwFunction).to.throw(
@@ -623,9 +637,11 @@ describe('Planner', () => {
623637
plan(
624638
new MetadataReader(),
625639
container,
626-
false,
627640
TargetTypeEnum.Variable,
628641
'Warrior',
642+
{
643+
isMultiInject: false,
644+
},
629645
);
630646
};
631647

@@ -659,13 +675,9 @@ describe('Planner', () => {
659675
container.bind<Katana>('Factory<Katana>').to(Katana);
660676

661677
const throwFunction: () => void = () => {
662-
plan(
663-
new MetadataReader(),
664-
container,
665-
false,
666-
TargetTypeEnum.Variable,
667-
'Ninja',
668-
);
678+
plan(new MetadataReader(), container, TargetTypeEnum.Variable, 'Ninja', {
679+
isMultiInject: false,
680+
});
669681
};
670682

671683
expect(throwFunction).to.throw(

0 commit comments

Comments
 (0)