Skip to content

Commit 07d70c2

Browse files
committed
fix(env): make errors from environment clearer (#115)
1 parent e1614e0 commit 07d70c2

File tree

9 files changed

+49
-12
lines changed

9 files changed

+49
-12
lines changed

src/core/createEnvForModule.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { constantCase } from 'constant-case';
22

3-
import type { CreatedModule, ModuleFactory } from './createModule';
3+
import type { CreatedModule, ModuleCreator } from './createModule';
44

55
const createEnvForModule = (constantName: string) =>
66
Object.entries(process.env)
@@ -18,14 +18,14 @@ const createEnvForModule = (constantName: string) =>
1818
}, {});
1919

2020
export const createAllModules = async (
21-
modules: Record<string, ModuleFactory>,
21+
modules: Record<string, ModuleCreator>,
2222
): Promise<CreatedModule[]> => {
2323
const createdModules: CreatedModule[] = [];
2424

25-
for (const [name, factory] of Object.entries(modules)) {
25+
for (const { name, factory } of Object.values(modules)) {
2626
const moduleConstantName = constantCase(name);
27-
const moduleEnv = createEnvForModule(moduleConstantName);
28-
const module = await factory({ env: moduleEnv });
27+
const env = createEnvForModule(moduleConstantName);
28+
const module = await factory({ env });
2929

3030
createdModules.push(module);
3131
}

src/core/createModule.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { constantCase } from 'constant-case';
12
import type { ClientEvents, ClientOptions } from 'discord.js';
23
import type { ZodTypeAny } from 'zod';
34
import { z } from 'zod';
@@ -21,6 +22,7 @@ type EventHandlers = {
2122
};
2223

2324
type BotModule<Env extends Record<string, ZodTypeAny>> = {
25+
name: string;
2426
env?: Env;
2527
intents?: ClientOptions['intents'];
2628
slashCommands?: ModuleFunction<Env, Array<BotCommand>>;
@@ -31,28 +33,56 @@ interface CreatedModuleInput {
3133
env: unknown;
3234
}
3335

36+
type ModuleFactory = (input: CreatedModuleInput) => Promise<CreatedModule>;
37+
3438
export interface CreatedModule {
3539
intents: ClientOptions['intents'];
3640
slashCommands: Array<BotCommand>;
3741
eventHandlers: EventHandlers;
3842
}
3943

40-
export type ModuleFactory = (input: CreatedModuleInput) => Promise<CreatedModule>;
44+
export interface ModuleCreator {
45+
name: string;
46+
factory: ModuleFactory;
47+
}
4148

4249
export const createModule = <Env extends Record<string, ZodTypeAny>>(
4350
module: BotModule<Env>,
44-
): ModuleFactory => {
45-
return async (input) => {
46-
const env = await z.object(module.env ?? ({} as Env)).parseAsync(input.env);
51+
): ModuleCreator => ({
52+
name: module.name,
53+
factory: async (input) => {
54+
const result = await z.object(module.env ?? ({} as Env)).safeParseAsync(input.env);
55+
56+
if (!result.success) {
57+
const constantName = constantCase(module.name);
58+
const zodErrors = result.error.flatten().fieldErrors;
59+
60+
const errors = Object.entries(zodErrors).reduce<Record<string, string[]>>(
61+
(acc, [key, value]) => ({
62+
...acc,
63+
...(Array.isArray(value) ? { [`${constantName}_${key}`]: value } : {}),
64+
}),
65+
{},
66+
);
67+
68+
const formattedErrors = Object.entries(errors).reduce(
69+
(acc, [key, values]) => values.reduce((acc, value) => `${acc}\n\t- ${key}: ${value}`, acc),
70+
'',
71+
);
72+
73+
throw new Error(
74+
`Encountered errors while validating environment variables for module ${module.name}:${formattedErrors}`,
75+
);
76+
}
4777

4878
const context = {
49-
env,
79+
env: result.data,
5080
};
5181

5282
return {
5383
intents: module.intents ?? [],
5484
slashCommands: module.slashCommands?.(context) ?? [],
5585
eventHandlers: module.eventHandlers?.(context) ?? {},
5686
};
57-
};
58-
};
87+
},
88+
});

src/modules/coolLinksManagement/coolLinksManagement.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const getThreadNameFromOpenGraph = async (url: string): Promise<string | null> =
3434
const youtubeUrlRegex = new RegExp('^(https?)?(://)?(www.)?(m.)?((youtube.com)|(youtu.be))');
3535

3636
export const coolLinksManagement = createModule({
37+
name: 'coolLinksManagement',
3738
env: {
3839
CHANNEL_ID: z.string().nonempty(),
3940
PAGE_SUMMARIZER_BASE_URL: z.string().url(),

src/modules/fart/fart.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { PermissionFlagsBits, SlashCommandBuilder } from 'discord.js';
33
import { createModule } from '../../core/createModule';
44

55
export const fart = createModule({
6+
name: 'fart',
67
slashCommands: () => [
78
{
89
schema: new SlashCommandBuilder()

src/modules/fixEmbedTwitterVideo/fixEmbedTwitterVideo.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const isTwitterVideo = async (tweetURL: string): Promise<boolean> => {
8888
};
8989

9090
export const fixEmbedTwitterVideo = createModule({
91+
name: 'fixEmbedTwitterVideo',
9192
env: {
9293
EXCLUDED_CHANNEL_ID: z.string().nonempty(),
9394
},

src/modules/quoiFeur/quoiFeur.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
} from './quoiFeur.helpers';
1010

1111
export const quoiFeur = createModule({
12+
name: 'quoiFeur',
1213
slashCommands: () => [
1314
{
1415
schema: new SlashCommandBuilder()

src/modules/recurringMessage/recurringMessage.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from './recurringMessage.helpers';
1212

1313
export const recurringMessage = createModule({
14+
name: 'recurringMessage',
1415
slashCommands: () => [
1516
{
1617
schema: new SlashCommandBuilder()

src/modules/voiceOnDemand/voiceOnDemand.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
} from './voiceOnDemand.helpers';
1111

1212
export const voiceOnDemand = createModule({
13+
name: 'voiceOnDemand',
1314
slashCommands: () => [
1415
{
1516
schema: new SlashCommandBuilder()

tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"noUncheckedIndexedAccess": true,
1414
"noUnusedLocals": true,
1515
"noUnusedParameters": true,
16+
"noEmit": true,
1617
"resolveJsonModule": true,
1718
"skipDefaultLibCheck": true,
1819
"skipLibCheck": true,

0 commit comments

Comments
 (0)