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

Configs are not decoded as well as validated by io-ts. Improvements to config validation, error messages, and TSafeRegex type.

This commit is contained in:
Dragory 2019-08-04 15:44:41 +03:00
parent 2163d43dd1
commit 242bfe2b39
5 changed files with 223 additions and 74 deletions

View file

@ -5,14 +5,23 @@ import { noop } from "./utils";
import deepDiff from "deep-diff";
import safeRegex from "safe-regex";
export const TSafeRegexString = new t.Type(
"TSafeRegexString",
(s): s is string => typeof s === "string",
const regexWithFlags = /^\/(.*?)\/([i]*)$/;
/**
* The TSafeRegex type supports two syntaxes for regexes: /<regex>/<flags> and just <regex>
* The value is then checked for "catastrophic exponential-time regular expressions" by
* https://www.npmjs.com/package/safe-regex
*/
export const TSafeRegex = new t.Type<RegExp, string>(
"TSafeRegex",
(s): s is RegExp => s instanceof RegExp,
(from, to) =>
either.chain(t.string.validate(from, to), s => {
return safeRegex(s) ? t.success(s) : t.failure(from, to, "Unsafe regex");
const advancedSyntaxMatch = s.match(regexWithFlags);
const [regexStr, flags] = advancedSyntaxMatch ? [advancedSyntaxMatch[1], advancedSyntaxMatch[2]] : [s, ""];
return safeRegex(regexStr) ? t.success(new RegExp(regexStr, flags)) : t.failure(from, to, "Unsafe regex");
}),
s => s,
s => `/${s.source}/${s.flags}`,
);
// From io-ts/lib/PathReporter
@ -42,24 +51,38 @@ function getContextPath(context) {
}
// tslint:enable
const report = fold((errors: any) => {
return errors.map(err => {
if (err.message) return err.message;
export class StrictValidationError extends Error {
private errors;
constructor(errors: string[]) {
errors = Array.from(new Set(errors));
super(errors.join("\n"));
this.errors = errors;
}
getErrors() {
return this.errors;
}
}
const report = fold((errors: any): StrictValidationError | void => {
const errorStrings = errors.map(err => {
const context = err.context.map(c => c.key).filter(k => k && !k.startsWith("{"));
if (context.length > 0 && !isNaN(context[context.length - 1])) context.splice(-1);
while (context.length > 0 && !isNaN(context[context.length - 1])) context.splice(-1);
const value = stringify(err.value);
return value === undefined
? `<${context.join("/")}> is required`
: `Invalid value supplied to <${context.join("/")}>`;
: `Invalid value supplied to <${context.join("/")}>${err.message ? `: ${err.message}` : ""}`;
});
return new StrictValidationError(errorStrings);
}, noop);
/**
* Validates the given value against the given schema while also disallowing extra properties
* 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 validateStrict(schema: t.HasProps, value: any): string[] | null {
export function decodeAndValidateStrict(schema: t.HasProps, value: any): StrictValidationError | any {
const validationResult = t.exact(schema).decode(value);
return pipe(
validationResult,
@ -70,10 +93,10 @@ export function validateStrict(schema: t.HasProps, value: any): string[] | null
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(".")}>`);
return errors.length ? errors : ["Found unknown properties"];
if (errors.length) return new StrictValidationError(errors);
}
return null;
return result;
},
),
);