diff --git a/backend/src/api/guilds.ts b/backend/src/api/guilds.ts index 6eabfa24..91096386 100644 --- a/backend/src/api/guilds.ts +++ b/backend/src/api/guilds.ts @@ -76,10 +76,9 @@ export function initGuildsAPI(app: express.Express) { parsedConfig = {}; } - const errors = await validateGuildConfig(parsedConfig); - - if (errors) { - return res.status(422).json({ errors }); + const error = await validateGuildConfig(parsedConfig); + if (error) { + return res.status(422).json({ errors: [error] }); } await configs.saveNewRevision(`guild-${req.params.guildId}`, config, req.user.userId); diff --git a/backend/src/configValidator.ts b/backend/src/configValidator.ts index 3a0227b9..19caab37 100644 --- a/backend/src/configValidator.ts +++ b/backend/src/configValidator.ts @@ -3,7 +3,7 @@ import { guildPlugins } from "./plugins/availablePlugins"; import { decodeAndValidateStrict, StrictValidationError } from "./validatorUtils"; import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; import { IZeppelinGuildConfig } from "./types"; -import { configUtils, PluginOptions } from "knub"; +import { configUtils, ConfigValidationError, PluginOptions } from "knub"; const pluginNameToPlugin = new Map(); for (const plugin of guildPlugins) { @@ -26,7 +26,7 @@ const globalConfigRootSchema = t.type({ const partialMegaTest = t.partial({ name: t.string }); -export async function validateGuildConfig(config: any): Promise { +export async function validateGuildConfig(config: any): Promise { const validationResult = decodeAndValidateStrict(partialGuildConfigRootSchema, config); if (validationResult instanceof StrictValidationError) return validationResult.getErrors(); @@ -35,7 +35,7 @@ export async function validateGuildConfig(config: any): Promise if (guildConfig.plugins) { for (const [pluginName, pluginOptions] of Object.entries(guildConfig.plugins)) { if (!pluginNameToPlugin.has(pluginName)) { - return [`Unknown plugin: ${pluginName}`]; + return `Unknown plugin: ${pluginName}`; } const plugin = pluginNameToPlugin.get(pluginName); @@ -43,10 +43,8 @@ export async function validateGuildConfig(config: any): Promise const mergedOptions = configUtils.mergeConfig(plugin.defaultOptions || {}, pluginOptions); await plugin.configPreprocessor(mergedOptions as PluginOptions); } catch (err) { - if (err instanceof StrictValidationError) { - return err.getErrors().map(err => { - return `${pluginName}: ${err.toString()}`; - }); + if (err instanceof ConfigValidationError) { + return `${pluginName}: ${err.toString()}`; } throw err; diff --git a/backend/src/index.ts b/backend/src/index.ts index ad2b882e..0485fe7f 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -21,6 +21,7 @@ import { GuildLogs } from "./data/GuildLogs"; import { LogType } from "./data/LogType"; import { ZeppelinPlugin } from "./plugins/ZeppelinPlugin"; import { logger } from "./logger"; +import { PluginLoadError } from "knub/dist/plugins/PluginLoadError"; const fsp = fs.promises; @@ -59,6 +60,12 @@ if (process.env.NODE_ENV === "production") { return; } + if (err instanceof PluginLoadError) { + // tslint:disable:no-console + console.warn(`${err.guild.name} (${err.guild.id}): Failed to load plugin '${err.pluginName}': ${err.message}`); + return; + } + // tslint:disable:no-console console.error(err); diff --git a/backend/src/pluginUtils.ts b/backend/src/pluginUtils.ts index 23b1ca61..9fa34040 100644 --- a/backend/src/pluginUtils.ts +++ b/backend/src/pluginUtils.ts @@ -3,7 +3,15 @@ */ import { Member } from "eris"; -import { CommandContext, configUtils, helpers, PluginBlueprint, PluginData, PluginOptions } from "knub"; +import { + CommandContext, + configUtils, + ConfigValidationError, + helpers, + PluginBlueprint, + PluginData, + PluginOptions, +} from "knub"; import { decodeAndValidateStrict, StrictValidationError, validate } from "./validatorUtils"; import { deepKeyIntersect, errorMessage, successMessage, tDeepPartial, tNullable } from "./utils"; import { ZeppelinPluginBlueprint } from "./plugins/ZeppelinPluginBlueprint"; @@ -52,6 +60,15 @@ const BasicPluginStructureType = t.type({ replaceDefaultOverrides: tNullable(t.boolean), }); +export function strictValidationErrorToConfigValidationError(err: StrictValidationError) { + return new ConfigValidationError( + err + .getErrors() + .map(e => e.toString()) + .join("\n"), + ); +} + export function getPluginConfigPreprocessor( blueprint: ZeppelinPluginBlueprint, customPreprocessor?: PluginBlueprint["configPreprocessor"], @@ -60,17 +77,16 @@ export function getPluginConfigPreprocessor( // 1. Validate the basic structure of plugin config const basicOptionsValidation = validate(BasicPluginStructureType, options); if (basicOptionsValidation instanceof StrictValidationError) { - throw basicOptionsValidation; + throw strictValidationErrorToConfigValidationError(basicOptionsValidation); } // 2. Validate config/overrides against *partial* config schema. This ensures valid properties have valid types. const partialConfigSchema = tDeepPartial(blueprint.configSchema); - // if (blueprint.name === "automod") console.log(partialConfigSchema); if (options.config) { const partialConfigValidation = validate(partialConfigSchema, options.config); if (partialConfigValidation instanceof StrictValidationError) { - throw partialConfigValidation; + throw strictValidationErrorToConfigValidationError(partialConfigValidation); } } @@ -78,7 +94,7 @@ export function getPluginConfigPreprocessor( for (const override of options.overrides) { const partialOverrideConfigValidation = validate(partialConfigSchema, override.config || {}); if (partialOverrideConfigValidation) { - throw partialOverrideConfigValidation; + throw strictValidationErrorToConfigValidationError(partialOverrideConfigValidation); } } } @@ -97,7 +113,7 @@ export function getPluginConfigPreprocessor( ? decodeAndValidateStrict(blueprint.configSchema, options.config) : options.config; if (decodedConfig instanceof StrictValidationError) { - throw decodedConfig; + throw strictValidationErrorToConfigValidationError(decodedConfig); } } @@ -108,7 +124,7 @@ export function getPluginConfigPreprocessor( ? decodeAndValidateStrict(blueprint.configSchema, overrideConfigMergedWithBaseConfig) : overrideConfigMergedWithBaseConfig; if (decodedOverrideConfig instanceof StrictValidationError) { - throw decodedOverrideConfig; + throw strictValidationErrorToConfigValidationError(decodedOverrideConfig); } decodedOverrides.push({ ...override,