feat: implement zod-validation-error (#10534)

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
Denis Cristea
2024-10-06 17:43:06 +03:00
committed by GitHub
parent 24128a3c45
commit 8ab4124ef9
22 changed files with 88 additions and 97 deletions

View File

@@ -69,7 +69,8 @@
"discord-api-types": "^0.37.101", "discord-api-types": "^0.37.101",
"ts-mixer": "^6.0.4", "ts-mixer": "^6.0.4",
"tslib": "^2.6.3", "tslib": "^2.6.3",
"zod": "^3.23.8" "zod": "^3.23.8",
"zod-validation-error": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@discordjs/api-extractor": "workspace:^", "@discordjs/api-extractor": "workspace:^",

View File

@@ -16,7 +16,7 @@ import type {
import { ComponentType } from 'discord-api-types/v10'; import { ComponentType } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js'; import { normalizeArray, type RestOrArray } from '../util/normalizeArray.js';
import { resolveBuilder } from '../util/resolveBuilder.js'; import { resolveBuilder } from '../util/resolveBuilder.js';
import { isValidationEnabled } from '../util/validation.js'; import { validate } from '../util/validation.js';
import { actionRowPredicate } from './Assertions.js'; import { actionRowPredicate } from './Assertions.js';
import { ComponentBuilder } from './Component.js'; import { ComponentBuilder } from './Component.js';
import type { AnyActionRowComponentBuilder } from './Components.js'; import type { AnyActionRowComponentBuilder } from './Components.js';
@@ -336,9 +336,7 @@ export class ActionRowBuilder extends ComponentBuilder<APIActionRowComponent<API
components: components.map((component) => component.toJSON(validationOverride)), components: components.map((component) => component.toJSON(validationOverride)),
}; };
if (validationOverride ?? isValidationEnabled()) { validate(actionRowPredicate, data, validationOverride);
actionRowPredicate.parse(data);
}
return data as APIActionRowComponent<APIActionRowComponentTypes>; return data as APIActionRowComponent<APIActionRowComponentTypes>;
} }

View File

@@ -1,5 +1,5 @@
import type { APIButtonComponent } from 'discord-api-types/v10'; import type { APIButtonComponent } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { buttonPredicate } from '../Assertions.js'; import { buttonPredicate } from '../Assertions.js';
import { ComponentBuilder } from '../Component.js'; import { ComponentBuilder } from '../Component.js';
@@ -24,10 +24,7 @@ export abstract class BaseButtonBuilder<ButtonData extends APIButtonComponent> e
*/ */
public override toJSON(validationOverride?: boolean): ButtonData { public override toJSON(validationOverride?: boolean): ButtonData {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(buttonPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
buttonPredicate.parse(clone);
}
return clone as ButtonData; return clone as ButtonData;
} }

View File

@@ -6,7 +6,7 @@ import {
SelectMenuDefaultValueType, SelectMenuDefaultValueType,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js'; import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { selectMenuChannelPredicate } from '../Assertions.js'; import { selectMenuChannelPredicate } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
@@ -108,10 +108,7 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder<APIChannelSe
*/ */
public override toJSON(validationOverride?: boolean): APIChannelSelectComponent { public override toJSON(validationOverride?: boolean): APIChannelSelectComponent {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(selectMenuChannelPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
selectMenuChannelPredicate.parse(clone);
}
return clone as APIChannelSelectComponent; return clone as APIChannelSelectComponent;
} }

View File

@@ -6,7 +6,7 @@ import {
SelectMenuDefaultValueType, SelectMenuDefaultValueType,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js'; import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { selectMenuMentionablePredicate } from '../Assertions.js'; import { selectMenuMentionablePredicate } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
@@ -119,10 +119,7 @@ export class MentionableSelectMenuBuilder extends BaseSelectMenuBuilder<APIMenti
*/ */
public override toJSON(validationOverride?: boolean): APIMentionableSelectComponent { public override toJSON(validationOverride?: boolean): APIMentionableSelectComponent {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(selectMenuMentionablePredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
selectMenuMentionablePredicate.parse(clone);
}
return clone as APIMentionableSelectComponent; return clone as APIMentionableSelectComponent;
} }

View File

@@ -5,7 +5,7 @@ import {
SelectMenuDefaultValueType, SelectMenuDefaultValueType,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js'; import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { selectMenuRolePredicate } from '../Assertions.js'; import { selectMenuRolePredicate } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
@@ -82,10 +82,7 @@ export class RoleSelectMenuBuilder extends BaseSelectMenuBuilder<APIRoleSelectCo
*/ */
public override toJSON(validationOverride?: boolean): APIRoleSelectComponent { public override toJSON(validationOverride?: boolean): APIRoleSelectComponent {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(selectMenuRolePredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
selectMenuRolePredicate.parse(clone);
}
return clone as APIRoleSelectComponent; return clone as APIRoleSelectComponent;
} }

View File

@@ -4,7 +4,7 @@ import { ComponentType } from 'discord-api-types/v10';
import type { APIStringSelectComponent, APISelectMenuOption } from 'discord-api-types/v10'; import type { APIStringSelectComponent, APISelectMenuOption } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
import { resolveBuilder } from '../../util/resolveBuilder.js'; import { resolveBuilder } from '../../util/resolveBuilder.js';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { selectMenuStringPredicate } from '../Assertions.js'; import { selectMenuStringPredicate } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
import { StringSelectMenuOptionBuilder } from './StringSelectMenuOption.js'; import { StringSelectMenuOptionBuilder } from './StringSelectMenuOption.js';
@@ -156,9 +156,7 @@ export class StringSelectMenuBuilder extends BaseSelectMenuBuilder<APIStringSele
options: options.map((option) => option.toJSON(false)), options: options.map((option) => option.toJSON(false)),
}; };
if (validationOverride ?? isValidationEnabled()) { validate(selectMenuStringPredicate, data, validationOverride);
selectMenuStringPredicate.parse(data);
}
return data as APIStringSelectComponent; return data as APIStringSelectComponent;
} }

