Skip to content

Problems with .superRefine using discriminated unions #3720

Open
@Hucxley

Description

@Hucxley

I'm new to Zod, but I seem to be having an issue where something is breaking one of the schemas that I'm using for a discriminated union. The code below is causing an error in the final line (the error states that (type at position 1 of source isn't compatible with position 1 of target):

// Anchor event case
const anchorEventTimeRange = z
	.object({
		timeRange: z.literal("Anchor"),
		yearsToInclude: z.array(z.string()),
		anchorEvent: anchorEvent,  // a base object with properties preDurationMonth and postDurationMonth, among others
		trialPopulation: z.object({
			displayName: z.string().min(1, { message: "Participant Population is required." }),
			url: z.string(),
			guid: z.string(),
		}),
		alterControlPostCosts: z.string().min(1, { message: "Cost-Capped Modification is required." }),
		minimumEligibleDays: z.number().min(0, { message: "Must be a positive integer." }),
	})
	.superRefine((values, context) => {
		const monthsPre = values.anchorEvent.preDurationMonth;
		const monthsPost = values.anchorEvent.postDurationMonth;
		const monthsPrePostMin = monthsPre < monthsPost ? monthsPre : monthsPost;
		if (monthsPrePostMin > 0 && values.minimumEligibleDays > 30 * monthsPrePostMin) {
			context.addIssue({
				message: `Must be less than or equal to ${30 * monthsPrePostMin}`,
				code: z.ZodIssueCode.custom,
				path: ["minimumEligibleDays"],
			});
		}
	});

// Year over year event case
const yearOverYearTimeRange = z.object({
	timeRange: z.literal("YOY"),
	anchorEvent: anchorEvent.optional(),
	yearsToInclude: z
		.array(z.string())
		.length(2, { message: "Select 2 years to use for Year Over Year comparison." })
		.refine(
			function (val) {
				const valNumeric = val.map(item => parseInt(item));
				valNumeric.sort((a, b) => a - b);
				return valNumeric[1] - valNumeric[0] === 1;
			},
			{ message: "Selected years must be consecutive." },
		),
	trialPopulation: z.object({
		displayName: z.string().min(1, { message: "Participant Population is required." }),
		url: z.string(),
		guid: z.string(),
	}),
	isPopulationCompatible: z.boolean({ coerce: true }).nullable(),
	alterControlPostCosts: z.string().nullable(),
	minimumEligibleDays: z
		.number({ message: "Must be a positive integer." })
		.min(0, { message: "Must be a positive integer." })
		.max(365, { message: "Must be less than or equal to 365." }),
});

const timeRangeUnion = z
	.discriminatedUnion("timeRange", [yearOverYearTimeRange, anchorEventTimeRange]);
	

If I try to use this, then I get a red line under anchorEventTimeRange in the discriminated union. If I move the .superRefine from the anchorEventTimeRange definition to after the discriminated union definition (append it to the end of the final line), it will allow the union to form, but the validation errors aren't consistently firing the correct max value.

To give more info about what I'm doing, in the year over year case, the max value for minimumEligibleDays is 365. However, if it is an anchor event case, then max values of minimumEligibleDays is (30 * the lesser of anchorEvent.preDurationMonth or anchorEvent.postDurationMonth).

Any ideas about what is going wrong with this that causes the discriminated union objects to not be compatible? I've tried adding a .superRefine to both of objects for each case, but that causes the year over year case to have an error that it is missing a bunch of properties that are truncated, and if I add it to the anchor event case as shown above, then I get the error mentioned above.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions