colinhacks / zod

TypeScript-first schema validation with static type inference
https://zod.dev
MIT License
33.85k stars 1.18k forks source link

"Intersection results could not be merged" does not mention the cause #3196

Open JacobWeisenburger opened 9 months ago

JacobWeisenburger commented 9 months ago

Discussed in https://github.com/colinhacks/zod/discussions/3194

Originally posted by **JoshuaKGoldberg** January 23, 2024 Zod is great and I'm enjoying the features around intersecting/merging objects. But for `.and`, when the intersections results can't be merged for some reason, the error message thrown at runtime doesn't provide any details. > An error occurred. > > ```plaintext > Intersection results could not be merged > ``` > > Stack Trace > > ```plaintext > ZodError: [ > { > "code": "invalid_intersection_types", > "path": [], > "message": "Intersection results could not be merged" > } > ] > at get error [as error] (file:///Users/josh/repos/dot-com/node_modules/.pnpm/zod@3.22.4/node_modules/zod/lib/index.mjs:538:31) > at ZodIntersection.parse (file:///Users/josh/repos/dot-com/node_modules/.pnpm/zod@3.22.4/node_modules/zod/lib/index.mjs:638:22) > at /Users/josh/repos/dot-com/src/pages/rss.xml.ts:26:48 > at Array.map () Not sure if this is a bug report or feature request, but: could the error message also include which field(s) are problematic? Note that I'm not trying to report that the error is wrong. The error may be right - it just informative enough to explain why clearly. Sorry that I'm not familiar enough with Zod to provide a standalone reproduction (I tried the Stackblitz Astro starter but couldn't get past TypeScript errors, amusingly). But here's how to get it on a real world project: ```plaintext git clone https://github.com/JoshuaKGoldberg/dot-com cd dot-com git checkout 708081f # ncu-u-for-vite-5 branch pnpm i pnpm dev open http://localhost:4321/rss.xml ```
MiaInturi commented 9 months ago

I also faced this problem. And i think it is a good idea add additional field for invalid_intersection_types code with problematic field path.

For example, for the code below, the problematic field will be property.nested1 (because of mergeValues function behavior)

const schema = z
  .strictObject({
    property: z
      .strictObject({
        nested1: z.function(),
        nested2: z.number()
      })
      .optional()
  })
  .and(z.custom((value) => !(value instanceof RegExp)));

const result = schema.safeParse({
  property: {
    nested1: () => {},
    nested2: 1000
  }
});

// Current error
{
    code: 'invalid_intersection_types',
    path: [],
    message: 'Intersection results could not be merged'
}

// Expected error
{
    code: 'invalid_intersection_types',
    path: [],
    problematicFieldPath: ['property', 'nested1']
    message: 'Intersection results could not be merged'
}

But I don't know how to view this change from a semver point of view: breaking or not. Maybe someone can help me decide on this?

manacy-keyvalue commented 8 months ago

I'm also facing the same issue. As of now, we have no clue what exactly caused this error.

Below is the usage of intersection, which caused this error.

import { components as crmComponents } from '@generated/crm-service-types';
import { z } from 'zod';

import { PERSON_TYPES } from '@/constants/filter';
import { mandatoryMessage } from '@/types/common';
import { asOptionalField, requiredWithRefine } from '@/utils/validations';

export type OpportunityStatus = crmComponents['schemas']['OpportunityStatus'];

export const OpportunityStatusList: OpportunityStatus[] = [
  'Interest',
  'Quotation',
  'CustomerDecision',
  'Agreement',
  'ClosedWon',
  'ClosedLost',
];

export enum OpportunityStatusEnum {
  Interest = 'Interest',
  Quotation = 'Quotation',
  CustomerDecision = 'Customer decision',
  Agreement = 'Agreement',
  ClosedWon = 'Closed won',
  ClosedLost = 'Closed lost',
}

export enum OPPORTUNITY_STATUS {
  INTEREST = 'Interest',
  QUOTATION = 'Quotation',
  CUSTOMERDECISION = 'CustomerDecision',
  AGREEMENT = 'Agreement',
  CLOSEDLOST = 'ClosedLost',
  CLOSEDWON = 'ClosedWon',
}

const commonCreateEditOpportunityCheck = {
  name: z.string(),
  status: z.enum(
    [
      OPPORTUNITY_STATUS.INTEREST,
      OPPORTUNITY_STATUS.QUOTATION,
      OPPORTUNITY_STATUS.CUSTOMERDECISION,
      OPPORTUNITY_STATUS.AGREEMENT,
      OPPORTUNITY_STATUS.CLOSEDLOST,
      OPPORTUNITY_STATUS.CLOSEDWON,
    ],
    {
      required_error: mandatoryMessage,
    }
  ),
  leasingCompany: asOptionalField(
    z.object({
      id: z.string(),
      name: z.string(),
    })
  ),
  dealer: asOptionalField(
    z.object({
      dealerId: z.string(),
      dealerName: z.string(),
    })
  ),
  salespersons: z
    .array(
      z.object({
        id: z.number(),
        firstName: z.string(),
        lastName: z.string(),
        email: z.string(),
        loginId: z.string(),
      })
    )
    .optional(),
  additionalComments: asOptionalField(z.string()),
};

const personTypeValidation = z
  .object({
    type: z
      .enum([PERSON_TYPES.BUSINESS, PERSON_TYPES.PRIVATE], {
        required_error: mandatoryMessage,
      })
      .optional(),
    organisation: asOptionalField(
      z.object({
        id: asOptionalField(z.string()),
        name: asOptionalField(z.string()),
      })
    ),
  })
  .refine(
    (data) => !(data.type === PERSON_TYPES.BUSINESS && !data.organisation?.id),
    {
      path: ['organisation'],
      message: 'Organisation is required for Business Customers',
    }
  );

export const EditOpportunityValidation = z.intersection(
  z
    .object({
      ...commonCreateEditOpportunityCheck,
      newCloseDate: z.unknown().optional(),
      customer: z
        .object({
          id: z.string(),
          firstName: z.string().nullable(),
          lastName: z.string(),
        })
        .optional(),
    })
    .passthrough(),
  personTypeValidation
);

export const AddOpportunityValidation = z.intersection(
  z
    .object({
      ...commonCreateEditOpportunityCheck,
      newCloseDate: requiredWithRefine(
        z.any({ required_error: mandatoryMessage })
      ),
      customer: z.object({
        id: z.string(),
        firstName: z.string().nullable(),
        lastName: z.string(),
        type: z.string(),
      }),
    })
    .passthrough(),
  personTypeValidation
);
tompahoward commented 2 weeks ago

I found I was getting this error when I was intersecting a passThrough schema with a schema with coercion.

e.g., the following works

    const mySchema = z.intersection(
        z.object({
            type: z.string(),
        }).passthrough(),
        z.object({
            metaData: z.object({
                timestamp: z.string().datetime(),
            }),
        }),
    );

but the following gets the invalid_intersection_types error

    const mySchema = z.intersection(
        z.object({
            type: z.string(),
        }).passthrough(),
        z.object({
            metaData: z.object({
                timestamp: z.coerce.date(),
            }),
        }),
    );

This also has the gives the same error

    const mySchema = z.intersection(
        z.object({
            type: z.string(),
        }).passthrough(),
        z.object({
            metaData: z.object({
                timestamp: z.string().datetime().transform(value => new Date(value)),
            }),
        }),
    );