Skip to content
This repository has been archived by the owner on Jan 8, 2022. It is now read-only.

Commit

Permalink
fix: replace ow with zod (#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
Khasms committed Nov 30, 2021
1 parent 580a955 commit 0b6fb81
Show file tree
Hide file tree
Showing 9 changed files with 6,557 additions and 3,833 deletions.
10,214 changes: 6,473 additions & 3,741 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@
"dependencies": {
"@sindresorhus/is": "^4.2.0",
"discord-api-types": "^0.24.0",
"ow": "^0.27.0",
"ts-mixer": "^6.0.0",
"tslib": "^2.3.1"
"tslib": "^2.3.1",
"zod": "^3.11.6"
},
"devDependencies": {
"@babel/core": "^7.15.8",
Expand Down
21 changes: 11 additions & 10 deletions src/interactions/contextMenuCommands/Assertions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import ow from 'ow';
import { z } from 'zod';
import { ApplicationCommandType } from 'discord-api-types/v9';
import type { ContextMenuCommandType } from './ContextMenuCommandBuilder';

Expand All @@ -10,23 +10,24 @@ export function validateRequiredParameters(name: string, type: number) {
validateType(type);
}

const namePredicate = ow.string
.minLength(1)
.maxLength(32)
.matches(/^( *[\p{L}\p{N}_-]+ *)+$/u);
const namePredicate = z
.string()
.min(1)
.max(32)
.regex(/^( *[\p{L}\p{N}_-]+ *)+$/u);

export function validateName(name: unknown): asserts name is string {
ow(name, 'name', namePredicate);
namePredicate.parse(name);
}

const typePredicate = ow.number.oneOf([ApplicationCommandType.User, ApplicationCommandType.Message]);
const typePredicate = z.union([z.literal(ApplicationCommandType.User), z.literal(ApplicationCommandType.Message)]);

export function validateType(type: unknown): asserts type is ContextMenuCommandType {
ow(type, 'type', typePredicate);
typePredicate.parse(type);
}

const defaultPermissionPredicate = ow.boolean;
const booleanPredicate = z.boolean();

export function validateDefaultPermission(value: unknown): asserts value is boolean {
ow(value, 'default_permission', defaultPermissionPredicate);
booleanPredicate.parse(value);
}
34 changes: 18 additions & 16 deletions src/interactions/slashCommands/Assertions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import is from '@sindresorhus/is';
import type { APIApplicationCommandOptionChoice } from 'discord-api-types/v9';
import ow from 'ow';
import { z } from 'zod';
import type { SlashCommandOptionBase } from './mixins/CommandOptionBase';
import type { ToAPIApplicationCommandOptions } from './SlashCommandBuilder';
import type { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder } from './SlashCommandSubcommands';
Expand All @@ -20,38 +20,40 @@ export function validateRequiredParameters(
validateMaxOptionsLength(options);
}

const namePredicate = ow.string.lowercase
.minLength(1)
.maxLength(32)
.addValidator({
message: (value, label) => `Expected ${label!} to match "^[\\p{L}\\p{N}_-]+$", got ${value} instead`,
validator: (value) => /^[\p{L}\p{N}_-]+$/u.test(value),
});
const namePredicate = z
.string()
.min(1)
.max(32)
.regex(/^[\P{Lu}\p{N}_-]+$/u);

export function validateName(name: unknown): asserts name is string {
ow(name, 'name', namePredicate);
namePredicate.parse(name);
}

const descriptionPredicate = ow.string.minLength(1).maxLength(100);
const descriptionPredicate = z.string().min(1).max(100);

export function validateDescription(description: unknown): asserts description is string {
ow(description, 'description', descriptionPredicate);
descriptionPredicate.parse(description);
}

const defaultPermissionPredicate = ow.boolean;
const booleanPredicate = z.boolean();

export function validateDefaultPermission(value: unknown): asserts value is boolean {
ow(value, 'default_permission', defaultPermissionPredicate);
booleanPredicate.parse(value);
}

const maxArrayLengthPredicate = ow.array.maxLength(25);
export function validateRequired(required: unknown): asserts required is boolean {
booleanPredicate.parse(required);
}

const maxArrayLengthPredicate = z.unknown().array().max(25);

export function validateMaxOptionsLength(options: unknown): asserts options is ToAPIApplicationCommandOptions[] {
ow(options, 'options', maxArrayLengthPredicate);
maxArrayLengthPredicate.parse(options);
}

export function validateMaxChoicesLength(choices: APIApplicationCommandOptionChoice[]) {
ow(choices, 'choices', maxArrayLengthPredicate);
maxArrayLengthPredicate.parse(choices);
}

export function assertReturnOfBuilder<
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIApplicationCommandChannelOptions, ApplicationCommandOptionType, ChannelType } from 'discord-api-types/v9';
import ow from 'ow';
import { z, ZodLiteral } from 'zod';
import type { ToAPIApplicationCommandOptions } from '../../..';
import { SlashCommandOptionBase } from './CommandOptionBase';

Expand All @@ -16,7 +16,13 @@ const allowedChannelTypes = [
ChannelType.GuildPrivateThread,
];

const channelTypePredicate = ow.number.oneOf(allowedChannelTypes);
const channelTypePredicate = z.union(
allowedChannelTypes.map((type) => z.literal(type)) as [
ZodLiteral<ChannelType>,
ZodLiteral<ChannelType>,
...ZodLiteral<ChannelType>[]
],
);

export abstract class ApplicationCommandOptionWithChannelTypesBase
extends SlashCommandOptionBase
Expand All @@ -32,7 +38,7 @@ export abstract class ApplicationCommandOptionWithChannelTypesBase
public addChannelType(channelType: Exclude<ChannelType, ChannelType.DM | ChannelType.GroupDM>) {
this.channelTypes ??= [];

ow(channelType, 'channel type', channelTypePredicate);
channelTypePredicate.parse(channelType);
this.channelTypes.push(channelType);

return this;
Expand Down
7 changes: 3 additions & 4 deletions src/interactions/slashCommands/mixins/CommandOptionBase.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { APIApplicationCommandOption, ApplicationCommandOptionType } from 'discord-api-types/v9';
import ow from 'ow';
import { validateRequiredParameters } from '../Assertions';
import { validateRequiredParameters, validateRequired } from '../Assertions';
import type { ToAPIApplicationCommandOptions } from '../SlashCommandBuilder';
import { SharedNameAndDescription } from './NameAndDescription';

Expand All @@ -20,7 +19,7 @@ export class SlashCommandOptionBase extends SharedNameAndDescription implements
*/
public setRequired(required: boolean) {
// Assert that you actually passed a boolean
ow(required, 'required', ow.boolean);
validateRequired(required);

this.required = required;

Expand All @@ -31,7 +30,7 @@ export class SlashCommandOptionBase extends SharedNameAndDescription implements
validateRequiredParameters(this.name, this.description, []);

// Assert that you actually passed a boolean
ow(this.required, 'required', ow.boolean);
validateRequired(this.required);

return {
type: this.type,
Expand Down
33 changes: 8 additions & 25 deletions src/interactions/slashCommands/mixins/CommandOptionWithChoices.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { APIApplicationCommandOptionChoice, ApplicationCommandOptionType } from 'discord-api-types/v9';
import ow, { Predicate } from 'ow';
import { z } from 'zod';
import { validateMaxChoicesLength } from '../Assertions';
import type { ToAPIApplicationCommandOptions } from '../SlashCommandBuilder';
import { SlashCommandOptionBase } from './CommandOptionBase';

const stringPredicate = ow.string.minLength(1).maxLength(100);
const integerPredicate = ow.number.finite;

// TODO: See resolution for sindresorhus/ow#217 in relation to this cast
const choicesPredicate = ow.array.ofType<[string, string | number]>(
ow.array.exactShape([stringPredicate, ow.any(ow.string, integerPredicate) as unknown as Predicate<string | number>]),
);
const stringPredicate = z.string().min(1).max(100);
const numberPredicate = z.number().gt(-Infinity).lt(Infinity);
const choicesPredicate = z.tuple([stringPredicate, z.union([stringPredicate, numberPredicate])]).array();

export abstract class ApplicationCommandOptionWithChoicesBase<T extends string | number>
extends SlashCommandOptionBase
Expand All @@ -30,11 +26,11 @@ export abstract class ApplicationCommandOptionWithChoicesBase<T extends string |
validateMaxChoicesLength(this.choices);

// Validate name
ow(name, `${ApplicationCommandOptionTypeNames[this.type]} choice name`, stringPredicate);
stringPredicate.parse(name);

// Validate the value
if (this.type === ApplicationCommandOptionType.String) ow(value, 'string choice value', stringPredicate);
else ow(value, `${ApplicationCommandOptionTypeNames[this.type]} choice value`, integerPredicate);
if (this.type === ApplicationCommandOptionType.String) stringPredicate.parse(value);
else numberPredicate.parse(value);

this.choices.push({ name, value });

Expand All @@ -47,7 +43,7 @@ export abstract class ApplicationCommandOptionWithChoicesBase<T extends string |
* @param choices The choices to add
*/
public addChoices(choices: [name: string, value: T][]) {
ow(choices, `${ApplicationCommandOptionTypeNames[this.type]} choices`, choicesPredicate);
choicesPredicate.parse(choices);

for (const [label, value] of choices) this.addChoice(label, value);
return this;
Expand All @@ -60,16 +56,3 @@ export abstract class ApplicationCommandOptionWithChoicesBase<T extends string |
};
}
}

const ApplicationCommandOptionTypeNames = {
[ApplicationCommandOptionType.Subcommand]: 'subcommand',
[ApplicationCommandOptionType.SubcommandGroup]: 'subcommand group',
[ApplicationCommandOptionType.String]: 'string',
[ApplicationCommandOptionType.Integer]: 'integer',
[ApplicationCommandOptionType.Boolean]: 'boolean',
[ApplicationCommandOptionType.User]: 'user',
[ApplicationCommandOptionType.Channel]: 'channel',
[ApplicationCommandOptionType.Role]: 'role',
[ApplicationCommandOptionType.Mentionable]: 'mentionable',
[ApplicationCommandOptionType.Number]: 'number',
} as const;
30 changes: 16 additions & 14 deletions src/messages/embed/Assertions.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
import type { APIEmbedField } from 'discord-api-types/v9';
import ow from 'ow';
import { z } from 'zod';

export const fieldNamePredicate = ow.string.minLength(1).maxLength(256);
export const fieldNamePredicate = z.string().min(1).max(256);

export const fieldValuePredicate = ow.string.minLength(1).maxLength(1024);
export const fieldValuePredicate = z.string().min(1).max(1024);

export const fieldInlinePredicate = ow.optional.boolean;
export const fieldInlinePredicate = z.boolean().optional();

export const embedFieldPredicate = ow.object.exactShape({
export const embedFieldPredicate = z.object({
name: fieldNamePredicate,
value: fieldValuePredicate,
inline: fieldInlinePredicate,
});

export const embedFieldsArrayPredicate = ow.array.ofType(embedFieldPredicate);
export const embedFieldsArrayPredicate = embedFieldPredicate.array();

export const fieldLengthPredicate = z.number().lte(25);

export function validateFieldLength(fields: APIEmbedField[], amountAdding: number): void {
ow(fields.length + amountAdding, 'field amount', ow.number.lessThanOrEqual(25));
fieldLengthPredicate.parse(fields.length + amountAdding);
}

export const authorNamePredicate = ow.any(fieldNamePredicate, ow.null);
export const authorNamePredicate = fieldNamePredicate.nullable();

export const urlPredicate = ow.any(ow.string.url, ow.nullOrUndefined);
export const urlPredicate = z.string().url().nullish();

export const colorPredicate = ow.any(ow.number.greaterThanOrEqual(0).lessThanOrEqual(0xffffff), ow.null);
export const colorPredicate = z.number().gte(0).lte(0xffffff).nullable();

export const descriptionPredicate = ow.any(ow.string.minLength(1).maxLength(4096), ow.null);
export const descriptionPredicate = z.string().min(1).max(4096).nullable();

export const footerTextPredicate = ow.any(ow.string.minLength(1).maxLength(2048), ow.null);
export const footerTextPredicate = z.string().min(1).max(2048).nullable();

export const timestampPredicate = ow.any(ow.number, ow.date, ow.null);
export const timestampPredicate = z.union([z.number(), z.date()]).nullable();

export const titlePredicate = ow.any(fieldNamePredicate, ow.null);
export const titlePredicate = fieldNamePredicate.nullable();

0 comments on commit 0b6fb81

Please sign in to comment.