View File

@@ -1,6 +1,6 @@
import type { JSONEncodable } from '@discordjs/util'; import type { JSONEncodable } from '@discordjs/util';
import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10'; import type { APIMessageComponentEmoji, APISelectMenuOption } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { selectMenuStringOptionPredicate } from '../Assertions.js'; import { selectMenuStringOptionPredicate } from '../Assertions.js';
/** /**
@@ -106,10 +106,7 @@ export class StringSelectMenuOptionBuilder implements JSONEncodable<APISelectMen
*/ */
public toJSON(validationOverride?: boolean): APISelectMenuOption { public toJSON(validationOverride?: boolean): APISelectMenuOption {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(selectMenuStringOptionPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
selectMenuStringOptionPredicate.parse(clone);
}
return clone as APISelectMenuOption; return clone as APISelectMenuOption;
} }

View File

@@ -5,7 +5,7 @@ import {
SelectMenuDefaultValueType, SelectMenuDefaultValueType,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js'; import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { selectMenuUserPredicate } from '../Assertions.js'; import { selectMenuUserPredicate } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js'; import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';
@@ -82,10 +82,7 @@ export class UserSelectMenuBuilder extends BaseSelectMenuBuilder<APIUserSelectCo
*/ */
public override toJSON(validationOverride?: boolean): APIUserSelectComponent { public override toJSON(validationOverride?: boolean): APIUserSelectComponent {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(selectMenuUserPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
selectMenuUserPredicate.parse(clone);
}
return clone as APIUserSelectComponent; return clone as APIUserSelectComponent;
} }

View File

@@ -1,5 +1,5 @@
import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10'; import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { ComponentBuilder } from '../Component.js'; import { ComponentBuilder } from '../Component.js';
import { textInputPredicate } from './Assertions.js'; import { textInputPredicate } from './Assertions.js';
@@ -154,10 +154,7 @@ export class TextInputBuilder extends ComponentBuilder<APITextInputComponent> {
*/ */
public toJSON(validationOverride?: boolean): APITextInputComponent { public toJSON(validationOverride?: boolean): APITextInputComponent {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(textInputPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
textInputPredicate.parse(clone);
}
return clone as APITextInputComponent; return clone as APITextInputComponent;
} }

View File

@@ -1,6 +1,6 @@
import { ApplicationCommandType, type RESTPostAPIChatInputApplicationCommandsJSONBody } from 'discord-api-types/v10'; import { ApplicationCommandType, type RESTPostAPIChatInputApplicationCommandsJSONBody } from 'discord-api-types/v10';
import { Mixin } from 'ts-mixer'; import { Mixin } from 'ts-mixer';
import { isValidationEnabled } from '../../../util/validation.js'; import { validate } from '../../../util/validation.js';
import { CommandBuilder } from '../Command.js'; import { CommandBuilder } from '../Command.js';
import { SharedNameAndDescription } from '../SharedNameAndDescription.js'; import { SharedNameAndDescription } from '../SharedNameAndDescription.js';
import { chatInputCommandPredicate } from './Assertions.js'; import { chatInputCommandPredicate } from './Assertions.js';
@@ -28,9 +28,7 @@ export class ChatInputCommandBuilder extends Mixin(
options: options?.map((option) => option.toJSON(validationOverride)), options: options?.map((option) => option.toJSON(validationOverride)),
}; };
if (validationOverride ?? isValidationEnabled()) { validate(chatInputCommandPredicate, data, validationOverride);
chatInputCommandPredicate.parse(data);
}
return data; return data;
} }

