ianstormtaylor / superstruct

A simple and composable way to validate data in JavaScript (and TypeScript).
https://docs.superstructjs.org
MIT License
6.97k stars 223 forks source link

How to describe type which contains props with enum #1097

Closed mborysenko closed 1 year ago

mborysenko commented 2 years ago

I have such code, and it doesn't compile.

import * as s from 'superstruct';

export enum RedirectType {
  PERMANENT_REDIRECT = 301,
  TEMPORARY_REDIRECT = 302,
}

export type ExactPath = string;

export interface RedirectDescriptor {
    type: RedirectType;
    to: ExactPath;
}

const redirectDescriptor: s.Describe<RedirectDescriptor> = s.object({
  type: s.enums([RedirectType.PERMANENT_REDIRECT, RedirectType.TEMPORARY_REDIRECT]),
  to: s.string(),
});
// Here I get compilation error

The error is:

error TS2322: Type 'Struct<{ type: RedirectType; to: string; }, { type: Struct<RedirectType, { 301: RedirectType.PERMANENT_REDIRECT; 302: RedirectType.TEMPORARY_REDIRECT; }>; to: Struct<string, null>; }>' is not assignable to type 'Describe<RedirectDescriptor>'.
  Types of property 'schema' are incompatible.
    Type '{ type: s.Struct<RedirectType, { 301: RedirectType.PERMANENT_REDIRECT; 302: RedirectType.TEMPORARY_REDIRECT; }>; to: s.Struct<string, null>; }' is not assignable to type '{ type: Describe<RedirectType>; to: Describe<string>; }'.
mborysenko commented 2 years ago

Is it correct way to define enum while describing type?

arturmuller commented 2 years ago

Hi @mborysenko -- yes, this is the correct way to define enums.

Unfortunately, as you have discovered, the Describe helper doesn't seem to be returning the right kind of struct based on the supplied type. (If you simplify, even const redirectType: Describe<RedirectType> = enums([RedirectType.PERMANENT_REDIRECT, RedirectType.TEMPORARY_REDIRECT]) doesn't work as it should.) My hunch is this is to do with how TypeScript mixes structural and nominal typing and the difficulty in establishing which way to go when recursively Describe -ing an object. I am not the author or maintainer of Superstruct however, so this could be totally wrong.

For what is worth, I have found that going the other way (using Infer from a struct, instead of using Describe from a type) is the better way for Superstruct.

export type RedirectDescriptor = Infer<typeof redirectDescriptor>

const redirectDescriptor = object({
  type: enums([RedirectType.PERMANENT_REDIRECT, RedirectType.TEMPORARY_REDIRECT]),
  to: string(),
});

Hope this help! ✌️