-
Notifications
You must be signed in to change notification settings - Fork 192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow "unknown" values to be boomified #291
Comments
I'm no TS wizard but I think using a flag would just make things more complicated. From my understanding of TS we should type it as Supporting |
Not really in favor of that, boom is not supposed to be a catch-all API, the constructor already accepts strings or errors which is I believe as far as it should go. You can just |
Alright, makes sense. I don't mind keeping it as is. @Marsup solution is simple enough. |
Typescript is there to ensure type safety. For example there is no point in doing this: function inc(a: number) {
if (typeof a !== 'number') throw new TypeError('a is expected to be a number')
// ...
} The whole point of using typescript is to ensure that, as long as you respect the type definitions (no In the case of Boom, this is exactly what is happening: the My point is it should do either, not both. Now I agree that anything catch (err) {
throw boomify(err as Error)
} But that causes a lot of overhead (every catch (err) {
throw boomify(err) // Note: a TypeError will be thrown instead if err is not an Error
} |
We could however create a new method / option / breaking change that expects |
Actually, a breaking change that always returns a |
Hapi already takes measures to ensure it doesn't pass non-Errors, which could be avoided. It would also fix this url parsing logic, where a custom query |
Just to make things clear: There are 2 things when it comes to typing a function using typescript. There is the "external" signature and then there is the typing of the arguments within the function's body. Usually they both are the same. But sometimes we need to distinguish the two (see Function Overloading). In the current implementation, and if As you can see, The proper TS implementation would be either: export function boomify(err: unknown): Boom {
if (!(err instanceof Error)) {
throw new TypeError('Invalid err')
}
// Here "err" can safely be used as an Error
return new Boom()
} or export function boomify(err: Error): Boom {
// no need to check for err's type at runtime
return new Boom()
} What is currently implemented is more like this: export function boomify(err: Error): Boom;
export function boomify(err: unknown): Boom {
if (!(err instanceof Error)) {
// This is included for extra safety when boom is used in environment not supporting type checking
throw new TypeError('Invalid err')
}
// Here err can safely be used as an Error
return new Boom()
} As you can see the current implementation (3) restricts its public API in a way that is not needed for its own implementation to be type safe. Now I really don't think that This is why I think that we should have the ability pass any argument to boomify and get a "system error" if that fails. What I mean is that only the typing should be changed, not the implementation. And since that would be a breaking change, we need either a new option to See this example (playground): Adding a new option would be kind of silly as Additional reflections:
|
Maybe something like: declare function boomifyAny(err: unknwon, { allowNonError = true }: { allowNonError: boolean }); if |
As I see it, the current implementation and type signature would also be valid for TS, except the check would be done using an assertion. It is not unheard of to assert that the passed parameters are what you expect, especially for public APIs and where the passed
That really depends on the context. Eg. for hapi handlers, we always want a |
I made a few tests: matthieusieben@31b80ef And a PR #293 Let me know what you think |
Bumping this and the PR #293 for any further discussion/review/thoughts. |
Still not sure about this. Typescript is a pita in this area, there are whole articles about this. We could relax the api, it would be caught by the assertion anyway, but it seems like a footgun at runtime. If we remain strict about it, the error type would be double-checked, so it's not ideal either. There's no good decision here, but usually errors are errors, and we have the safety net, so maybe just relax the signature, it doesn't seem any different from the usual assumption most of us do in standard js, nobody checks the type of thrown errors 🤷♂️ (sorry for the swinging opinion) |
As I see it, the options are:
There are two kinds of breaking changes here:
I believe that it is safer to avoid making a breaking change that impacts runtime (= "soft" breaking change). But I think that having the ability to safely turn anything into an error could really be an added benefit of Boom. Edit: added option 4) |
Let's agree on the option to follow before we agree on the potential change in signature 😀 |
Thanks for the overview. As you probably guess I favour option 2. Option 4 seems intriguing, but I would prefer not to go that route, unless someone can demonstrate a case where the option makes sense. I much prefer to keep the API simple (also the main reason I don't like option 3).
In this specific case, I'm disinclined to agree since the error throwing is already causing unintended behaviour, and never throwing is likely to fix subtle bugs, see #291 (comment) for details. FYI, just verified that throwing a non-Error from a custom query parser does crash the server. |
In the case of no. 2, what error would be returned: would it be a |
I'm gonna let you guys decide on that ;-) FWIW, here is what I would do: if (!(err instanceof Error)) {
return badImplementation('Trying to turn a non-error into an error', err)
} |
I would probably do it like Having another look at it, I'm conflicted about going for 2, since I think I'm mostly in favour of option 0 now. And possibly update the Boom |
Well Can I suggest another options then: |
Maybe also default const useMessage = typeof message === 'string'
const { statusCode = 500, data = useMessage ? null : message ?? null, ctor = exports.Boom } = options;
const error = new Error(useMessage ? message : undefined); |
After taking some time to think about it, maybe option 3 is not that bad. A helper inside your app is not that hard to create and import, but if every typescript developer out there ends up sharing the same snippet, might as well integrate it into boom. Just have to come up with a name that makes sense. |
I just started to use Boom in typescript, and came here to see why boomify doesn't support the unknown errors, which is the most common use case. I'd go for option 1 or 2 for simplicity since all the guides already teach you to use |
Support plan
Context
What problem are you trying to solve?
Since typescript 4.4, the type of the
err
in a catch block will be "unknown".There are two solutions, both of which result in a lot of added code
Since
boomify
is already an error wrapper utility, having to do either of these is anoying.Do you have a new or modified API suggestion to solve the problem?
Could we allow the first argument of
boomify()
to be of typeunknown
?If you don't want to change the current signature, we could also make this optional:
The text was updated successfully, but these errors were encountered: