hapijs / joi

The most powerful data validation library for JS
Other
20.86k stars 1.51k forks source link

Typings for `concat` broken in v17.5.0 #2717

Open donleistman opened 2 years ago

donleistman commented 2 years ago

Context

What are you trying to achieve or the steps to reproduce?

Given the following related schemas:

interface IInterfaceA {
  baz: string;
}

interface IInterfaceB extends IInterfaceA {
  foo: string;
  bar: string;
}

export const dataSchemaA = Joi.object<IInterfaceA>({
  baz: Joi.string(),
});

export const dataSchemaB = Joi.object<IInterfaceB>({
  foo: Joi.string(),
  bar: Joi.string(),
})
  .concat(dataSchemaA) // <= typescript error here
  .required();

What was the result you got?

Argument of type 'ObjectSchema<IInterfaceA>' is not assignable to parameter of type 'ObjectSchema<IInterfaceB>'.
  The types returned by 'validate(...)' are incompatible between these types.
    Type 'ValidationResult<IInterfaceA>' is not assignable to type 'ValidationResult<IInterfaceB>'.
      Type '{ error: undefined; warning?: ValidationError | undefined; value: IInterfaceA; }' is not assignable to type 'ValidationResult<IInterfaceB>'.
        Type '{ error: undefined; warning?: ValidationError | undefined; value: IInterfaceA; }' is not assignable to type '{ error: undefined; warning?: ValidationError | undefined; value: IInterfaceB; }'.
          Types of property 'value' are incompatible.
            Type 'IInterfaceA' is not assignable to type 'IInterfaceB'.ts(2345)

What result did you expect?

No error. It appears that concat is trying to check if InterfaceA applies to InterfaceB, when it should be checking that InterfaceA is a subset of InterfaceB instead.

I think this may be related to the following recent change: https://github.com/sideway/joi/pull/2703

If this is not in fact a bug and my implementation is incorrect, any help would be greatly appreciated :)

alexandercerutti commented 2 years ago

I'm in the same situation. I'm using Joi as a standalone in node. Until 17.4.2 I was doing this:

const PassProps = Joi.object<
    OverridablePassProps & PassKindsProps & PassPropsFromMethods
>()
    .concat(OverridablePassProps)
    .concat(PassKindsProps)
    .concat(PassPropsFromMethods);

Where each schema has an associated type with the same name. Each schema in concat is a Joi.ObjectSchema. With 17.5 I cannot create a "merged schema" with a well-defined type. I can, of course, remove OverridablePassProps & PassKindsProps & PassPropsFromMethods but that would mean to have a Joi.ObjectSchema<any>, and I actually rather fix Joi to 17.4.2 than have something to any.

I was hacking a bit with Typescript definitions. Seems that one of the possible solutions might be to change in Anyschema:

        /**
         * Returns a new type that is the result of adding the rules of one type to another.
         */
        concat(schema: this): this;

to

        /**
         * Returns a new type that is the result of adding the rules of one type to another.
         */
        concat(schema: Schema): this;

So any Schema can be added to concat, but I'm not quite sure if this is wanted or not, cause of the description on the API docs, which is not very clear to me:

Can only be of the same type as the context type or any. If applied to an any type, the schema can be any other schema