Rhys - Is there a way to use preconditions to remo...

RRhys11/13/2022
Is there a way to use preconditions to remove redundant if checks?

For example:

@ApplyOptions<Command.Options>({
  name: "channel-settings",
  preconditions: ["GuildOnly"]})
export class UserCommand extends Command {

  // slash command
  public async chatInputRun(interaction: Command.ChatInputInteraction) {
    const channel_settings = await this.container.answer_overflow.channel_settings.get({
      where: {
        channel_id: interaction.channelId,
      },
    });
     // Problem is here, precondition says this only runs in a guild
    const guild = interaction.guild;
  }
}


The expectation there is that guild would be defined, but its type is Guild | None so I have to add if checks to it to make typescript happy

Two alternatives I can think of are:

1. Making something like a 'GuildCommand' class which has a function that takes guild as a parameter, and then extending that class with the other commands

something along the lines of
export abstract class GuildCommand{

public abstract guildCommandRun(interaction: Command.ChatInputInteraction, guild: Guild, ...);


  public async chatInputRun(interaction: Command.ChatInputInteraction) {
if(!interaction.guild)
{ 
respond('no guild found';
return;
}
await guildCommandRun(interaction, guild, ...)

}


2. Asserting the type i.e
const guild = interaction.guild as Guild;

but then that's not great as I'm losing the typesafety of typescript and ignoring the warning

I'm currently leaning towards approach #1, with treating preconditions more as a runtime check thing for permissions & cooldowns

Looking at the docs again it actually looks like the validation in supposed to happen in the parse method, so I suppose you could put the conditional checks there, although then from my understanding you don't get to give error messages to the user
RRhys11/13/2022
Going to keep playing with this but found a solution that works decently

ButtonBaseHandler:
  public async getParseData(interaction: ButtonInteraction) {
    if (!this.checkIfButtonIDMatches(interaction)) return null;
    return {};
  }
 


GuildTextChannelButtonHandler
  public override async parse(interaction: ButtonInteraction) {
    const data = await super.parse(interaction);
    if (data.isNone()) return this.none();
    const interaction_with_target_channel = addTargetChannelToInteraction(
      interaction,
      data.unwrap()
    );
    return this.some(interaction_with_target_channel);
  }

This is layer 1, it now sets up having a parent channel in child classes

ChannelSettingsButtonHandler
  public override async parse(interaction: ButtonInteraction) {
    const some = await super.parse(interaction);
    if (some.isNone()) return this.none();
    const data = some.unwrap();
    const with_channel_data = await addChannelSettingsParseDataToInteraction(
      data.parent_channel,
      data
    );
    return this.some(with_channel_data);
  }


edit: realized this was for a different problem than the one above with button setups, whoopsie
RRhys11/13/2022
So each layer overrides get parse data to extract the data it needs, then the very bottom layer has to call what is in the parse function to have types available throughout it
S2Seren_Modz 2111/13/2022
u can use generics
it works on other interaction types that have the cache type generic
async chatInputRun(interaction: Command.ChatInputInteraction<"cached">) {
  // interaction.guild will no longer be nullable
}
S2Seren_Modz 2111/13/2022
i personally don't see any issue with doing this because the precondition already prevents the command if there is no guild
RRhys11/14/2022
Ah thanks that's helpful, I think that's what I'm looking for