Skip to content

Commit

Permalink
feat(preconditions): add context to Container and Condition (#158)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Added `PreconditionContext` as third parameter to `IPreconditionContainer#run`.
BREAKING CHANGE: Changed `PermissionsPrecondition#context` from `PreconditionContext` to `Record<PropertyKey, unknown>`.
BREAKING CHANGE: Changed `PreconditionContainerSingle#context` from `PreconditionContext` to `Record<PropertyKey, unknown>`.
BREAKING CHANGE: Changed `PreconditionSingleResolvableDetails#context` from `PreconditionContext` to `Record<PropertyKey, unknown>`.
BREAKING CHANGE: Added `PreconditionContext` as third parameter to `IPreconditionCondition#run`.
  • Loading branch information
kyranet authored Feb 6, 2021
1 parent c8c7417 commit de6bc03
Show file tree
Hide file tree
Showing 10 changed files with 42 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/events/command-handler/CorePreCommandRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class CoreEvent extends Event<Events.PreCommandRun> {
return;
}

const result = await command.preconditions.run(message, command);
const result = await command.preconditions.run(message, command, { command });
if (isErr(result)) {
message.client.emit(Events.CommandDenied, result.error, payload);
} else {
Expand Down
4 changes: 3 additions & 1 deletion src/lib/structures/Precondition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ export abstract class Precondition extends Piece {
}
}

export interface PreconditionContext extends Record<PropertyKey, unknown> {}
export interface PreconditionContext extends Record<PropertyKey, unknown> {
command: Command;
}
5 changes: 3 additions & 2 deletions src/lib/utils/preconditions/IPreconditionContainer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Awaited } from '@sapphire/pieces';
import type { Message } from 'discord.js';
import type { UserError } from '../../errors/UserError';
import type { Command } from '../../structures/Command';
import type { Result } from '../../parsers/Result';
import type { Command } from '../../structures/Command';
import type { PreconditionContext } from '../../structures/Precondition';

