Open timoxley opened 3 years ago
Ok, I found an ugly workaround that just rewrites the error.message
property instead of creating a new Error
.
One downside is that if the error's .stack
getter has already been accessed, then the updated .message
won't appear in the error's toString
, and sometimes .message
can be frozen, but IMO still better to throw the original error.
// horrible, hacky workaround to prevent tsyringe from replacing useful,
// specific errors produced by constructors with tsyringe-specific errors that
// look like "Cannot inject the dependency". These errors lose the original
// error's stack, constructor and any additional context that was attached for
// control flow or debugging purposes e.g. err.code or err.syscall.
//
// See: https://github.com/microsoft/tsyringe/issues/177
// @ts-nocheck
import { container } from 'tsyringe'
// eslint-disable-next-line import/no-unresolved
import { isTokenDescriptor, isTransformDescriptor } from 'tsyringe/dist/cjs/providers/injection-token'
import { formatErrorCtor } from 'tsyringe/dist/cjs/error-helpers'
// Should be identical to original resolveParams, but replaces new Error with err.message = formatErrorCtor
// See: https://github.com/microsoft/tsyringe/blob/0cb911b799ccd0b3079629865f1a8fb04cc49658/src/dependency-container.ts#L495-L525
container.constructor.prototype.resolveParams = function resolveParams(context, ctor) {
return (param, idx) => {
try {
if (isTokenDescriptor(param)) {
if (isTransformDescriptor(param)) {
return param.multiple
? this.resolve(param.transform).transform(
this.resolveAll(param.token),
...param.transformArgs
)
: this.resolve(param.transform).transform(
this.resolve(param.token, context),
...param.transformArgs
)
// eslint-disable-next-line no-else-return
} else {
return param.multiple
? this.resolveAll(param.token)
: this.resolve(param.token, context)
}
// eslint-disable-next-line no-else-return
} else if (isTransformDescriptor(param)) {
return this.resolve(param.transform, context).transform(
this.resolve(param.token, context),
...param.transformArgs
)
}
return this.resolve(param, context)
} catch (e) {
e.message = formatErrorCtor(ctor, idx, e)
throw e
}
}
}
The tsyringe error
"Cannot inject the dependency"
handler is great while you're trying to iron out issues with the DI, but a consumer doesn't care/shouldn't know anything about tsyringe, nor should tsyringe be silently throwing away useful context from the original error. The way tsyringe forcibly wraps errors circumvents constructors from being able to throw more specific errors and prevents callers from responding to those specific errors, instead making every error into a vanillaError
that mostly talks about tsyringe.See line 522:
https://github.com/microsoft/tsyringe/blob/0cb911b799ccd0b3079629865f1a8fb04cc49658/src/dependency-container.ts#L495-L524
The tsyringe error does grab the original error's
.message
string as the"Reason: "
, but critically it loses all other context from the original error including the original error's stack, constructor and any additional properties that may have been set on it to aid debugging or control flow e.g.RangeError
vsSyntaxError
orerror.code
orerr.syscall
.Ideally there would be some way to prevent tsyringe from wrapping errors, or perhaps it could attach the original error to the wrapped error so the caller can optionally ignore the tsyringe error and rethrow the original error.
I'd be happy to even find an ugly hack that works around this but I don't currently see a path to even hack it to behave better with monkeypatching etc.