Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract plan metadata interface #1673

Merged
merged 3 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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 @@
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 @@
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;

Check warning on line 886 in src/container/container.ts

View check run for this annotation

Codecov / codecov/patch

src/container/container.ts#L886

Added line #L886 was not covered by tests
}

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 @@
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
Loading