Skip to content

Commit

Permalink
Extract plan metadata interface (#1673)
Browse files Browse the repository at this point in the history
* feat: update NextArgs with isOptional

extract PlanMetadata interface as well

* docs: update changelog

* test: add test case
  • Loading branch information
notaphplover authored Dec 4, 2024
1 parent b391465 commit fdbf854
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 96 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 30 additions & 5 deletions src/container/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
createMockRequest,
getBindingDictionary,
plan,
PlanMetadata,
} from '../planning/planner';
import { resolve } from '../resolution/resolver';
import { BindingToSyntax } from '../syntax/binding_to_syntax';
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -862,6 +868,27 @@ class Container implements interfaces.Container {
return getNotAllArgs;
}

private _getPlanMetadataFromNextArgs(
args: interfaces.NextArgs<unknown>,
): 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
Expand All @@ -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,
);

Expand Down
1 change: 1 addition & 0 deletions src/interfaces/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ namespace interfaces {
avoidConstraints: boolean;
contextInterceptor: (contexts: Context) => Context;
isMultiInject: boolean;
isOptional?: boolean;
targetType: TargetType;
serviceIdentifier: interfaces.ServiceIdentifier<T>;
key?: string | number | symbol | undefined;
Expand Down
60 changes: 29 additions & 31 deletions src/planning/planner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
} from './reflection_utils';
import { Request } from './request';

function getBindingDictionary(
export function getBindingDictionary(
cntnr: interfaces.Container,
): interfaces.Lookup<interfaces.Binding<unknown>> {
return (
Expand All @@ -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 =
Expand All @@ -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;
}
Expand Down Expand Up @@ -122,21 +117,25 @@ 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;

const metadataList: Metadata[] = [
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;
Expand Down Expand Up @@ -312,24 +311,28 @@ function getBindings<T>(
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 {
Expand All @@ -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 =
Expand All @@ -382,5 +382,3 @@ function createMockRequest(
);
return request;
}

export { plan, createMockRequest, getBindingDictionary };
92 changes: 52 additions & 40 deletions src/test/planning/planner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -122,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 {
Expand Down Expand Up @@ -233,9 +257,11 @@ describe('Planner', () => {
const actualPlan: Plan = plan(
new MetadataReader(),
container,
false,
TargetTypeEnum.Variable,
ninjaId,
{
isMultiInject: false,
},
).plan;

expect(actualPlan.rootRequest.serviceIdentifier).eql(ninjaId);
Expand Down Expand Up @@ -285,9 +311,11 @@ describe('Planner', () => {
const actualPlan: Plan = plan(
new MetadataReader(),
container,
false,
TargetTypeEnum.Variable,
ninjaId,
{
isMultiInject: false,
},
).plan;

// root request has no target
Expand Down Expand Up @@ -399,13 +427,9 @@ describe('Planner', () => {
container.bind<Shuriken>(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`);
Expand Down Expand Up @@ -444,13 +468,9 @@ describe('Planner', () => {
container.bind<Shuriken>(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`);
Expand Down Expand Up @@ -493,9 +513,11 @@ describe('Planner', () => {
const actualPlan: Plan = plan(
new MetadataReader(),
container,
false,
TargetTypeEnum.Variable,
ninjaId,
{
isMultiInject: false,
},
).plan;

// root request has no target
Expand Down Expand Up @@ -534,13 +556,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();
Expand All @@ -561,13 +579,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(
Expand Down Expand Up @@ -623,9 +637,11 @@ describe('Planner', () => {
plan(
new MetadataReader(),
container,
false,
TargetTypeEnum.Variable,
'Warrior',
{
isMultiInject: false,
},
);
};

Expand Down Expand Up @@ -659,13 +675,9 @@ describe('Planner', () => {
container.bind<Katana>('Factory<Katana>').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(
Expand Down
Loading

0 comments on commit fdbf854

Please sign in to comment.