3
0
Fork 0
mirror of https://github.com/ZeppelinBot/Zeppelin.git synced 2025-03-15 05:41:51 +00:00

Validate override criteria and extra criteria. When loading existing configs, silently remove invalid overrides.

This commit is contained in:
Dragory 2021-05-23 18:10:23 +03:00
parent 8ab6538744
commit 52e4b5b519
No known key found for this signature in database
GPG key ID: 5F387BA66DF8AAC1
4 changed files with 62 additions and 5 deletions

View file

@ -37,7 +37,7 @@ export async function validateGuildConfig(config: any): Promise<string | null> {
const plugin = pluginNameToPlugin.get(pluginName)!;
try {
const mergedOptions = configUtils.mergeConfig(plugin.defaultOptions || {}, pluginOptions);
await plugin.configPreprocessor?.((mergedOptions as unknown) as PluginOptions<any>);
await plugin.configPreprocessor?.((mergedOptions as unknown) as PluginOptions<any>, true);
} catch (err) {
if (err instanceof ConfigValidationError || err instanceof StrictValidationError) {
return `${pluginName}: ${err.message}`;

View file

@ -54,6 +54,19 @@ const PluginOverrideCriteriaType: t.Type<PluginOverrideCriteria<unknown>> = t.re
}),
);
const validTopLevelOverrideKeys = [
"channel",
"category",
"level",
"user",
"role",
"all",
"any",
"not",
"extra",
"config",
];
const BasicPluginStructureType = t.type({
enabled: tNullable(t.boolean),
config: tNullable(t.unknown),
@ -74,7 +87,7 @@ export function getPluginConfigPreprocessor(
blueprint: ZeppelinPlugin,
customPreprocessor?: ZeppelinPlugin["configPreprocessor"],
) {
return async (options: PluginOptions<any>) => {
return async (options: PluginOptions<any>, strict?: boolean) => {
// 1. Validate the basic structure of plugin config
const basicOptionsValidation = validate(BasicPluginStructureType, options);
if (basicOptionsValidation instanceof StrictValidationError) {
@ -93,8 +106,32 @@ export function getPluginConfigPreprocessor(
if (options.overrides) {
for (const override of options.overrides) {
const partialOverrideConfigValidation = validate(partialConfigSchema, override.config || {});
if (partialOverrideConfigValidation) {
// Validate criteria and extra criteria
// FIXME: This is ugly
for (const key of Object.keys(override)) {
if (!validTopLevelOverrideKeys.includes(key)) {
if (strict) {
throw new ConfigValidationError(`Unknown override criterion '${key}'`);
}
delete override[key];
}
}
if (override.extra != null) {
for (const extraCriterion of Object.keys(override.extra)) {
if (!blueprint.customOverrideCriteriaFunctions?.[extraCriterion]) {
if (strict) {
throw new ConfigValidationError(`Unknown override extra criterion '${extraCriterion}'`);
}
delete override.extra[extraCriterion];
}
}
}
// Validate override config
const partialOverrideConfigValidation = decodeAndValidateStrict(partialConfigSchema, override.config || {});
if (partialOverrideConfigValidation instanceof StrictValidationError) {
throw strictValidationErrorToConfigValidationError(partialOverrideConfigValidation);
}
}

View file

@ -10,6 +10,8 @@ import {
import * as t from "io-ts";
import { getPluginConfigPreprocessor } from "../pluginUtils";
import { TMarkdown } from "../types";
import { Awaitable } from "knub/dist/utils";
import { PluginOptions } from "knub/dist/config/configTypes";
/**
* GUILD PLUGINS
@ -26,6 +28,11 @@ export interface ZeppelinGuildPluginBlueprint<TPluginData extends GuildPluginDat
usageGuide?: TMarkdown;
configurationGuide?: TMarkdown;
};
configPreprocessor?: (
options: PluginOptions<TPluginData["_pluginType"]>,
strict?: boolean,
) => Awaitable<PluginOptions<TPluginData["_pluginType"]>>;
}
export function zeppelinGuildPlugin<TBlueprint extends ZeppelinGuildPluginBlueprint>(
@ -59,6 +66,7 @@ export function zeppelinGuildPlugin(...args) {
export interface ZeppelinGlobalPluginBlueprint<TPluginType extends BasePluginType = BasePluginType>
extends GlobalPluginBlueprint<GlobalPluginData<TPluginType>> {
configSchema: t.TypeC<any>;
configPreprocessor?: (options: PluginOptions<TPluginType>, strict?: boolean) => Awaitable<PluginOptions<TPluginType>>;
}
export function zeppelinGlobalPlugin<TBlueprint extends ZeppelinGlobalPluginBlueprint>(

View file

@ -111,7 +111,11 @@ export function validate(schema: t.Type<any>, value: any): StrictValidationError
* Decodes and validates the given value against the given schema while also disallowing extra properties
* See: https://github.com/gcanti/io-ts/issues/322
*/
export function decodeAndValidateStrict<T extends t.HasProps>(schema: T, value: any): StrictValidationError | any {
export function decodeAndValidateStrict<T extends t.HasProps>(
schema: T,
value: any,
debug = false,
): StrictValidationError | any {
const validationResult = t.exact(schema).decode(value);
return pipe(
validationResult,
@ -119,6 +123,14 @@ export function decodeAndValidateStrict<T extends t.HasProps>(schema: T, value:
err => report(validationResult),
result => {
// Make sure there are no extra properties
if (debug)
console.log(
"JSON.stringify() check:",
JSON.stringify(value) === JSON.stringify(result)
? "they are the same, no excess"
: "they are not the same, might have excess",
result,
);
if (JSON.stringify(value) !== JSON.stringify(result)) {
const diff = deepDiff(result, value);
const errors = diff.filter(d => d.kind === "N").map(d => `Unknown property <${d.path.join(".")}>`);