Spraxium logoSpraxium

Validação de Modal

Valide submissões de modal com regras @ModalValidate e personalize respostas de erro com @ModalValidationConfig. Aprenda validadores nativos, regras personalizadas e todo o ciclo de vida da validação, da submissão à chamada do handler.

Por que usar validação em modais

A validação nativa de modais do Discord se limita às restrições minLength, maxLength e required em inputs de texto. Em modais do mundo real, é comum precisar de validações mais ricas: contagem mínima de palavras para relatórios, verificação de formato de e-mail em formulários de contato, validação de URL para links, correspondência de padrão para códigos ou regras de negócio totalmente personalizadas. A validação de modal do Spraxium roda no servidor depois da submissão, dando controle total sobre o que conta como entrada válida.

O sistema de validação usa uma abordagem declarativa com regras @ModalValidate aplicadas a campos individuais. As regras são executadas em sequência e interrompem na primeira falha. Quando a validação falha, o sistema pode colocar em cache os valores enviados pelo usuário para que sejam preenchidos novamente quando o modal for reaberto, evitando que a pessoa precise digitar tudo de novo. Um embed de erro personalizado é enviado como resposta efêmera, explicando o que deu errado.

Essa combinação de validação, cache e apresentação de erro cria uma experiência fluida em que submissões inválidas são barradas imediatamente e os usuários conseguem corrigir a entrada sem perder o progresso.

Aplicando regras de validação

O decorator @ModalValidate aceita um array de regras de validação e fica em uma propriedade de campo ao lado do decorator de tipo do campo. Cada regra é uma chamada de função que retorna um objeto de regra. As regras são executadas na ordem do array e param na primeira falha.

src/components/contact.modal.ts
import {
  ModalComponent,
  ModalInput,
  ModalValidate,
  minWords,
  emailFormat,
  urlFormat,
} from '@spraxium/components';

@ModalComponent({ id: 'contact-modal', title: 'Contact Us' })
export class ContactModal {
  @ModalInput({
    label: 'Email',
    style: 'short',
    required: true,
    placeholder: '[email protected]',
  })
  @ModalValidate([emailFormat()])
  email!: string;

  @ModalInput({
    label: 'Website',
    style: 'short',
    required: false,
    placeholder: 'https://your-website.com',
  })
  @ModalValidate([urlFormat()])
  website!: string;

  @ModalInput({
    label: 'Message',
    style: 'paragraph',
    required: true,
    minLength: 20,
  })
  @ModalValidate([minWords(5)])
  message!: string;
}

Regras de validação nativas

O Spraxium traz cinco regras de validação nativas que cobrem cenários comuns. Cada regra é uma função factory que retorna um objeto ModalValidationRule. O método validate da regra recebe o valor do campo como string e retorna true se ele for válido, ou uma string com a mensagem de erro se for inválido.

RegraUsoFalha quando
minWordsminWords(count)

O valor do campo contém menos de count palavras separadas por espaço em branco.

matchesPatternmatchesPattern(regex, message?)

O valor do campo não corresponde à expressão regular fornecida.

emailFormatemailFormat()

O valor do campo não está em um formato de endereço de e-mail válido.

urlFormaturlFormat()

O valor do campo não é uma URL válida iniciada por http:// ou https://.

isNumericisNumeric()

O valor do campo não pode ser interpretado como um número finito.

Regras de validação personalizadas

Crie regras personalizadas fornecendo um objeto com um método validate. Esse método recebe o valor do campo e retorna true para entrada válida ou uma string com a mensagem de erro para entrada inválida. Regras personalizadas se integram sem atrito às regras nativas dentro do array @ModalValidate.

src/components/invite-code.modal.ts
import {
  ModalComponent,
  ModalInput,
  ModalValidate,
  matchesPattern,
} from '@spraxium/components';
import type { ModalValidationRule } from '@spraxium/components';

const noSpaces: ModalValidationRule = {
  validate(value: string) {
    return value.includes(' ') ? 'Invite codes cannot contain spaces.' : true;
  },
};

const isUppercase: ModalValidationRule = {
  validate(value: string) {
    return value === value.toUpperCase()
      ? true
      : 'Invite code must be all uppercase.';
  },
};

@ModalComponent({ id: 'invite-modal', title: 'Enter Invite Code' })
export class InviteCodeModal {
  @ModalInput({
    label: 'Invite Code',
    style: 'short',
    required: true,
    placeholder: 'XXXX-XXXX-XXXX',
  })
  @ModalValidate([
    noSpaces,
    isUppercase,
    matchesPattern(
      /^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/,
      'Invalid invite code format. Expected: XXXX-XXXX-XXXX',
    ),
  ])
  code!: string;
}

Personalizando erros de validação

O decorator de classe @ModalValidationConfig personaliza o embed de erro enviado quando a validação falha. Por padrão, o sistema envia um embed efêmero simples com a mensagem de erro. Você pode sobrescrever a cor do embed, o título, o template de descrição, o rodapé e se a resposta será efêmera.

src/components/strict.modal.ts
import {
  ModalComponent,
  ModalInput,
  ModalValidate,
  ModalValidationConfig,
  ModalCache,
  minWords,
} from '@spraxium/components';

@ModalComponent({ id: 'strict-modal', title: 'Strict Input' })
@ModalCache({ ttl: 180 })
@ModalValidationConfig({
  ephemeral: true,
  embed: (errors) => ({
    color: 0xff4444,
    title: 'Validation Failed',
    footer: { text: 'Please fix the highlighted field and try again.' },
  }),
})
export class StrictModal {
  @ModalInput({
    label: 'Essay',
    style: 'paragraph',
    required: true,
  })
  @ModalValidate([minWords(50)])
  essay!: string;
}

Ciclo de vida da validação

O fluxo completo de validação envolve várias etapas coordenadas entre o dispatcher, o validator runner, o cache de campos e o componente responsável pela resposta de erro. Entender esse ciclo de vida ajuda você a projetar modais que oferecem uma experiência suave para o usuário.

Submissão recebida

O ModalDispatcher recebe o ModalSubmitInteraction do Discord. Ele resolve o customId do modal para o handler registrado e verifica se a classe do componente de modal tem decorators @ModalValidate em algum campo. Se não houver regras de validação, ele pula direto para a resolução dos campos e a chamada do handler.

Execução das regras

O ModalValidatorRunner percorre cada campo que possui regras @ModalValidate. Para cada campo, ele extrai o valor enviado e executa as regras na ordem do array. Na primeira regra que retorna uma mensagem de erro, a execução é interrompida imediatamente. O runner não verifica os campos nem as regras restantes.

Cache em caso de falha

Se o modal tiver @ModalCache e a validação falhar, o runner armazena todos os valores enviados no ModalFieldCache. A chave de cache é customId:userId, e os valores em cache formam um Record<string, string> que mapeia IDs de campo para os respectivos valores enviados. A entrada de cache recebe o TTL especificado em @ModalCache.

Resposta de erro

O dispatcher constrói um embed usando as configurações de @ModalValidationConfig, ou os padrões, e a mensagem de erro retornada pela regra que falhou. Ele envia isso como uma resposta efêmera para a interação de submissão do modal. O handler não é invocado.

Modal reaberto com cache

Quando o usuário aciona o modal novamente, por exemplo clicando no mesmo botão, a chamada ModalService.buildFor detecta valores em cache para essa combinação de usuário e modal. Ela preenche cada campo com o valor em cache, então o usuário vê a entrada anterior. Depois disso, basta corrigir o campo inválido e reenviar.

Submissão bem-sucedida

Quando todas as regras de validação passam, o runner retorna sucesso. Se o modal tiver @ModalCache, o dispatcher chama ModalService.clearCacheFor para remover entradas antigas desse usuário e modal. Em seguida, o dispatcher resolve os decorators @Field e invoca o handler normalmente.

Combinando validação com modais dinâmicos

A validação funciona sem atrito com campos dinâmicos e condicionais. O ModalValidatorRunner só valida campos que estão presentes no schema resolvido do modal. Se um campo foi excluído por @ModalWhen ou não foi gerado por @ModalDynamicFields, as regras de validação dele não são executadas. Isso significa que você pode aplicar @ModalValidate com segurança a campos condicionais sem se preocupar com validação quando eles estiverem ausentes.

src/components/dynamic-validated.modal.ts
import {
  ModalComponent,
  ModalDynamic,
  ModalInput,
  ModalWhen,
  ModalValidate,
  ModalCache,
  emailFormat,
  urlFormat,
  minWords,
} from '@spraxium/components';

interface FormData {
  includeContact: boolean;
}

@ModalComponent({ id: 'dynamic-form', title: 'Dynamic Form' })
@ModalDynamic<FormData>()
@ModalCache({ ttl: 300 })
export class DynamicValidatedModal {
  @ModalInput({
    label: 'Full Name',
    style: 'short',
    required: true,
  })
  name!: string;

  @ModalWhen<FormData>((data) => data?.includeContact === true)
  @ModalInput({
    label: 'Contact Email',
    style: 'short',
    required: true,
  })
  @ModalValidate([emailFormat()])
  email!: string;

  @ModalWhen<FormData>((data) => data?.includeContact === true)
  @ModalInput({
    label: 'Website',
    style: 'short',
    required: false,
  })
  @ModalValidate([urlFormat()])
  website!: string;

  @ModalInput({
    label: 'Message',
    style: 'paragraph',
    required: true,
  })
  @ModalValidate([minWords(10)])
  message!: string;
}