Typed error
ref #568
A design proposal for structurally expressing errors. This PR makes it possible to support custom error messages and i18n. This library will be available for displaying errors to users.
For example, size(string(), 1, 100) outputs the error message ${expected} with a length ${of} but received one with a length of ' ${length}'. This change outputs the following error content in addition to the message. It is possible to format the error message according to the context and language.
interface SizeErrorDetail<T extends number | Date> extends ErrorDetail {
class: 'size'
actually: T
min: T | undefined
max: T | undefined
minExlusive: boolean
maxExlusive: boolean
}
- todos
- resolve conflicts
- think about compatibility
@kamijin-fanta this is cool! A few ideas...
- I think the
Egeneric should maybe represent just a dictionary of values that will be attached to a struct error instead of needing for it toextends Erroritself? - I think the details should be mixed directly into the error object, instead of living in a
detailsproperty. - We can make the existing
Failure.refinementprop part of the error dictionary properties instead, which is nice. And we can do the same fortypetoo. - I don't think the
SizeErrorDetailshould be common acrossmin/maxlike that, and instead each refinement should be self-contained. - Couldn't tell if this already happens, but it would be good to keep the ability to return
true/falseinstead of requiring using a details object, since this makes the library really easy to pick up for people. - The
InferErrorutility is a great idea!
Which would make a min(number(), 0) struct look something like:
Struct<number, null, {
type: 'number'
} | {
refinement: 'min'
min: 0,
exclusive: false
}>
And Failure might look like:
type Failure<E extends Record<string, any>> = {
value: any
key: any
branch: Array<any>
path: Array<any>
message: string
} & E
@ianstormtaylor Thanks for the idea!
The reason I made min / max common is that I feel that I often need it when displaying error messages. For example, "A value of 10 to 20 is allowed. It was actually 25."
I work on a Failure type change.
Do you have any idea for the definition of toFailure, Result and Validator when rewriting to type Failure E extends Record<string, any> ? For example, it is difficult to generate Failure<E> from the following Result type.
export type Result<E extends Record<string, any>> =
| boolean
| string
| Partial<Failure<E>>
| Iterable<boolean | string | Partial<Failure<E>>>
It may be possible to solve it by redefining the Result type as follows.
export type DescribedResult<E extends Record<string, any>> =
| boolean
| string
| Partial<Failure<E>>
| Iterable<boolean | string | Partial<Failure<E>>>
export type SimpleResult = boolean | string | Iterable<boolean | string>
export function define<T>(
name: string,
validator: SimpleValidator
): Struct<T, null, {}>;
Also, it can be easily implemented by giving up the type definition for detailed error information. Of course I want to avoid this if possible.
@kamijin-fanta good question! I tried messing with it in the TypeScript Playground and came up with this approach which may help? It keeps a "props" dictionary as the third argument which then determines which "results" are allowed to be returned by a validation function.
I wasn't sure how to limit it more than object, but it might work for now?
Hi, what's the status for this PR?
@xiaoyu-tamu sorry. I couldn't get enough time and left it. I would like to work on it when other work is done, but I would like to welcome other people's contributions.
Maybe the maintainers would consider taking this code in and continuing on it?