View File

@@ -7,7 +7,7 @@ import { ApplicationCommandOptionType } from 'discord-api-types/v10';
import { Mixin } from 'ts-mixer'; import { Mixin } from 'ts-mixer';
import { normalizeArray, type RestOrArray } from '../../../util/normalizeArray.js'; import { normalizeArray, type RestOrArray } from '../../../util/normalizeArray.js';
import { resolveBuilder } from '../../../util/resolveBuilder.js'; import { resolveBuilder } from '../../../util/resolveBuilder.js';
import { isValidationEnabled } from '../../../util/validation.js'; import { validate } from '../../../util/validation.js';
import type { SharedNameAndDescriptionData } from '../SharedNameAndDescription.js'; import type { SharedNameAndDescriptionData } from '../SharedNameAndDescription.js';
import { SharedNameAndDescription } from '../SharedNameAndDescription.js'; import { SharedNameAndDescription } from '../SharedNameAndDescription.js';
import { chatInputCommandSubcommandGroupPredicate, chatInputCommandSubcommandPredicate } from './Assertions.js'; import { chatInputCommandSubcommandGroupPredicate, chatInputCommandSubcommandPredicate } from './Assertions.js';
@@ -69,9 +69,7 @@ export class ChatInputCommandSubcommandGroupBuilder
options: options?.map((option) => option.toJSON(validationOverride)) ?? [], options: options?.map((option) => option.toJSON(validationOverride)) ?? [],
}; };
if (validationOverride ?? isValidationEnabled()) { validate(chatInputCommandSubcommandGroupPredicate, data, validationOverride);
chatInputCommandSubcommandGroupPredicate.parse(data);
}
return data; return data;
} }
@@ -102,9 +100,7 @@ export class ChatInputCommandSubcommandBuilder
options: options?.map((option) => option.toJSON(validationOverride)) ?? [], options: options?.map((option) => option.toJSON(validationOverride)) ?? [],
}; };
if (validationOverride ?? isValidationEnabled()) { validate(chatInputCommandSubcommandPredicate, data, validationOverride);
chatInputCommandSubcommandPredicate.parse(data);
}
return data; return data;
} }

View File

@@ -5,7 +5,7 @@ import type {
ApplicationCommandOptionType, ApplicationCommandOptionType,
} from 'discord-api-types/v10'; } from 'discord-api-types/v10';
import type { z } from 'zod'; import type { z } from 'zod';
import { isValidationEnabled } from '../../../../util/validation.js'; import { validate } from '../../../../util/validation.js';
import type { SharedNameAndDescriptionData } from '../../SharedNameAndDescription.js'; import type { SharedNameAndDescriptionData } from '../../SharedNameAndDescription.js';
import { SharedNameAndDescription } from '../../SharedNameAndDescription.js'; import { SharedNameAndDescription } from '../../SharedNameAndDescription.js';
import { basicOptionPredicate } from '../Assertions.js'; import { basicOptionPredicate } from '../Assertions.js';
@@ -49,10 +49,7 @@ export abstract class ApplicationCommandOptionBase
*/ */
public toJSON(validationOverride?: boolean): APIApplicationCommandBasicOption { public toJSON(validationOverride?: boolean): APIApplicationCommandBasicOption {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate((this.constructor as typeof ApplicationCommandOptionBase).predicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
(this.constructor as typeof ApplicationCommandOptionBase).predicate.parse(clone);
}
return clone as APIApplicationCommandBasicOption; return clone as APIApplicationCommandBasicOption;
} }

View File