/**
* Defines the result's value for a PreconditionContainer.
Expand Down Expand Up @@ -33,5 +34,5 @@ export interface IPreconditionContainer {
* @param message The message that ran this precondition.
* @param command The command the message invoked.
*/
run(message: Message, command: Command): PreconditionContainerReturn;
run(message: Message, command: Command, context: PreconditionContext): PreconditionContainerReturn;
}
7 changes: 4 additions & 3 deletions src/lib/utils/preconditions/PreconditionContainerArray.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Collection, Message } from 'discord.js';
import type { Command } from '../../structures/Command';
import type { PreconditionContext } from '../../structures/Precondition';
import type { IPreconditionCondition } from './conditions/IPreconditionCondition';
import { PreconditionConditionAnd } from './conditions/PreconditionConditionAnd';
import { PreconditionConditionOr } from './conditions/PreconditionConditionOr';
Expand Down Expand Up @@ -151,10 +152,10 @@ export class PreconditionContainerArray implements IPreconditionContainer {
* @param message The message that ran this precondition.
* @param command The command the message invoked.
*/
public run(message: Message, command: Command): PreconditionContainerReturn {
public run(message: Message, command: Command, context: PreconditionContext): PreconditionContainerReturn {
return this.mode === PreconditionRunMode.Sequential
? this.condition.sequential(message, command, this.entries)
: this.condition.parallel(message, command, this.entries);
? this.condition.sequential(message, command, this.entries, context)
: this.condition.parallel(message, command, this.entries, context);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions src/lib/utils/preconditions/PreconditionContainerSingle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export interface PreconditionSingleResolvableDetails {
* The context to be set at [[PreconditionContainerSingle.context]].
* @since 1.0.0
*/
context: PreconditionContext;
context: Record<PropertyKey, unknown>;
}

/**
Expand All @@ -40,7 +40,7 @@ export class PreconditionContainerSingle implements IPreconditionContainer {
* [[PreconditionSingleResolvableDetails.context]].
* @since 1.0.0
*/
public readonly context: PreconditionContext;
public readonly context: Record<PropertyKey, unknown>;

/**
* The name of the precondition to run.
Expand All @@ -64,9 +64,9 @@ export class PreconditionContainerSingle implements IPreconditionContainer {
* @param message The message that ran this precondition.
* @param command The command the message invoked.
*/
public run(message: Message, command: Command) {
public run(message: Message, command: Command, context: PreconditionContext) {
const precondition = Store.injectedContext.stores.get('preconditions').get(this.name);
if (precondition) return precondition.run(message, command, this.context);
if (precondition) return precondition.run(message, command, { ...context, ...this.context });
throw new Error(`The precondition "${this.name}" is not available.`);
}
}
15 changes: 13 additions & 2 deletions src/lib/utils/preconditions/conditions/IPreconditionCondition.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Message } from 'discord.js';
import type { Command } from '../../../structures/Command';
import type { PreconditionContext } from '../../../structures/Precondition';
import type { IPreconditionContainer, PreconditionContainerReturn } from '../IPreconditionContainer';

/**
Expand All @@ -15,7 +16,12 @@ export interface IPreconditionCondition {
* @param command The command the message invoked.
* @param entries The containers to run.
*/
sequential(message: Message, command: Command, entries: readonly IPreconditionContainer[]): PreconditionContainerReturn;
sequential(
message: Message,
command: Command,
entries: readonly IPreconditionContainer[],
context: PreconditionContext
): PreconditionContainerReturn;

/**
* Runs all the containers using `Promise.all`, then checks the results once all tasks finished running.
Expand All @@ -25,5 +31,10 @@ export interface IPreconditionCondition {
* @param command The command the message invoked.
* @param entries The containers to run.
*/
parallel(message: Message, command: Command, entries: readonly IPreconditionContainer[]): PreconditionContainerReturn;
parallel(
message: Message,
command: Command,
entries: readonly IPreconditionContainer[],
context: PreconditionContext
): PreconditionContainerReturn;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import type { IPreconditionCondition } from './IPreconditionCondition';
* @since 1.0.0
*/
export const PreconditionConditionAnd: IPreconditionCondition = {
async sequential(message, command, entries) {
async sequential(message, command, entries, context) {
for (const child of entries) {
const result = await child.run(message, command);
const result = await child.run(message, command, context);
if (isErr(result)) return result;
}

return ok();
},
async parallel(message, command, entries) {
const results = await Promise.all(entries.map((entry) => entry.run(message, command)));
async parallel(message, command, entries, context) {
const results = await Promise.all(entries.map((entry) => entry.run(message, command, context)));
// This is simplified compared to PreconditionContainerAny because we're looking for the first error.
// However, the base implementation short-circuits with the first Ok.
return results.find(isErr) ?? ok();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import type { IPreconditionCondition } from './IPreconditionCondition';
* @since 1.0.0
*/
export const PreconditionConditionOr: IPreconditionCondition = {
async sequential(message, command, entries) {
async sequential(message, command, entries, context) {
let error: PreconditionContainerResult | null = null;
for (const child of entries) {
const result = await child.run(message, command);
const result = await child.run(message, command, context);
if (isOk(result)) return result;
error = result;
}

return error ?? ok();
},
async parallel(message, command, entries) {
const results = await Promise.all(entries.map((entry) => entry.run(message, command)));
async parallel(message, command, entries, context) {
const results = await Promise.all(entries.map((entry) => entry.run(message, command, context)));

let error: PreconditionContainerResult | null = null;
for (const result of results) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { PermissionResolvable, Permissions } from 'discord.js';
import type { PreconditionContext } from '../../../structures/Precondition';
import type { PreconditionSingleResolvableDetails } from '../PreconditionContainerSingle';

/**
Expand All @@ -25,7 +24,7 @@ import type { PreconditionSingleResolvableDetails } from '../PreconditionContain
*/
export class PermissionsPrecondition implements PreconditionSingleResolvableDetails {
public name: string;
public context: PreconditionContext;
public context: Record<PropertyKey, unknown>;

/**
* Constructs a precondition container entry.
Expand Down
6 changes: 5 additions & 1 deletion src/preconditions/Cooldown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export class CorePrecondition extends Precondition {
public buckets = new WeakMap<Command, Bucket<string>>();

public run(message: Message, command: Command, context: CooldownContext) {
if (!context.delay || context.delay === 0) return this.ok();
// If the command it is testing for is not this one, return ok:
if (context.command !== command) return this.ok();

// If there is no delay (undefined, null, 0), return ok:
if (!context.delay) return this.ok();

const bucket = this.getBucket(command, context);
const remaining = bucket.take(this.getID(message, context));
Expand Down

0 comments on commit de6bc03

Please sign in to comment.