customErrorFactory does not act like CustomError class
ashleyw opened this issue · 2 comments
Hi!
I've noticed some inconsistencies between using customErrorFactory
and the CustomError
class directly. When using the factory, the return type resolves as just a basic Error
instance, i.e. it does not contain any extra properties:
import { CustomError, customErrorFactory } from 'ts-custom-error';
interface Props {
env: string;
message: string;
}
export const EnvironmentVariableMissingViaFactory = customErrorFactory<Props>(
function EnvironmentVariableMissing(env: string) {
this.message = `Hello world`;
this.env = env;
},
);
class EnvironmentVariableMissingViaClass extends CustomError {
constructor(public env: string, message: string = `Hello world`) {
super(message);
}
}
// I think this `InstanceType<typeof ...>` signature is correct?
function myFunc1(error: InstanceType<typeof EnvironmentVariableMissingViaFactory>) {
// typeof error === Error
return {
msg: error.message,
env: error.env,
};
// => ❌ Property 'env' does not exist on type 'Error'
}
function myFunc2(error: EnvironmentVariableMissingViaClass) {
// typeof error === EnvironmentVariableMissingViaClass
return {
msg: error.message,
env: error.env,
};
// {
// msg: (property) Error.message: string,
// env: (property) EnvironmentVariableMissingViaClass.env: string
// }
}
const myErr = new EnvironmentVariableMissingViaFactory('PORT');
function myFunc3(error: typeof myErr) {
// typeof error === CustomErrorInterface & Props
return {
msg: error.message,
env: error.env,
};
// {
// msg: (property) message: string
// env: (property) Props.env: string
// }
}
As a real-world example, I'm using Ava for testing, which has a throws<T extends Error>()
method:
const key = 'PORT';
const error = t.throws<InstanceType<typeof EnvironmentVariableMissingViaFactory>>(() => {
GetEnv<number>(key);
}, EnvironmentVariableMissingViaFactory);
t.is(error.env, key);
// => ❌ Property 'env' does not exist on type 'Error'
const key = 'PORT';
const error = t.throws<EnvironmentVariableMissingViaClass>(() => {
GetEnv<number>(key);
}, EnvironmentVariableMissingViaClass);
t.is(error.env, key);
// ✅ EnvironmentVariableMissingViaClass.env: string
Is this behaviour intentional? Isn't it possible for customErrorFactory
to return a CustomError
instance to ensure both options are consistent?
Thanks!
- Typescript: 3.4.5
- ts-custom-error: 3.0.0
Thanks for your feedback, with great details ;)
I think this
InstanceType<typeof ...>
signature is correct?
As your last example myFunc3
shows, the actual type of an Error instance returned by constructor build with customErrorFactory is CustomErrorInterface & Props
where CustomErrorInterface
is just anythig that extends Error
.
You can create a type to use in signatures:
type EnvironmentVariableMissingErrorViaFactory = CustomErrorInterface & Props
function myFunc1(error: EnvironmentVariableMissingErrorViaFactory)...
N.B. unfortunately InstanceType
seems not to be helpfull in this case:
type EnvironmentVariableMissingViaFactory2 = InstanceType<CustomErrorConstructor<Props>> // e.q. Error
function myFunc1(error: EnvironmentVariableMissingViaFactory2)...
Is this behaviour intentional?
yes, and no. I looked for typescript trick to express a 'dynamic' interface using generics (something like
CustomError<Props> === CustomError & Props
) without finding one, so I came up with returning CustomErrorInterface & Props
... and the interface composition can't be avoided IMO because the actual interface is created inside the factory.
The reason why I used CustomErrorInterface & Props
and not CustomError & Props
was just to avoid unecessary ties between factory & class because I assumed one will only use one or the other.
Isn't it possible for customErrorFactory to return a CustomError instance to ensure both options are consistent?
The only obvious solution I can think of is to replace CustomErrorInterface
in https://github.com/adriengibrat/ts-custom-error/blob/master/src/factory.ts#L3 by an import of CustomError
.
You'll still have to create a custom type when using factory, but at least class & factory will use the same base type...
type EnvironmentVariableMissingErrorViaFactory = CustomError & Props
function myFunc1(error: EnvironmentVariableMissingErrorViaFactory)...
Was my explaination helpfull?
Do you think using CustomError
as a base type in factory.ts is worth a fix ?
Closing, feel free to reopen if needed.