From 075dc0421814371bbf06c306011a1537a257dbae Mon Sep 17 00:00:00 2001 From: hakkiai Date: Fri, 21 Nov 2025 21:02:12 +0530 Subject: [PATCH 1/2] fix(builders): improve component validation predicates --- packages/builders/src/Assertions.ts | 1 + .../builders/src/components/Assertions.ts | 83 ++++++++----------- 2 files changed, 37 insertions(+), 47 deletions(-) diff --git a/packages/builders/src/Assertions.ts b/packages/builders/src/Assertions.ts index c672c95c08dc..54d87c0718e1 100644 --- a/packages/builders/src/Assertions.ts +++ b/packages/builders/src/Assertions.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; export const idPredicate = z.int().min(0).max(2_147_483_647).optional(); export const customIdPredicate = z.string().min(1).max(100); +export const snowflakePredicate = z.string().regex(/^\d{17,20}$/); export const memberPermissionsPredicate = z.coerce.bigint(); diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts index 67b9b44fc0c4..b6480432992f 100644 --- a/packages/builders/src/components/Assertions.ts +++ b/packages/builders/src/components/Assertions.ts @@ -1,12 +1,12 @@ import { ButtonStyle, ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10'; import { z } from 'zod'; -import { idPredicate, customIdPredicate } from '../Assertions.js'; +import { customIdPredicate, idPredicate, snowflakePredicate } from '../Assertions.js'; const labelPredicate = z.string().min(1).max(80); export const emojiPredicate = z .strictObject({ - id: z.string().optional(), + id: snowflakePredicate.optional(), name: z.string().min(2).max(32).optional(), animated: z.boolean().optional(), }) @@ -39,7 +39,7 @@ const buttonLinkPredicate = buttonPredicateBase.extend({ const buttonPremiumPredicate = buttonPredicateBase.extend({ style: z.literal(ButtonStyle.Premium), - sku_id: z.string(), + sku_id: snowflakePredicate, }); export const buttonPredicate = z.discriminatedUnion('style', [ @@ -51,7 +51,7 @@ export const buttonPredicate = z.discriminatedUnion('style', [ buttonPremiumPredicate, ]); -const selectMenuBasePredicate = z.object({ +const selectMenuBasePredicate = z.strictObject({ id: idPredicate, placeholder: z.string().max(150).optional(), min_values: z.number().min(0).max(25).optional(), @@ -62,9 +62,9 @@ const selectMenuBasePredicate = z.object({ export const selectMenuChannelPredicate = selectMenuBasePredicate.extend({ type: z.literal(ComponentType.ChannelSelect), - channel_types: z.enum(ChannelType).array().optional(), + channel_types: z.nativeEnum(ChannelType).array().optional(), default_values: z - .object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Channel) }) + .strictObject({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.Channel) }) .array() .max(25) .optional(), @@ -73,9 +73,12 @@ export const selectMenuChannelPredicate = selectMenuBasePredicate.extend({ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({ type: z.literal(ComponentType.MentionableSelect), default_values: z - .object({ - id: z.string(), - type: z.literal([SelectMenuDefaultValueType.Role, SelectMenuDefaultValueType.User]), + .strictObject({ + id: snowflakePredicate, + type: z.union([ + z.literal(SelectMenuDefaultValueType.Role), + z.literal(SelectMenuDefaultValueType.User), + ]), }) .array() .max(25) @@ -85,13 +88,13 @@ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({ export const selectMenuRolePredicate = selectMenuBasePredicate.extend({ type: z.literal(ComponentType.RoleSelect), default_values: z - .object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Role) }) + .strictObject({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.Role) }) .array() .max(25) .optional(), }); -export const selectMenuStringOptionPredicate = z.object({ +export const selectMenuStringOptionPredicate = z.strictObject({ label: labelPredicate, value: z.string().min(1).max(100), description: z.string().min(1).max(100).optional(), @@ -104,37 +107,23 @@ export const selectMenuStringPredicate = selectMenuBasePredicate type: z.literal(ComponentType.StringSelect), options: selectMenuStringOptionPredicate.array().min(1).max(25), }) - .check((ctx) => { - const addIssue = (name: string, minimum: number) => - ctx.issues.push({ - code: 'too_small', - message: `The number of options must be greater than or equal to ${name}`, + .superRefine((value, ctx) => { + if (value.min_values !== undefined && value.options.length < value.min_values) { + ctx.addIssue({ + code: z.ZodIssueCode.too_small, + minimum: value.min_values, + type: 'array', inclusive: true, - minimum, - type: 'number', path: ['options'], - origin: 'number', - input: minimum, + message: `The number of options must be greater than or equal to min_values`, }); - - if (ctx.value.min_values !== undefined && ctx.value.options.length < ctx.value.min_values) { - addIssue('min_values', ctx.value.min_values); } - if ( - ctx.value.min_values !== undefined && - ctx.value.max_values !== undefined && - ctx.value.min_values > ctx.value.max_values - ) { - ctx.issues.push({ - code: 'too_big', - message: `The maximum amount of options must be greater than or equal to the minimum amount of options`, - inclusive: true, - maximum: ctx.value.max_values, - type: 'number', + if (value.min_values !== undefined && value.max_values !== undefined && value.min_values > value.max_values) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, path: ['min_values'], - origin: 'number', - input: ctx.value.min_values, + message: `The maximum amount of options must be greater than or equal to the minimum amount of options`, }); } }); @@ -142,30 +131,30 @@ export const selectMenuStringPredicate = selectMenuBasePredicate export const selectMenuUserPredicate = selectMenuBasePredicate.extend({ type: z.literal(ComponentType.UserSelect), default_values: z - .object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.User) }) + .strictObject({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.User) }) .array() .max(25) .optional(), }); -export const actionRowPredicate = z.object({ +export const actionRowPredicate = z.strictObject({ id: idPredicate, type: z.literal(ComponentType.ActionRow), components: z.union([ z - .object({ type: z.literal(ComponentType.Button) }) + .strictObject({ type: z.literal(ComponentType.Button) }) .array() .min(1) .max(5), z - .object({ - type: z.literal([ - ComponentType.ChannelSelect, - ComponentType.MentionableSelect, - ComponentType.StringSelect, - ComponentType.RoleSelect, - ComponentType.TextInput, - ComponentType.UserSelect, + .strictObject({ + type: z.union([ + z.literal(ComponentType.ChannelSelect), + z.literal(ComponentType.MentionableSelect), + z.literal(ComponentType.StringSelect), + z.literal(ComponentType.RoleSelect), + z.literal(ComponentType.TextInput), + z.literal(ComponentType.UserSelect), ]), }) .array() From 5fff61b4fed062505b61d871c385530d388f42b3 Mon Sep 17 00:00:00 2001 From: hakkiai Date: Fri, 21 Nov 2025 22:45:08 +0530 Subject: [PATCH 2/2] fix(builders): apply validation feedback --- .../builders/src/components/Assertions.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/packages/builders/src/components/Assertions.ts b/packages/builders/src/components/Assertions.ts index b6480432992f..57a164fde659 100644 --- a/packages/builders/src/components/Assertions.ts +++ b/packages/builders/src/components/Assertions.ts @@ -62,7 +62,7 @@ const selectMenuBasePredicate = z.strictObject({ export const selectMenuChannelPredicate = selectMenuBasePredicate.extend({ type: z.literal(ComponentType.ChannelSelect), - channel_types: z.nativeEnum(ChannelType).array().optional(), + channel_types: z.enum(ChannelType).array().optional(), default_values: z .strictObject({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.Channel) }) .array() @@ -75,10 +75,7 @@ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({ default_values: z .strictObject({ id: snowflakePredicate, - type: z.union([ - z.literal(SelectMenuDefaultValueType.Role), - z.literal(SelectMenuDefaultValueType.User), - ]), + type: z.literal([SelectMenuDefaultValueType.Role, SelectMenuDefaultValueType.User]), }) .array() .max(25) @@ -148,13 +145,13 @@ export const actionRowPredicate = z.strictObject({ .max(5), z .strictObject({ - type: z.union([ - z.literal(ComponentType.ChannelSelect), - z.literal(ComponentType.MentionableSelect), - z.literal(ComponentType.StringSelect), - z.literal(ComponentType.RoleSelect), - z.literal(ComponentType.TextInput), - z.literal(ComponentType.UserSelect), + type: z.literal([ + ComponentType.ChannelSelect, + ComponentType.MentionableSelect, + ComponentType.StringSelect, + ComponentType.RoleSelect, + ComponentType.TextInput, + ComponentType.UserSelect, ]), }) .array()