mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-13 18:13:29 +01:00
feat!: use zod v4 (#10922)
* feat: zod 4 * feat: zod v3, but v4 feat: validation error class Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com> * chore: bump --------- Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
This commit is contained in:
@@ -1,21 +1,20 @@
|
||||
import { ButtonStyle, ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10';
|
||||
import { z } from 'zod';
|
||||
import { customIdPredicate, refineURLPredicate } from '../Assertions.js';
|
||||
import { z } from 'zod/v4';
|
||||
import { customIdPredicate } from '../Assertions.js';
|
||||
|
||||
const labelPredicate = z.string().min(1).max(80);
|
||||
|
||||
export const emojiPredicate = z
|
||||
.object({
|
||||
.strictObject({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(2).max(32).optional(),
|
||||
animated: z.boolean().optional(),
|
||||
})
|
||||
.strict()
|
||||
.refine((data) => data.id !== undefined || data.name !== undefined, {
|
||||
message: "Either 'id' or 'name' must be provided",
|
||||
error: "Either 'id' or 'name' must be provided",
|
||||
});
|
||||
|
||||
const buttonPredicateBase = z.object({
|
||||
const buttonPredicateBase = z.strictObject({
|
||||
type: z.literal(ComponentType.Button),
|
||||
disabled: z.boolean().optional(),
|
||||
});
|
||||
@@ -26,31 +25,22 @@ const buttonCustomIdPredicateBase = buttonPredicateBase.extend({
|
||||
label: labelPredicate,
|
||||
});
|
||||
|
||||
const buttonPrimaryPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Primary) }).strict();
|
||||
const buttonSecondaryPredicate = buttonCustomIdPredicateBase
|
||||
.extend({ style: z.literal(ButtonStyle.Secondary) })
|
||||
.strict();
|
||||
const buttonSuccessPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Success) }).strict();
|
||||
const buttonDangerPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Danger) }).strict();
|
||||
const buttonPrimaryPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Primary) });
|
||||
const buttonSecondaryPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Secondary) });
|
||||
const buttonSuccessPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Success) });
|
||||
const buttonDangerPredicate = buttonCustomIdPredicateBase.extend({ style: z.literal(ButtonStyle.Danger) });
|
||||
|
||||
const buttonLinkPredicate = buttonPredicateBase
|
||||
.extend({
|
||||
style: z.literal(ButtonStyle.Link),
|
||||
url: z
|
||||
.string()
|
||||
.url()
|
||||
.refine(refineURLPredicate(['http:', 'https:', 'discord:'])),
|
||||
emoji: emojiPredicate.optional(),
|
||||
label: labelPredicate,
|
||||
})
|
||||
.strict();
|
||||
const buttonLinkPredicate = buttonPredicateBase.extend({
|
||||
style: z.literal(ButtonStyle.Link),
|
||||
url: z.url({ protocol: /^(?:https?|discord)$/ }),
|
||||
emoji: emojiPredicate.optional(),
|
||||
label: labelPredicate,
|
||||
});
|
||||
|
||||
const buttonPremiumPredicate = buttonPredicateBase
|
||||
.extend({
|
||||
style: z.literal(ButtonStyle.Premium),
|
||||
sku_id: z.string(),
|
||||
})
|
||||
.strict();
|
||||
const buttonPremiumPredicate = buttonPredicateBase.extend({
|
||||
style: z.literal(ButtonStyle.Premium),
|
||||
sku_id: z.string(),
|
||||
});
|
||||
|
||||
export const buttonPredicate = z.discriminatedUnion('style', [
|
||||
buttonLinkPredicate,
|
||||
@@ -71,7 +61,7 @@ const selectMenuBasePredicate = z.object({
|
||||
|
||||
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
|
||||
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Channel) })
|
||||
.array()
|
||||
@@ -84,7 +74,7 @@ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({
|
||||
default_values: z
|
||||
.object({
|
||||
id: z.string(),
|
||||
type: z.union([z.literal(SelectMenuDefaultValueType.Role), z.literal(SelectMenuDefaultValueType.User)]),
|
||||
type: z.literal([SelectMenuDefaultValueType.Role, SelectMenuDefaultValueType.User]),
|
||||
})
|
||||
.array()
|
||||
.max(25)
|
||||
@@ -113,23 +103,25 @@ export const selectMenuStringPredicate = selectMenuBasePredicate
|
||||
type: z.literal(ComponentType.StringSelect),
|
||||
options: selectMenuStringOptionPredicate.array().min(1).max(25),
|
||||
})
|
||||
.superRefine((menu, ctx) => {
|
||||
.check((ctx) => {
|
||||
const addIssue = (name: string, minimum: number) =>
|
||||
ctx.addIssue({
|
||||
ctx.issues.push({
|
||||
code: 'too_small',
|
||||
message: `The number of options must be greater than or equal to ${name}`,
|
||||
inclusive: true,
|
||||
minimum,
|
||||
type: 'number',
|
||||
path: ['options'],
|
||||
origin: 'number',
|
||||
input: minimum,
|
||||
});
|
||||
|
||||
if (menu.max_values !== undefined && menu.options.length < menu.max_values) {
|
||||
addIssue('max_values', menu.max_values);
|
||||
if (ctx.value.max_values !== undefined && ctx.value.options.length < ctx.value.max_values) {
|
||||
addIssue('max_values', ctx.value.max_values);
|
||||
}
|
||||
|
||||
if (menu.min_values !== undefined && menu.options.length < menu.min_values) {
|
||||
addIssue('min_values', menu.min_values);
|
||||
if (ctx.value.min_values !== undefined && ctx.value.options.length < ctx.value.min_values) {
|
||||
addIssue('min_values', ctx.value.min_values);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -152,14 +144,13 @@ export const actionRowPredicate = z.object({
|
||||
.max(5),
|
||||
z
|
||||
.object({
|
||||
type: z.union([
|
||||
z.literal(ComponentType.ChannelSelect),
|
||||
z.literal(ComponentType.MentionableSelect),
|
||||
z.literal(ComponentType.RoleSelect),
|
||||
z.literal(ComponentType.StringSelect),
|
||||
z.literal(ComponentType.UserSelect),
|
||||
// And this!
|
||||
z.literal(ComponentType.TextInput),
|
||||
type: z.literal([
|
||||
ComponentType.ChannelSelect,
|
||||
ComponentType.MentionableSelect,
|
||||
ComponentType.StringSelect,
|
||||
ComponentType.RoleSelect,
|
||||
ComponentType.TextInput,
|
||||
ComponentType.UserSelect,
|
||||
]),
|
||||
})
|
||||
.array()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { ComponentType, TextInputStyle } from 'discord-api-types/v10';
|
||||
import { z } from 'zod';
|
||||
import { z } from 'zod/v4';
|
||||
import { customIdPredicate } from '../../Assertions.js';
|
||||
|
||||
export const textInputPredicate = z.object({
|
||||
type: z.literal(ComponentType.TextInput),
|
||||
custom_id: customIdPredicate,
|
||||
label: z.string().min(1).max(45),
|
||||
style: z.nativeEnum(TextInputStyle),
|
||||
style: z.enum(TextInputStyle),
|
||||
min_length: z.number().min(0).max(4_000).optional(),
|
||||
max_length: z.number().min(1).max(4_000).optional(),
|
||||
placeholder: z.string().max(100).optional(),
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { ComponentType, SeparatorSpacingSize } from 'discord-api-types/v10';
|
||||
import { z } from 'zod';
|
||||
import { refineURLPredicate } from '../../Assertions.js';
|
||||
import { z } from 'zod/v4';
|
||||
import { actionRowPredicate } from '../Assertions.js';
|
||||
|
||||
const unfurledMediaItemPredicate = z.object({
|
||||
url: z
|
||||
.string()
|
||||
.url()
|
||||
.refine(refineURLPredicate(['http:', 'https:', 'attachment:']), {
|
||||
message: 'Invalid protocol for media URL. Must be http:, https:, or attachment:',
|
||||
}),
|
||||
url: z.url({ protocol: /^(?:https?|attachment)$/ }),
|
||||
});
|
||||
|
||||
export const thumbnailPredicate = z.object({
|
||||
@@ -19,12 +13,7 @@ export const thumbnailPredicate = z.object({
|
||||
});
|
||||
|
||||
const unfurledMediaItemAttachmentOnlyPredicate = z.object({
|
||||
url: z
|
||||
.string()
|
||||
.url()
|
||||
.refine(refineURLPredicate(['attachment:']), {
|
||||
message: 'Invalid protocol for file URL. Must be attachment:',
|
||||
}),
|
||||
url: z.url({ protocol: /^attachment$/ }),
|
||||
});
|
||||
|
||||
export const filePredicate = z.object({
|
||||
@@ -34,7 +23,7 @@ export const filePredicate = z.object({
|
||||
|
||||
export const separatorPredicate = z.object({
|
||||
divider: z.boolean().optional(),
|
||||
spacing: z.nativeEnum(SeparatorSpacingSize).optional(),
|
||||
spacing: z.enum(SeparatorSpacingSize).optional(),
|
||||
});
|
||||
|
||||
export const textDisplayPredicate = z.object({
|
||||
@@ -73,5 +62,5 @@ export const containerPredicate = z.object({
|
||||
)
|
||||
.min(1),
|
||||
spoiler: z.boolean().optional(),
|
||||
accent_color: z.number().int().min(0).max(0xffffff).nullish(),
|
||||
accent_color: z.int().min(0).max(0xffffff).nullish(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user