feat: Add initialization error handling and refactor Inject

This commit is contained in:
2024-08-22 23:23:45 +02:00
committed by Max P.
parent ae9f25fe94
commit 5bc9aef9ad

View File

@@ -24,6 +24,7 @@ import { InitDelegate } from '../types/InitDelegate';
* @throws A {@link DependencyResolutionError} if the dependency is not found. * @throws A {@link DependencyResolutionError} if the dependency is not found.
* @throws A {@link InjectorError} if an error occurs during the injection process. * @throws A {@link InjectorError} if an error occurs during the injection process.
* @throws A {@link NoInstantiationMethodError} if the dependency does not have a constructor. * @throws A {@link NoInstantiationMethodError} if the dependency does not have a constructor.
* @throws An {@link InitializationError} if an error occurs during the initialization process.
* @example * @example
* ```ts * ```ts
* class MyClass { * class MyClass {
@@ -45,83 +46,59 @@ export function Inject<T, U>(
necessary = true, necessary = true,
) { ) {
return function (target: unknown, propertyKey: string | symbol): void { return function (target: unknown, propertyKey: string | symbol): void {
// Unique symbol to store the private property /**
const privatePropertyKey: unique symbol = Symbol(); * Function to evaluate the dependency lazily
// Get the DI container instance * to avoid circular dependencies, not found dependencies, etc.
const diContainer = TSinjex.getInstance(); * @returns The resolved dependency or undefined if the dependency is not found.
*/
// Function to evaluate the dependency lazily const resolve = (): T | undefined => {
// to avoid circular dependencies, not found dependencies, etc. return TSinjex.getInstance().resolve<T>(identifier, necessary);
const evaluate = (): T | undefined => {
return diContainer.resolve<T>(identifier, necessary);
}; };
// Define the property
Object.defineProperty(target, propertyKey, { Object.defineProperty(target, propertyKey, {
get() { get() {
// If the property is not defined, evaluate the dependency let instance: T | U | undefined;
if (!this.hasOwnProperty(privatePropertyKey)) {
if (init != null) { const dependency: T | undefined = tryAndCatch(
try { () => resolve(),
const dependency = evaluate(); necessary,
identifier,
DependencyResolutionError,
);
if (dependency != null) { if (dependency != null) {
if (typeof init === 'function') { const initFunction: (() => U) | undefined =
try { typeof init === 'function' && dependency != null
this[privatePropertyKey] = ? (): U => init(dependency)
init(dependency); : init === true && hasConstructor(dependency)
} catch (error) { ? (): U => new dependency() as U
if (necessary) : undefined;
throw new InitializationError(
if (init == null) instance = dependency;
else if (initFunction != null)
instance = tryAndCatch(
initFunction,
necessary,
identifier, identifier,
error, InitializationError,
); );
} else if (necessary)
} else if ( throw new NoInstantiationMethodError(identifier);
init === true && } else if (necessary)
hasConstructor(dependency)
) {
this[privatePropertyKey] = new dependency();
} else
throw new NoInstantiationMethodError(
identifier,
);
} else if (necessary) {
throw new DependencyResolutionError(identifier); throw new DependencyResolutionError(identifier);
}
} catch (error) {
if (necessary) {
if (
!(
error instanceof
NoInstantiationMethodError
) &&
!(
error instanceof
DependencyResolutionError
)
)
throw new InjectorError(identifier, error);
else throw error;
}
}
} else {
this[privatePropertyKey] = evaluate();
}
}
/** /**
* Replace itself with the resolved dependency * Replace itself with the resolved dependency
* for performance reasons. * for performance reasons.
*/ */
Object.defineProperty(this, propertyKey, { Object.defineProperty(this, propertyKey, {
value: this[privatePropertyKey], value: instance,
writable: false, writable: false,
enumerable: false, enumerable: false,
configurable: false, configurable: false,
}); });
return this[privatePropertyKey]; return instance;
}, },
/** /**
* Make the property configurable to allow replacing it * Make the property configurable to allow replacing it
@@ -131,6 +108,34 @@ export function Inject<T, U>(
}; };
} }
/**
* Tries to execute a function and catches any errors that occur.
* If the function is necessary and an error occurs, it throws the error
* with the specified error class and identifier.
* @param fn The function to execute.
* @param necessary If true, throws an error if an error occurs.
* @param identifier The identifier of the dependency.
* @param errorClass The error class to throw if an error occurs.
* @returns The result of the function or undefined if an error occurs and the function is not necessary.
*/
function tryAndCatch<ReturnType, ErrorType>(
fn: () => ReturnType,
necessary: boolean,
identifier?: Identifier,
errorClass?: ErrorType,
): ReturnType | undefined {
try {
return fn();
} catch (error) {
if (necessary)
throw new (errorClass != null ? errorClass : error)(
identifier ?? 'not specified',
error,
);
else return undefined;
}
}
/** /**
* Checks if an object has a constructor. * Checks if an object has a constructor.
* @param obj The object to check. * @param obj The object to check.