@@ -1,5 +1,5 @@
import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10'; import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../../util/validation.js'; import { validate } from '../../../util/validation.js';
import { messageCommandPredicate } from './Assertions.js'; import { messageCommandPredicate } from './Assertions.js';
import { ContextMenuCommandBuilder } from './ContextMenuCommand.js'; import { ContextMenuCommandBuilder } from './ContextMenuCommand.js';
@@ -9,10 +9,7 @@ export class MessageContextCommandBuilder extends ContextMenuCommandBuilder {
*/ */
public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody { public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody {
const data = { ...structuredClone(this.data), type: ApplicationCommandType.Message }; const data = { ...structuredClone(this.data), type: ApplicationCommandType.Message };
validate(messageCommandPredicate, data, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
messageCommandPredicate.parse(data);
}
return data as RESTPostAPIContextMenuApplicationCommandsJSONBody; return data as RESTPostAPIContextMenuApplicationCommandsJSONBody;
} }

View File

@@ -1,5 +1,5 @@
import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10'; import { ApplicationCommandType, type RESTPostAPIContextMenuApplicationCommandsJSONBody } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../../util/validation.js'; import { validate } from '../../../util/validation.js';
import { userCommandPredicate } from './Assertions.js'; import { userCommandPredicate } from './Assertions.js';
import { ContextMenuCommandBuilder } from './ContextMenuCommand.js'; import { ContextMenuCommandBuilder } from './ContextMenuCommand.js';
@@ -9,10 +9,7 @@ export class UserContextCommandBuilder extends ContextMenuCommandBuilder {
*/ */
public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody { public override toJSON(validationOverride?: boolean): RESTPostAPIContextMenuApplicationCommandsJSONBody {
const data = { ...structuredClone(this.data), type: ApplicationCommandType.User }; const data = { ...structuredClone(this.data), type: ApplicationCommandType.User };
validate(userCommandPredicate, data, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
userCommandPredicate.parse(data);
}
return data as RESTPostAPIContextMenuApplicationCommandsJSONBody; return data as RESTPostAPIContextMenuApplicationCommandsJSONBody;
} }

View File

@@ -10,7 +10,7 @@ import { ActionRowBuilder } from '../../components/ActionRow.js';
import { createComponentBuilder } from '../../components/Components.js'; import { createComponentBuilder } from '../../components/Components.js';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js'; import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
import { resolveBuilder } from '../../util/resolveBuilder.js'; import { resolveBuilder } from '../../util/resolveBuilder.js';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { modalPredicate } from './Assertions.js'; import { modalPredicate } from './Assertions.js';
export interface ModalBuilderData extends Partial<Omit<APIModalInteractionResponseCallbackData, 'components'>> { export interface ModalBuilderData extends Partial<Omit<APIModalInteractionResponseCallbackData, 'components'>> {
@@ -162,9 +162,7 @@ export class ModalBuilder implements JSONEncodable<APIModalInteractionResponseCa
components: components.map((component) => component.toJSON(validationOverride)), components: components.map((component) => component.toJSON(validationOverride)),
}; };
if (validationOverride ?? isValidationEnabled()) { validate(modalPredicate, data, validationOverride);
modalPredicate.parse(data);
}
return data as APIModalInteractionResponseCallbackData; return data as APIModalInteractionResponseCallbackData;
} }

View File

@@ -3,7 +3,7 @@ import type { APIEmbed, APIEmbedAuthor, APIEmbedField, APIEmbedFooter } from 'di
import type { RestOrArray } from '../../util/normalizeArray.js'; import type { RestOrArray } from '../../util/normalizeArray.js';
import { normalizeArray } from '../../util/normalizeArray.js'; import { normalizeArray } from '../../util/normalizeArray.js';
import { resolveBuilder } from '../../util/resolveBuilder.js'; import { resolveBuilder } from '../../util/resolveBuilder.js';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { embedPredicate } from './Assertions.js'; import { embedPredicate } from './Assertions.js';
import { EmbedAuthorBuilder } from './EmbedAuthor.js'; import { EmbedAuthorBuilder } from './EmbedAuthor.js';
import { EmbedFieldBuilder } from './EmbedField.js'; import { EmbedFieldBuilder } from './EmbedField.js';
@@ -343,9 +343,7 @@ export class EmbedBuilder implements JSONEncodable<APIEmbed> {
footer: this.data.footer?.toJSON(false), footer: this.data.footer?.toJSON(false),
}; };
if (validationOverride ?? isValidationEnabled()) { validate(embedPredicate, data, validationOverride);
embedPredicate.parse(data);
}
return data; return data;
} }

View File

@@ -1,5 +1,5 @@
import type { APIEmbedAuthor } from 'discord-api-types/v10'; import type { APIEmbedAuthor } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { embedAuthorPredicate } from './Assertions.js'; import { embedAuthorPredicate } from './Assertions.js';
/** /**
@@ -72,10 +72,7 @@ export class EmbedAuthorBuilder {
*/ */
public toJSON(validationOverride?: boolean): APIEmbedAuthor { public toJSON(validationOverride?: boolean): APIEmbedAuthor {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(embedAuthorPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
embedAuthorPredicate.parse(clone);
}
return clone as APIEmbedAuthor; return clone as APIEmbedAuthor;
} }

