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) {
try {
const dependency = evaluate();
if (dependency != null) { const dependency: T | undefined = tryAndCatch(
if (typeof init === 'function') { () => resolve(),
try { necessary,
this[privatePropertyKey] = identifier,
init(dependency); DependencyResolutionError,
} catch (error) { );
if (necessary)
throw new InitializationError( if (dependency != null) {
identifier, const initFunction: (() => U) | undefined =
error, typeof init === 'function' && dependency != null
); ? (): U => init(dependency)
} : init === true && hasConstructor(dependency)
} else if ( ? (): U => new dependency() as U
init === true && : undefined;
hasConstructor(dependency)
) { if (init == null) instance = dependency;
this[privatePropertyKey] = new dependency(); else if (initFunction != null)
} else instance = tryAndCatch(
throw new NoInstantiationMethodError( initFunction,
identifier, necessary,
); identifier,
} else if (necessary) { InitializationError,
throw new DependencyResolutionError(identifier); );
} else if (necessary)
} catch (error) { throw new NoInstantiationMethodError(identifier);
if (necessary) { } else if (necessary)
if ( throw new DependencyResolutionError(identifier);
!(
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.