Skip to content

Commit

Permalink
Remove 'choices' option from StructuredValidator signature
Browse files Browse the repository at this point in the history
  • Loading branch information
jsamr committed Nov 21, 2022
1 parent 8ecd933 commit 328e056
Show file tree
Hide file tree
Showing 3 changed files with 20 additions and 36 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ The typing logic behaves differently here:
- When no types can be inferred from context, output type defaults to any.
- Otherwise, infers type from `default` or `devDefault`.
- Also allows validator parametrized types.
- Finally, the generated validator disallow `choices` parameter.

Below is an example of a validator for query parameters (e.g. `option1=foo&option2=bar`)

Expand Down
22 changes: 16 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,22 @@ export interface Spec<T> {
}

export type OptionalSpec<T> = Omit<Spec<T>, 'default'> & { default: undefined }
export type OptionalChoiceless = Omit<OptionalSpec<unknown>, 'choices'>
export type OptionalTypelessSpec = Omit<OptionalSpec<unknown>, 'choices'>

export type RequiredSpec<T> = (Spec<T> & { default: NonNullable<T> }) | Omit<Spec<T>, 'default'>
export type RequiredTypelessSpec = Omit<Spec<unknown>, 'choices' | 'default'> & {
devDefault?: undefined
}
export type RequiredChoicelessSpecWithType<T> = Omit<Spec<T>, 'choices'> &

export type ChoicelessOptionalSpec<T> = Omit<Spec<T>, 'default' | 'choices'> & {
default: undefined
}

export type ChoicelessRequiredSpec<T> =
| (Omit<Spec<T>, 'choices'> & { default: NonNullable<T> })
| Omit<Spec<T>, 'default' | 'choices'>

export type ChoicelessRequiredSpecWithType<T> = ChoicelessRequiredSpec<T> &
(
| {
default: NonNullable<T>
Expand Down Expand Up @@ -65,7 +74,7 @@ export interface BaseValidator<BaseT> {
// These function overloads enable nuanced type inferences for optimal DX
// This will prevent specifying "default" alone from narrowing down output type.
// https://www.typescriptlang.org/docs/handbook/2/functions.html#function-overloads
(spec: RequiredChoicelessSpecWithType<BaseT>): RequiredValidatorSpec<BaseT>
(spec: ChoicelessRequiredSpecWithType<BaseT>): RequiredValidatorSpec<BaseT>
<T extends BaseT>(spec?: RequiredSpec<T>): RequiredValidatorSpec<T>
<T extends BaseT>(spec: OptionalSpec<T>): OptionalValidatorSpec<T>
}
Expand All @@ -75,6 +84,7 @@ export interface BaseValidator<BaseT> {
// - it has no supertype
// - it fallbacks to 'any' when no type information can be inferred
// from the spec object.
// - One can't pass "choices" since choices uses reference equality.
export interface StructuredValidator {
// Defaults to any when no argument (prevents 'unknown')
(): RequiredValidatorSpec<any>
Expand All @@ -83,9 +93,9 @@ export interface StructuredValidator {
// Make sure we grab 'any' when no type inference can be made
// otherwise it would resolve to 'unknown'
(spec: RequiredTypelessSpec): RequiredValidatorSpec<any>
(spec: OptionalChoiceless): OptionalValidatorSpec<any>
<T>(spec: OptionalSpec<T>): OptionalValidatorSpec<T>
<T>(spec: RequiredSpec<T>): RequiredValidatorSpec<T>
(spec: OptionalTypelessSpec): OptionalValidatorSpec<any>
<T>(spec: ChoicelessOptionalSpec<T>): OptionalValidatorSpec<T>
<T>(spec: ChoicelessRequiredSpec<T>): RequiredValidatorSpec<T>
}

export type FromSpecsRecord<S> = {
Expand Down
33 changes: 3 additions & 30 deletions tests/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,12 @@ describe('validators types', () => {
})
test('structured data validator', () => {
const validator = makeStructuredValidator(() => ({}))
const tt = validator({ default: {} });
expectTypeOf(validator()).toEqualTypeOf<RequiredValidatorSpec<any>>()
expectTypeOf(validator({ default: {} as any })).toEqualTypeOf<RequiredValidatorSpec<any>>()
expectTypeOf(validator({ default: undefined })).toEqualTypeOf<OptionalValidatorSpec<any>>()
expectTypeOf(validator({ default: undefined, choices: [{ foo: 'bar' }] })).toEqualTypeOf<
OptionalValidatorSpec<{ foo: 'bar' }>
>()
//@ts-expect-error - Choices not available for structured data
validator({ choices: [{ foo: 'bar' }] })
expectTypeOf(validator({ devDefault: undefined })).toEqualTypeOf<RequiredValidatorSpec<any>>()
expectTypeOf(validator({ devDefault: { foo: 'bar' } })).toEqualTypeOf<
RequiredValidatorSpec<{ foo: string }>
Expand Down Expand Up @@ -188,33 +188,6 @@ describe('validators types', () => {
hello: string
}>
>()
expectTypeOf(
validator({
choices: [
{ hello: 'world', option: false },
{ hello: 'world', option: true },
],
}),
).toEqualTypeOf<
RequiredValidatorSpec<{
hello: string
option: boolean
}>
>()
expectTypeOf(
validator({
choices: [
{ hello: 'world', option: false },
{ hello: 'world', option: true },
],
default: { hello: 'world', option: false },
}),
).toEqualTypeOf<
RequiredValidatorSpec<{
hello: string
option: boolean
}>
>()
})
})

Expand Down

0 comments on commit 328e056

Please sign in to comment.