View File

@@ -1,5 +1,5 @@
import type { APIEmbedField } from 'discord-api-types/v10'; import type { APIEmbedField } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { embedFieldPredicate } from './Assertions.js'; import { embedFieldPredicate } from './Assertions.js';
/** /**
@@ -56,10 +56,7 @@ export class EmbedFieldBuilder {
*/ */
public toJSON(validationOverride?: boolean): APIEmbedField { public toJSON(validationOverride?: boolean): APIEmbedField {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(embedFieldPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
embedFieldPredicate.parse(clone);
}
return clone as APIEmbedField; return clone as APIEmbedField;
} }

View File

@@ -1,5 +1,5 @@
import type { APIEmbedFooter } from 'discord-api-types/v10'; import type { APIEmbedFooter } from 'discord-api-types/v10';
import { isValidationEnabled } from '../../util/validation.js'; import { validate } from '../../util/validation.js';
import { embedFooterPredicate } from './Assertions.js'; import { embedFooterPredicate } from './Assertions.js';
/** /**
@@ -54,10 +54,7 @@ export class EmbedFooterBuilder {
*/ */
public toJSON(validationOverride?: boolean): APIEmbedFooter { public toJSON(validationOverride?: boolean): APIEmbedFooter {
const clone = structuredClone(this.data); const clone = structuredClone(this.data);
validate(embedFooterPredicate, clone, validationOverride);
if (validationOverride ?? isValidationEnabled()) {
embedFooterPredicate.parse(clone);
}
return clone as APIEmbedFooter; return clone as APIEmbedFooter;
} }

View File

@@ -1,4 +1,7 @@
let validate = true; import type { z } from 'zod';
import { fromZodError } from 'zod-validation-error';
let validationEnabled = true;
/** /**
* Enables validators. * Enables validators.
@@ -6,7 +9,7 @@ let validate = true;
* @returns Whether validation is occurring. * @returns Whether validation is occurring.
*/ */
export function enableValidators() { export function enableValidators() {
return (validate = true); return (validationEnabled = true);
} }
/** /**
@@ -15,12 +18,39 @@ export function enableValidators() {
* @returns Whether validation is occurring. * @returns Whether validation is occurring.
*/ */
export function disableValidators() { export function disableValidators() {
return (validate = false); return (validationEnabled = false);
} }
/** /**
* Checks whether validation is occurring. * Checks whether validation is occurring.
*/ */
export function isValidationEnabled() { export function isValidationEnabled() {
return validate; return validationEnabled;
}
/**
* Parses a value with a given validator, accounting for wether validation is enabled.
*
* @param validator - The zod validator to use
* @param value - The value to parse
* @param validationOverride - Force validation to run/not run regardless of your global preference
* @returns The result from parsing
* @internal
*/
export function validate<Validator extends z.ZodTypeAny>(
validator: Validator,
value: unknown,
validationOverride?: boolean,
): z.output<Validator> {
if (validationOverride === false || !isValidationEnabled()) {
return value;
}
const result = validator.safeParse(value);
if (!result.success) {
throw fromZodError(result.error);
}
return result.data;
} }

13
pnpm-lock.yaml generated
View File

@@ -685,6 +685,9 @@ importers:
zod: zod:
specifier: ^3.23.8 specifier: ^3.23.8
version: 3.23.8 version: 3.23.8
zod-validation-error:
specifier: ^3.4.0
version: 3.4.0(zod@3.23.8)
devDependencies: devDependencies:
'@discordjs/api-extractor': '@discordjs/api-extractor':
specifier: workspace:^ specifier: workspace:^
@@ -13661,6 +13664,12 @@ packages:
peerDependencies: peerDependencies:
zod: ^3.18.0 zod: ^3.18.0
zod-validation-error@3.4.0:
resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
zod: ^3.18.0
zod@3.23.8: zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
@@ -29775,6 +29784,10 @@ snapshots:
dependencies: dependencies:
zod: 3.23.8 zod: 3.23.8
zod-validation-error@3.4.0(zod@3.23.8):
dependencies:
zod: 3.23.8
zod@3.23.8: {} zod@3.23.8: {}
zwitch@2.0.4: {} zwitch@2.0.4: {}