Skip to content

Commit

Permalink
Strategy dependency implementation (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
Animagne authored Feb 3, 2025
1 parent 35c5556 commit f032b13
Show file tree
Hide file tree
Showing 97 changed files with 1,338 additions and 275 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"**/node_modules": true,
"**/out": true,
"**/lib": true,
"**/cypress": true,
"**/cypress/downloads": true,
"**/cypress/support": true,
"**/dist": true,
"common/temp": true
},
Expand Down
42 changes: 39 additions & 3 deletions cloud-agnostic/core/src/Bindable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DependenciesConfig, DependencyConfig } from "./DependencyConfig";
import { DependencyFactory } from "./DependencyFactory";
import { DIContainer } from "./DIContainer";
import { DependencyError } from "./internal";
import { StrategyDependency } from "./StrategyDependency";
import { Types } from "./Types";

export abstract class Bindable {
Expand All @@ -33,6 +34,22 @@ export abstract class Bindable {
factory.addDependency(dependencyBindings);
}

private bindStrategyDependencies(
container: DIContainer,
factory: DependencyFactory,
config: DependencyConfig
): void {
let first = true;
for (const dependency of factory.getStrategyDependencies()) {
if (!(dependency instanceof StrategyDependency)) continue;
if (first) {
first = false;
dependency.registerStrategy(container, config);
}
dependency.registerInstance(container, config);
}
}

private bindNamedDependencies(
container: DIContainer,
factory: DependencyFactory,
Expand Down Expand Up @@ -60,9 +77,28 @@ export abstract class Bindable {

this._dependencyFactories.forEach((factory) => {
const dependencyConfig = config[factory.dependencyType];
if (Array.isArray(dependencyConfig))
this.bindNamedDependencies(container, factory, dependencyConfig);
else this.bindDependency(container, factory, dependencyConfig);
switch (dependencyConfig.bindingStrategy) {
case "NamedDependency": {
this.bindNamedDependencies(
container,
factory,
dependencyConfig.instances
);
break;
}
case "StrategyDependency": {
this.bindStrategyDependencies(
container,
factory,
dependencyConfig.instance
);
break;
}
default: {
this.bindDependency(container, factory, dependencyConfig.instance);
break;
}
}
});
}
}
22 changes: 20 additions & 2 deletions cloud-agnostic/core/src/DependencyConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,29 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

export interface DependencyConfig {
dependencyName: string;
instanceName?: string;
}

export interface NamedDependencyConfig extends DependencyConfig {
instanceName: string;
}

export type TypedDependencyConfig =
| {
bindingStrategy: "Dependency";
instance: DependencyConfig;
}
| {
bindingStrategy: "NamedDependency";
instances: NamedDependencyConfig[];
}
| {
bindingStrategy: "StrategyDependency";
instance: DependencyConfig;
};

export interface DependenciesConfig {
[dependencyType: string]: DependencyConfig | DependencyConfig[];
[dependencyType: string]: TypedDependencyConfig;
}
4 changes: 4 additions & 0 deletions cloud-agnostic/core/src/DependencyFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class DependencyFactory {
return dependency;
}

public getStrategyDependencies(): Iterable<Dependency> {
return this._dependencyMap.values();
}

public getNamedDependency(name: string): NamedDependency {
const dependency = this.getDependency(name);
if (!(dependency instanceof NamedDependency))
Expand Down
38 changes: 38 additions & 0 deletions cloud-agnostic/core/src/StrategyDependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { Dependency } from "./Dependency";
import { DependencyConfig } from "./DependencyConfig";
import { DIContainer } from "./DIContainer";

export class StrategyInstance<T> {
constructor(
public readonly instance: T,
public readonly instanceName: string
) {}
}

export abstract class StrategyDependency extends Dependency {
protected abstract _registerInstance(
container: DIContainer,
childContainer: DIContainer,
config: DependencyConfig
): void;

public abstract registerStrategy(
container: DIContainer,
config: DependencyConfig
): void;

public registerInstance(
container: DIContainer,
config: DependencyConfig
): void {
const childContainer = container.createChild();

this.register(childContainer, config);
this._registerInstance(container, childContainer, config);
}
}
1 change: 1 addition & 0 deletions cloud-agnostic/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export * from "./DIContainer";
export * from "./Bindable";
export * from "./Dependency";
export * from "./NamedDependency";
export * from "./StrategyDependency";
export * from "./DependencyConfig";
export * from "./Types";
76 changes: 58 additions & 18 deletions cloud-agnostic/core/src/test/Bindable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,31 @@ import { Bindable, DependenciesConfig, NamedInstance } from "..";
import { DependencyError, DependencyTypeError } from "../internal";
import { InversifyWrapper } from "../inversify";

import { ConcreteTest, Test, TestConfig } from "./Test";
import { StrategyTestBase } from "./StrategyTest";
import { ConcreteTest, NamedTestConfig, Test, TestConfig } from "./Test";
import { ConcreteTestDependencyBindings } from "./TestDependency";
import {
TestSetup,
TestSetupNoDefaultDependencies,
TestSetupNoFactory,
TestSetupWithNamedInstances,
TestSetupWithStrategyInstances,
} from "./TestSetup";

const testConfig: TestConfig = {
dependencyName: "testName",
testProperty: "testProperty",
};

const testConfigWithOneInstance: TestConfig[] = [
const testConfigWithOneInstance: NamedTestConfig[] = [
{
dependencyName: "testName",
instanceName: "instanceName",
testProperty: "testProperty",
},
];

const testConfigWithMultipleInstances: TestConfig[] = [
const testConfigWithMultipleInstances: NamedTestConfig[] = [
{
dependencyName: "testName",
instanceName: "instanceName",
Expand All @@ -44,18 +46,34 @@ const testConfigWithMultipleInstances: TestConfig[] = [
];

const dependenciesConfigWithOneInstance: DependenciesConfig = {
testType: testConfigWithOneInstance,
testNamedType: {
bindingStrategy: "NamedDependency",
instances: testConfigWithOneInstance,
},
};

const dependenciesConfigWithMultipleInstances: DependenciesConfig = {
testType: testConfigWithMultipleInstances,
testNamedType: {
bindingStrategy: "NamedDependency",
instances: testConfigWithMultipleInstances,
},
};

const dependenciesStrategyConfig: DependenciesConfig = {
testStrategyType: {
bindingStrategy: "StrategyDependency",
instance: testConfig,
},
};

const dependenciesConfig: DependenciesConfig = {
testType: testConfig,
testType: {
bindingStrategy: "Dependency",
instance: testConfig,
},
};

function validateTestObject(test: Test, config: TestConfig) {
function validateNamedTestObject(test: Test, config: NamedTestConfig) {
expect(test instanceof ConcreteTest).to.be.true;
expect(test.instanceName === config.instanceName).to.be.true;
expect(test.property === config.testProperty).to.be.true;
Expand Down Expand Up @@ -104,10 +122,12 @@ describe(`${Bindable.name}`, () => {
});

it(`should throw if testName does not support named dependency instances`, () => {
const setup = new TestSetup(
InversifyWrapper.create(),
dependenciesConfigWithMultipleInstances
);
const setup = new TestSetup(InversifyWrapper.create(), {
testType: {
bindingStrategy: "NamedDependency",
instances: testConfigWithOneInstance,
},
});

const testedFunction = () => setup.start();
expect(testedFunction)
Expand All @@ -127,7 +147,7 @@ describe(`${Bindable.name}`, () => {
setup.start();

const test = setup.container.resolveNamed(Test, "instanceName");
validateTestObject(test, testConfigWithOneInstance[0]);
validateNamedTestObject(test, testConfigWithOneInstance[0]);
});

it(`should resolve multiple registered named dependency instances by name`, () => {
Expand All @@ -140,15 +160,15 @@ describe(`${Bindable.name}`, () => {

const test = setup.container.resolveNamed(
Test,
testConfigWithMultipleInstances[0].instanceName!
testConfigWithMultipleInstances[0].instanceName
);
const test2 = setup.container.resolveNamed(
Test,
testConfigWithMultipleInstances[1].instanceName!
testConfigWithMultipleInstances[1].instanceName
);

validateTestObject(test, testConfigWithMultipleInstances[0]);
validateTestObject(test2, testConfigWithMultipleInstances[1]);
validateNamedTestObject(test, testConfigWithMultipleInstances[0]);
validateNamedTestObject(test2, testConfigWithMultipleInstances[1]);
});

it(`should resolve multiple registered named dependency instances as array`, () => {
Expand All @@ -164,7 +184,27 @@ describe(`${Bindable.name}`, () => {
);

expect(tests.length).to.be.equal(2);
validateTestObject(tests[0]?.instance, testConfigWithMultipleInstances[0]);
validateTestObject(tests[1]?.instance, testConfigWithMultipleInstances[1]);
validateNamedTestObject(
tests[0]?.instance,
testConfigWithMultipleInstances[0]
);
validateNamedTestObject(
tests[1]?.instance,
testConfigWithMultipleInstances[1]
);
});

it(`should resolve strategy dependency with multiple instances`, () => {
const setup = new TestSetupWithStrategyInstances(
InversifyWrapper.create(),
dependenciesStrategyConfig
);

setup.start();

const test = setup.container.resolve(StrategyTestBase);

expect(test.method("testName1")).to.be.equal("testProperty1");
expect(test.method("testName2")).to.be.equal("testProperty2");
});
});
34 changes: 34 additions & 0 deletions cloud-agnostic/core/src/test/StrategyTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { TestConfig } from "./Test";

export abstract class StrategyTestBase {
abstract method(instance: string): string;
}

export class ConcreteStrategyTest extends StrategyTestBase {
constructor(private _config: TestConfig) {
super();
}

public get dependencyName(): string {
return this._config.dependencyName;
}

public method(_instance: string): string {
return this._config.testProperty;
}
}

export class StrategyTest extends StrategyTestBase {
constructor(private _instances: Map<string, StrategyTestBase>) {
super();
}

public method(instance: string): string {
return this._instances.get(instance)!.method(instance);
}
}
13 changes: 10 additions & 3 deletions cloud-agnostic/core/src/test/Test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,20 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { DependencyConfig } from "..";
import { DependencyConfig, NamedDependencyConfig } from "..";

export interface TestConfig extends DependencyConfig {
testProperty: string;
}

export const testConfigType = Symbol.for("TestConfig");
export interface NamedTestConfig extends NamedDependencyConfig {
testProperty: string;
}

export const testTypes = {
testConfigType: Symbol.for("TestConfig"),
namedTestConfigType: Symbol.for("NamedTestConfig"),
};

export abstract class Test {
abstract get property(): string;
Expand All @@ -26,6 +33,6 @@ export class ConcreteTest extends Test {
}

public get instanceName(): string | undefined {
return this._config.instanceName;
return (this._config as unknown as NamedDependencyConfig).instanceName;
}
}
Loading

0 comments on commit f032b13

Please sign in to comment.