Improve error context for DI

This commit is contained in:
Garrett Mills 2022-08-10 21:53:56 -05:00
parent 9b47d2ac99
commit 3d836afa59
4 changed files with 60 additions and 20 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@extollo/lib", "name": "@extollo/lib",
"version": "0.13.9", "version": "0.13.10",
"description": "The framework library that lifts up your code.", "description": "The framework library that lifts up your code.",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",

View File

@ -8,7 +8,16 @@ import {
TypedDependencyKey, TypedDependencyKey,
} from './types' } from './types'
import {AbstractFactory} from './factory/AbstractFactory' import {AbstractFactory} from './factory/AbstractFactory'
import {Awaitable, collect, Collection, ErrorWithContext, globalRegistry, hasOwnProperty, logIfDebugging} from '../util' import {
Awaitable,
collect,
Collection,
ErrorWithContext,
globalRegistry,
hasOwnProperty,
logIfDebugging,
withErrorContext,
} from '../util'
import {Factory} from './factory/Factory' import {Factory} from './factory/Factory'
import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError' import {DuplicateFactoryKeyError} from './error/DuplicateFactoryKeyError'
import {ClosureFactory} from './factory/ClosureFactory' import {ClosureFactory} from './factory/ClosureFactory'
@ -376,7 +385,7 @@ export class Container {
/** /**
* Resolve the dependency key. If a singleton value for that key already exists in this container, * Resolve the dependency key. If a singleton value for that key already exists in this container,
* return that value. Otherwise, use the factory an given parameters to produce and return the value. * return that value. Otherwise, use the factory and given parameters to produce and return the value.
* @param {DependencyKey} key * @param {DependencyKey} key
* @param {...any} parameters * @param {...any} parameters
*/ */
@ -432,11 +441,16 @@ export class Container {
// Create the dependencies for the factory // Create the dependencies for the factory
const keys = factory.getDependencyKeys().filter(req => this.hasKey(req.key)) const keys = factory.getDependencyKeys().filter(req => this.hasKey(req.key))
const dependencies = keys.map<ResolvedDependency>(req => { const dependencies = keys.map<ResolvedDependency>(req => {
return { return withErrorContext(() => {
paramIndex: req.paramIndex, return {
key: req.key, paramIndex: req.paramIndex,
resolved: this.resolveAndCreate(req.key), key: req.key,
} resolved: this.resolveAndCreate(req.key),
}
}, {
producingToken: factory.getTokenName(),
constructorDependency: req,
})
}).sortBy('paramIndex') }).sortBy('paramIndex')
// Build the arguments for the factory, using dependencies in the // Build the arguments for the factory, using dependencies in the
@ -460,7 +474,12 @@ export class Container {
factory.getInjectedProperties().each(dependency => { factory.getInjectedProperties().each(dependency => {
logIfDebugging('extollo.di.injector', 'Resolving injected dependency:', dependency) logIfDebugging('extollo.di.injector', 'Resolving injected dependency:', dependency)
if ( dependency.key && inst ) { if ( dependency.key && inst ) {
(inst as any)[dependency.property] = this.resolveAndCreate(dependency.key) withErrorContext(() => {
(inst as any)[dependency.property] = this.resolveAndCreate(dependency.key)
}, {
producingToken: factory.getTokenName(),
propertyDependency: dependency,
})
} }
}) })
@ -499,14 +518,22 @@ export class Container {
this.checkForMakeCycles() this.checkForMakeCycles()
try { try {
if (this.hasKey(target)) { const result = withErrorContext(() => {
const realized = this.resolveAndCreate(target, ...parameters) if (this.hasKey(target)) {
Container.makeStack.pop() const realized = this.resolveAndCreate(target, ...parameters)
return realized Container.makeStack?.pop()
} else if (typeof target !== 'string' && isInstantiable(target)) { return realized
const realized = this.produceFactory(new Factory(target), parameters) } else if (typeof target !== 'string' && isInstantiable(target)) {
Container.makeStack.pop() const realized = this.produceFactory(new Factory(target), parameters)
return realized Container.makeStack?.pop()
return realized
}
}, {
makeStack: Container.makeStack,
})
if ( result ) {
return result
} }
} catch (e: unknown) { } catch (e: unknown) {
Container.makeStack.pop() Container.makeStack.pop()

View File

@ -1,11 +1,12 @@
import {DependencyKey} from '../types' import {DependencyKey} from '../types'
import {ErrorWithContext} from '../../util'
/** /**
* Error thrown when a dependency key that has not been registered is passed to a resolver. * Error thrown when a dependency key that has not been registered is passed to a resolver.
* @extends Error * @extends Error
*/ */
export class InvalidDependencyKeyError extends Error { export class InvalidDependencyKeyError extends ErrorWithContext {
constructor(key: DependencyKey) { constructor(key: DependencyKey, context: {[key: string]: any} = {}) {
super(`No such dependency is registered with this container: ${key}`) super(`No such dependency is registered with this container: ${key}`, context)
} }
} }

View File

@ -41,4 +41,16 @@ export abstract class AbstractFactory<T> {
* @return Collection<PropertyDependency> * @return Collection<PropertyDependency>
*/ */
abstract getInjectedProperties(): Collection<PropertyDependency> abstract getInjectedProperties(): Collection<PropertyDependency>
/**
* Get a human-readable name of the token this factory produces.
* This is meant for debugging output only.
*/
public getTokenName(): string {
if ( typeof this.token === 'string' ) {
return this.token
}
return this.token.name ?? '(unknown token)'
}
} }