Skip to content
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

Incorrect types using SchemaOf with DateSchema #1183

Closed
Kaldy14 opened this issue Dec 15, 2020 · 19 comments
Closed

Incorrect types using SchemaOf with DateSchema #1183

Kaldy14 opened this issue Dec 15, 2020 · 19 comments

Comments

@Kaldy14
Copy link

Kaldy14 commented Dec 15, 2020

Describe the bug
Incorrect types using SchemaOf with DateSchema

To Reproduce
See example https://codesandbox.io/s/kaldy-dgj5g?file=/src/index.ts

Expected behavior
Types will match

Platform (please complete the following information):

  • Version 0.32.8
  • Version of @types/yup 0.29.10
@jquense
Copy link
Owner

jquense commented Dec 15, 2020

ah looks like Date is being inferred to an ObjectSchema incorrectly here

@wit1312
Copy link

wit1312 commented Dec 21, 2020

Similar error with mixed types

import { SchemaOf, object, mixed } from "yup";

export const schema: SchemaOf<{ file: File }> = object({
  file: mixed<File | null>()
});

Type 'OptionalObjectSchema<{ file: MixedSchema<File | null | undefined, Record<string, any>, File | null | undefined>; }, Record<string, any>, TypeOfShape<{ file: MixedSchema<...>; }>>' is not assignable to type 'ObjectSchemaOf<{ file: File; }>'.
  The types of 'fields.file' are incompatible between these types.
    Type 'MixedSchema<File | null | undefined, Record<string, any>, File | null | undefined>' is missing the following properties from type 'ObjectSchema<{ readonly lastModified: BaseSchema<Maybe<number>, Record<string, any>, number>; readonly name: BaseSchema<Maybe<string>, Record<string, any>, string>; ... 5 more ...; text: ObjectSchemaOf<...>; }, Record<...>, TypeOfShape<...>, AssertsShape<...>>': fields, _sortErrors, _nodes, _excludedEdges, and 11 more.ts(2322)

@jquense
Copy link
Owner

jquense commented Dec 22, 2020

So ultimately this may be a limit of the approach. TS doesn't distinguish have a way (AFICT) to distinguish between any ol js object and class instances. We can change the order to inference to whitelist the common types like Date, but the mixed case for something like File, may be impossible :/

@wit1312
Copy link

wit1312 commented Dec 22, 2020

@jquense May be definition of ObjectSchemaOf could be changed to something like this?

type YupSchema = MixedSchema | BooleanSchema | StringSchema | NumberSchema | DateSchema;
declare type ObjectSchemaOf<T extends AnyObject> = ObjectSchema<{
    [k in keyof T]-?: T[k] extends YupSchema ? 
        YupSchema : T[k] extends AnyObject ? 
        ObjectSchemaOf<T[k]> : T[k] extends Array<infer E> ? 
        ArraySchema<SchemaOf<E>> : BaseSchema<Maybe<T[k]>, AnyObject, T[k]>;
}>;

With this definition MixedSchema | BooleanSchema | StringSchema | NumberSchema | DateSchema keys aren't redefined to ObjectSchemaOf and there is no type error.

@jquense
Copy link
Owner

jquense commented Dec 22, 2020

the issue there is T[k] is never a yup schema, you're going from a plain object to a schema. Basically the issue is the there is no way of differentiating in TS between File and { any: 'object'} without knowing something specific about File. i.e you can't implement IsPlainObject<T> in TS as far as I can tell.

@PupoSDC
Copy link

PupoSDC commented Jan 4, 2021

Is this the same error, or should I open a new issue?

code

export interface Participant {
  email: string;
  firstName: string;
  locale: Locale;
  gender?: Gender;
}

const participantSchema: yup.ObjectSchema<Participant> = yup
  .object()
  .shape({
    firstName: yup.string().min(2).required().default(''),
    email: yup.string().email().default('').required(),
    locale: yup
      .string()
      .oneOf(Object.values(Locale))
      .default(Locale.german)
      .required(),
    gender: yup.string().oneOf(Object.values(Gender)).required()
  })
  .required();

Error:

Type 'Participant' does not satisfy the constraint 'Record<string, AnySchema<any, any, any> | Reference<unknown> | Lazy<any, any>>'.
  Index signature is missing in type 'Participant'.

This error showed up when trying to bump from 0.30.0 to 0.32.8 along with several other similar errors.

@jquense
Copy link
Owner

jquense commented Jan 4, 2021

@PupoSDC that is different, you are using the generic incorrectly on ObjectSchema

@JoseLion
Copy link

I'm not sure if this could help, but it seems this only happens with .required(). For instance, the example provided by @wit1312 actually works; it's just not defined correctly. It should be defined as follows:

