Skip to content

Commit 075dc04

Browse files
committed
fix(builders): improve component validation predicates
1 parent 0b12263 commit 075dc04

File tree

2 files changed

+37
-47
lines changed

2 files changed

+37
-47
lines changed

packages/builders/src/Assertions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { z } from 'zod';
33

44
export const idPredicate = z.int().min(0).max(2_147_483_647).optional();
55
export const customIdPredicate = z.string().min(1).max(100);
6+
export const snowflakePredicate = z.string().regex(/^\d{17,20}$/);
67

78
export const memberPermissionsPredicate = z.coerce.bigint();
89

packages/builders/src/components/Assertions.ts

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { ButtonStyle, ChannelType, ComponentType, SelectMenuDefaultValueType } from 'discord-api-types/v10';
22
import { z } from 'zod';
3-
import { idPredicate, customIdPredicate } from '../Assertions.js';
3+
import { customIdPredicate, idPredicate, snowflakePredicate } from '../Assertions.js';
44

55
const labelPredicate = z.string().min(1).max(80);
66

77
export const emojiPredicate = z
88
.strictObject({
9-
id: z.string().optional(),
9+
id: snowflakePredicate.optional(),
1010
name: z.string().min(2).max(32).optional(),
1111
animated: z.boolean().optional(),
1212
})
@@ -39,7 +39,7 @@ const buttonLinkPredicate = buttonPredicateBase.extend({
3939

4040
const buttonPremiumPredicate = buttonPredicateBase.extend({
4141
style: z.literal(ButtonStyle.Premium),
42-
sku_id: z.string(),
42+
sku_id: snowflakePredicate,
4343
});
4444

4545
export const buttonPredicate = z.discriminatedUnion('style', [
@@ -51,7 +51,7 @@ export const buttonPredicate = z.discriminatedUnion('style', [
5151
buttonPremiumPredicate,
5252
]);
5353

54-
const selectMenuBasePredicate = z.object({
54+
const selectMenuBasePredicate = z.strictObject({
5555
id: idPredicate,
5656
placeholder: z.string().max(150).optional(),
5757
min_values: z.number().min(0).max(25).optional(),
@@ -62,9 +62,9 @@ const selectMenuBasePredicate = z.object({
6262

6363
export const selectMenuChannelPredicate = selectMenuBasePredicate.extend({
6464
type: z.literal(ComponentType.ChannelSelect),
65-
channel_types: z.enum(ChannelType).array().optional(),
65+
channel_types: z.nativeEnum(ChannelType).array().optional(),
6666
default_values: z
67-
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Channel) })
67+
.strictObject({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.Channel) })
6868
.array()
6969
.max(25)
7070
.optional(),
@@ -73,9 +73,12 @@ export const selectMenuChannelPredicate = selectMenuBasePredicate.extend({
7373
export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({
7474
type: z.literal(ComponentType.MentionableSelect),
7575
default_values: z
76-
.object({
77-
id: z.string(),
78-
type: z.literal([SelectMenuDefaultValueType.Role, SelectMenuDefaultValueType.User]),
76+
.strictObject({
77+
id: snowflakePredicate,
78+
type: z.union([
79+
z.literal(SelectMenuDefaultValueType.Role),
80+
z.literal(SelectMenuDefaultValueType.User),
81+
]),
7982
})
8083
.array()
8184
.max(25)
@@ -85,13 +88,13 @@ export const selectMenuMentionablePredicate = selectMenuBasePredicate.extend({
8588
export const selectMenuRolePredicate = selectMenuBasePredicate.extend({
8689
type: z.literal(ComponentType.RoleSelect),
8790
default_values: z
88-
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.Role) })
91+
.strictObject({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.Role) })
8992
.array()
9093
.max(25)
9194
.optional(),
9295
});
9396

94-
export const selectMenuStringOptionPredicate = z.object({
97+
export const selectMenuStringOptionPredicate = z.strictObject({
9598
label: labelPredicate,
9699
value: z.string().min(1).max(100),
97100
description: z.string().min(1).max(100).optional(),
@@ -104,68 +107,54 @@ export const selectMenuStringPredicate = selectMenuBasePredicate
104107
type: z.literal(ComponentType.StringSelect),
105108
options: selectMenuStringOptionPredicate.array().min(1).max(25),
106109
})
107-
.check((ctx) => {
108-
const addIssue = (name: string, minimum: number) =>
109-
ctx.issues.push({
110-
code: 'too_small',
111-
message: `The number of options must be greater than or equal to ${name}`,
110+
.superRefine((value, ctx) => {
111+
if (value.min_values !== undefined && value.options.length < value.min_values) {
112+
ctx.addIssue({
113+
code: z.ZodIssueCode.too_small,
114+
minimum: value.min_values,
115+
type: 'array',
112116
inclusive: true,
113-
minimum,
114-
type: 'number',
115117
path: ['options'],
116-
origin: 'number',
117-
input: minimum,
118+
message: `The number of options must be greater than or equal to min_values`,
118119
});
119-
120-
if (ctx.value.min_values !== undefined && ctx.value.options.length < ctx.value.min_values) {
121-
addIssue('min_values', ctx.value.min_values);
122120
}
123121

124-
if (
125-
ctx.value.min_values !== undefined &&
126-
ctx.value.max_values !== undefined &&
127-
ctx.value.min_values > ctx.value.max_values
128-
) {
129-
ctx.issues.push({
130-
code: 'too_big',
131-
message: `The maximum amount of options must be greater than or equal to the minimum amount of options`,
132-
inclusive: true,
133-
maximum: ctx.value.max_values,
134-
type: 'number',
122+
if (value.min_values !== undefined && value.max_values !== undefined && value.min_values > value.max_values) {
123+
ctx.addIssue({
124+
code: z.ZodIssueCode.custom,
135125
path: ['min_values'],
136-
origin: 'number',
137-
input: ctx.value.min_values,
126+
message: `The maximum amount of options must be greater than or equal to the minimum amount of options`,
138127
});
139128
}
140129
});
141130

142131
export const selectMenuUserPredicate = selectMenuBasePredicate.extend({
143132
type: z.literal(ComponentType.UserSelect),
144133
default_values: z
145-
.object({ id: z.string(), type: z.literal(SelectMenuDefaultValueType.User) })
134+
.strictObject({ id: snowflakePredicate, type: z.literal(SelectMenuDefaultValueType.User) })
146135
.array()
147136
.max(25)
148137
.optional(),
149138
});
150139

151-
export const actionRowPredicate = z.object({
140+
export const actionRowPredicate = z.strictObject({
152141
id: idPredicate,
153142
type: z.literal(ComponentType.ActionRow),
154143
components: z.union([
155144
z
156-
.object({ type: z.literal(ComponentType.Button) })
145+
.strictObject({ type: z.literal(ComponentType.Button) })
157146
.array()
158147
.min(1)
159148
.max(5),
160149
z
161-
.object({
162-
type: z.literal([
163-
ComponentType.ChannelSelect,
164-
ComponentType.MentionableSelect,
165-
ComponentType.StringSelect,
166-
ComponentType.RoleSelect,
167-
ComponentType.TextInput,
168-
ComponentType.UserSelect,
150+
.strictObject({
151+
type: z.union([
152+
z.literal(ComponentType.ChannelSelect),
153+
z.literal(ComponentType.MentionableSelect),
154+
z.literal(ComponentType.StringSelect),
155+
z.literal(ComponentType.RoleSelect),
156+
z.literal(ComponentType.TextInput),
157+
z.literal(ComponentType.UserSelect),
169158
]),
170159
})
171160
.array()

0 commit comments

Comments
 (0)