zod icon indicating copy to clipboard operation
zod copied to clipboard

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

Open JacobWeisenburger opened this issue 1 year ago • 3 comments

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.

Intersection results could not be merged

Stack Trace

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/[email protected]/node_modules/zod/lib/index.mjs:538:31)
    at ZodIntersection.parse (file:///Users/josh/repos/dot-com/node_modules/.pnpm/[email protected]/node_modules/zod/lib/index.mjs:638:22)
    at /Users/josh/repos/dot-com/src/pages/rss.xml.ts:26:48
    at Array.map (<anonymous>)

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:

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
```</div>

JacobWeisenburger avatar Jan 27 '24 15:01 JacobWeisenburger

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?

MiaInturi avatar Jan 27 '24 20:01 MiaInturi

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
);

manacy-keyvalue avatar Feb 24 '24 11:02 manacy-keyvalue