const schema: SchemaOf<{ file: File | null }> = object({
  file: Yup.mixed<File | null>().defined()
});

The same happens with Date, if it's not required it works just fine:

const schema1: SchemaOf<{ date: Date | null }> = object({
  date: date().defined()
});

const schema2: SchemaOf<{ date?: Date }> = object({
  date: date().notRequired()
});

But more curious is that the following code does NOT complain in TypeScript 🤔

const schema3: SchemaOf<{ date?: Date }> = object({
  date: date().required()
});

(Notice { date?: Date } is defining date as not required, but the required() function is used)

Anyways, I hope this really helps somehow to solve this issue.

Cheers!

@JoseLion
Copy link

It seems that the date issue is fixed by #1305, but it's not in the latest version (i.e., v0.32.9 by the time I'm writing this). @jquense is a release planned soon? It would be great to have this fix out since it's essential for TS users 🙂

@cameronspeakflash
Copy link

Any update on this? The date().required() trick isn't working and I've still got Type 'DateSchema<Date, Record<string, any>, Date>' is not assignable to type 'ObjectSchemaOf<Date> | ObjectSchemaOf<Lazy<Date, never>>'.
With the following code:

interface Base {
  id: number;
  due: Date;
  amount: number;
  reversed: boolean;
}

const BaseSchema: SchemaOf<Base> = object({
  id: number(),
  due: date(),
  amount: number(),
  reversed: boolean(),
});

@cipacda
Copy link
Contributor

cipacda commented May 5, 2021

This also blocks creating a custom schema for @js-joda/core date like LocalTime, as it would expect it to be an object. I think the fix in #1305 would not fix that as LocalTime will still be required to use an object schema, but cannot create a ObjectSchema<LocalTime> as LocalTime does not satisfy ObjectShape constraint.

I get why this is complicated, but limits the intended functionality.

@cipacda
Copy link
Contributor

cipacda commented May 7, 2021

Added a more generic fix in #1358

@plaa
Copy link

plaa commented Jun 12, 2021

Is there a way to force proper types in yup? @cipacda 's link seems to hint at it but doesn't provide any instructions.

The workaround I'm currently using involves @ts-ignore, would be nice to avoid it.

// @ts-ignore - override correct yup type
export const requiredDateSchema: yup.SchemaOf<Date> = yup.date().required();

interface MyDate {
  date: Date;
}
const schema: yup.SchemaOf<MyDate> = yup.object().shape({
  date: requiredDateSchema,
});

@philipp985
Copy link

@plaa do you also have a solution to make this work with optional dates? The solution below, does not work because of the current typings.

// @ts-ignore - override correct yup type
export const notRequiredDateSchema: yup.SchemaOf<Date> = yup.date().notRequired();

interface MyDate {
  date?: Date;
}
const schema: yup.SchemaOf<MyDate> = yup.object().shape({
  date: notRequiredDateSchema,
});

@plaa
Copy link

plaa commented Sep 2, 2021

@philipp985 I tried fiddling with | undefined and | null a bit but didn't get it to match up. 🤷‍♂️

I really wish yup would have some syntax to force typing when by default it goes wrong. I've had to type in an inordinate amount of @ts-ignores in my code due to yup typings being wrong... 🙁

@philipp985
Copy link

Ok, that's what I also tried. Except I am now sticking to @ts-expect-error this might help in the future when / if this will be resolved.

@saschb2b
Copy link

Seems to be fixed with 0.32.11

@JoseLion
Copy link

Working for me on v0.32.9 too. I think this can be closed 🙂

@zelid
Copy link

zelid commented May 31, 2022

An example from above gives typing errors in yup: ^0.32.11

interface Base {
  id: number;
  amount: number;
  reversed: boolean;
}

const BaseSchema: yup.SchemaOf<Base> = yup.object({
  id: yup.number(),
  amount: yup.number(),
  reversed: yup.boolean(),
});

Error:

Type 'OptionalObjectSchema<{ id: NumberSchema<number | undefined, AnyObject, number | undefined>; amount: NumberSchema<number | undefined, AnyObject, number | undefined>; reversed: BooleanSchema<...>; }, AnyObject, TypeOfShape<...>>' is not assignable to type 'ObjectSchemaOf<Base, never>'.
  The types of 'fields.id.__outputType' are incompatible between these types.ts(2322)

While this code has no type errors:

interface Base {
    id?: number;
    amount?: number;
    due?: Date;
    reversed?: boolean;
}

const BaseSchema: yup.SchemaOf<Base> = yup.object({
    id: yup.number(),
    amount: yup.number(),
    due: yup.date(),
    reversed: yup.boolean(),
});

@Kaldy14 Kaldy14 closed this as completed Mar 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests