Spraxium logoSpraxium

Argumentos e Parsing

O parser de prefix tokeniza mensagens e faz coerção dos argumentos para 14 saídas tipadas. Conheça cada tipo de @PrefixArg, as opções de validação e como a pipeline de parsing funciona.

Como a entrada da mensagem vira argv

Depois que o prefixo é detectado, o parser remove o trecho correspondente e tokeniza o restante do texto. O primeiro token vira o nome do comando e tudo o que vem depois forma o array argv. A tokenização oferece suporte a strings entre aspas duplas, então "hello world" é tratado como um único token em vez de dois. Isso permite passar argumentos com várias palavras sem precisar escapar manualmente.

O tokenizer divide a entrada por espaços em branco por padrão. Quando encontra uma aspa dupla de abertura, ele coleta todos os caracteres até a aspa de fechamento e emite tudo como um único token. Se a aspa de fechamento não aparecer, o restante da entrada vira o token atual. Esse comportamento é intencional e evita desalinhamento silencioso de argumentos.

Em comandos com subcomandos, o primeiro token de argv é comparado com os nomes de subcomando registrados antes de o parsing dos argumentos começar. Se houver correspondência, o framework desloca o argv e passa a usar o schema específico daquele subcomando no lugar do schema raiz. Isso significa que o parser resolve o roteamento antes de resolver os argumentos.

Decorators de método @PrefixArg

O namespace @PrefixArg oferece 14 decorators de método tipados que você empilha sobre um método build em comandos simples ou sobre métodos marcados com @PrefixSubcommand em comandos com subcomandos. Cada decorator declara um argumento posicional com nome, tipo e regras opcionais de validação. Eles se acumulam de cima para baixo para determinar a ordem posicional.

import { PrefixArg, PrefixCommand } from '@spraxium/common';

@PrefixCommand({ name: 'roll', cooldown: 3 })
export class RollCommand {
  @PrefixArg.Integer('sides', { required: false, min: 2, max: 1000 })
  @PrefixArg.Integer('count', { required: false, min: 1, max: 25 })
  build() {}
}

Tipos de argumento suportados

O framework oferece 14 tipos de argumento que cobrem os padrões de entrada mais comuns em bots de Discord. Cada tipo tem seu próprio parser, responsável por converter o token bruto em uma saída tipada. Se a conversão falhar, o parser lança uma ArgumentException com uma mensagem descritiva.

Opções de validação

Todo decorator @PrefixArg aceita um objeto opcional de configuração com regras de validação. Essas regras são aplicadas durante a fase de parsing, antes de o handler receber o valor. Se qualquer validação falhar, o parser lança uma ArgumentException contendo o nome do argumento, a restrição esperada e a entrada recebida.

Ordem da pipeline de validação

O parser de argumentos aplica as validações em uma ordem específica para cada argumento. Entender essa ordem ajuda a desenhar schemas que geram mensagens de erro claras, porque a primeira validação que falha é a que a pessoa usuária vê. A pipeline interrompe a execução na primeira falha, então checagens posteriores só rodam se todas as anteriores passarem.

Checagem de presença

Se o argumento for obrigatório e o token bruto estiver ausente ou vazio, o parser lança uma ArgumentException imediatamente. Argumentos opcionais sem token recebem undefined e pulam todas as verificações restantes. Esse é o primeiro gate que impede valores vazios de chegarem aos parsers específicos de tipo.

Validação customizada

Se houver uma regex em validation.pattern, a entrada bruta precisa casar com ela. Se houver uma função em validation.validate, ela recebe o valor bruto e deve devolver true ou uma string de erro. As duas checagens rodam antes de qualquer coerção de tipo, então você consegue validar formato de maneira independente do parsing final.

Checagem de choices

Se houver um array choices, a entrada precisa corresponder exatamente a um dos valores permitidos. Isso funciona como uma restrição de enum e é útil para argumentos que precisam vir de um conjunto fixo, como status, tipos de ação ou modos disponíveis.

Coerção de tipo e limites

O parser converte o token bruto para o tipo-alvo usando o parser específico daquele tipo. Para tipos numéricos, as checagens de faixa, min e max, rodam depois da coerção bem-sucedida. Para tipos de texto, são aplicados minLength e maxLength. Se a coerção ou os limites falharem, o parser lança uma ArgumentException.

Decorator de parâmetro @PrefixArg

Na classe handler, a função @PrefixArg sem namespace funciona como decorator de parâmetro que injeta um valor de argumento já processado pelo nome. Esse nome precisa corresponder a um argumento declarado no schema do comando. O decorator @Ctx injeta o objeto Message bruto. Juntos, eles permitem que o handler receba parâmetros totalmente tipados sem parsing manual.

src/modules/fun/handlers/roll-command.handler.ts
import { Ctx, PrefixArg, PrefixCommandHandler } from '@spraxium/common';
import type { Message } from 'discord.js';
import { RollCommand } from '../commands/roll.command';

@PrefixCommandHandler(RollCommand)
export class RollHandler {
  async handle(
    @Ctx() message: Message,
    @PrefixArg('sides') sides: number | undefined,
    @PrefixArg('count') count: number | undefined,
  ): Promise<void> {
    const faces = sides ?? 6;
    const rolls = count ?? 1;
    const results = Array.from(
      { length: rolls },
      () => Math.floor(Math.random() * faces) + 1,
    );
    await message.reply(`Rolled ${rolls}d${faces}: [${results.join(', ')}]`);
  }
}