Commit f1b9f7f2 by Vladislav Lagunov

Печать названия проблемных полей в printProblem

parent 4c6c4a99
......@@ -84,15 +84,6 @@ export function fromString(d: Decoder<any>, value: string): unknown {
return value.split(', ').map(x => fromString(d._decoder, x));
}
// if (d instanceof t.Tuple) {
// return value.split(', ').map((x, idx) => fromString(d._tuple[idx], x));
// }
// if (d instanceof t.Literals) {
// // TODO: проверять значение исходя из типа rep._literals
// return value;
// }
if (d instanceof t.Dict) {
try {
return JSON.parse(value);
......@@ -141,6 +132,9 @@ export function fromString(d: Decoder<any>, value: string): unknown {
if (d instanceof WithDefault) {
return fromString(d._decoder, value);
}
if (d instanceof t.Variants) {
return value;
}
return fromString(d.toDecoder(), value);
}
......@@ -225,6 +219,10 @@ export function configInfo(decoder: RecordDecoder<any>, prefix=''): Record<strin
info.default = d._default;
return info;
}
if (d instanceof t.Variants) {
return { type: d._variants.map(x => JSON.stringify(x)).join('|') };
}
const info = go(d.toDecoder());
if (d instanceof ConfigDescription) {
......
......@@ -20,6 +20,7 @@ export type Decoder<A> =
| OneOf<A>
| Discriminate<A>
| ToDecoder<A>
;
/**
......@@ -27,6 +28,7 @@ export type Decoder<A> =
*/
export type Problem = {
value: any;
path: string[];
rootValue: any;
decoder: Decoder<any>;
rootDecoder: Decoder<any>;
......@@ -51,6 +53,7 @@ export class DecoderBase<A> {
readonly _A: A;
static rootDecoder: Decoder<any>|undefined = undefined;
static rootValue: any = undefined;
static path: string[] = [];
/**
* Печать декодера в виде выражения которым он был создан
......@@ -161,6 +164,15 @@ export class RecordDecoder<A> extends DecoderBase<A> {
extend<F extends { [K: string]: Decoder<any> }>(fields: F): RecordDecoder<A & { [K in keyof F]: F[K]['_A'] }> {
return new RecordDecoder({ ...this._description, ...fields as any });
}
pick<K extends keyof A>(...keys: K[]): RecordDecoder<Pick<A, K>> {
return new RecordDecoder(keys.reduce<any>((acc, k) => (acc[k] = this._description[k as string], acc), {}));
}
omit<K extends keyof A>(...keys: K[]): RecordDecoder<Omit<A, K>> {
const description = Object.keys(this._description).reduce((acc, k) => (keys.indexOf(k as any) === -1 && (acc[k] = this._description[k]), acc), {});
return new RecordDecoder(description);
}
}
export class AtDecoder<A> extends DecoderBase<A> {
......@@ -221,6 +233,16 @@ export class WithDefault<A> extends ToDecoder<A> {
}
}
export class Variants<A> extends ToDecoder<A> {
constructor(
readonly _variants: A[],
) { super(); }
toDecoder() {
return new OneOf<A>(this._variants.map(x => decoder(v => v === x ? Either.of(v) : Either.failure(`expected ${x}, got ${fancyTypeOf(v)}`))));
}
}
/**
* Результат валидаии
......@@ -381,13 +403,15 @@ export function optional(key: string|string[], dec: Decoder<any>, def: any): Dec
/**
* Создание декодера перечислением всех допустимых значений
*/
export function literals<A extends Expr[]>(...array: A): Decoder<A[number]>;
export function literals<A extends Expr[]>(array: A): Decoder<A[number]>;
export function literals(): Decoder<any> {
const literals: Expr[] = Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.call(arguments);
return new OneOf(literals.map(x => decoder(v => v === x ? Either.of(v) : Either.failure(`expected ${x}, got ${fancyTypeOf(v)}`))));
export function variants<A extends Expr[]>(...array: A): Variants<A[number]>;
export function variants<A extends Expr[]>(array: A): Variants<A[number]>;
export function variants(): Variants<any> {
return new Variants(Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.call(arguments));
}
// @deprecated
export const literals = variants;
/**
* Кортежи разных размеров. Проверяемое значение необязательно должно
......@@ -414,7 +438,7 @@ export function printProblems(problems: Problem): void {
console.log('%croot decoder:', 'font-weight: 600;', problems.rootDecoder.prettyPrint());
console.log('%cfailed value:', 'font-weight: 600;', problems.value);
console.log('%croot value:', 'font-weight: 600;', problems.rootValue);
//console.log('decoders ', problems.decoders.map(x => x.prettyPrint()));
console.log('%cproblem path:', 'font-weight: 600;', ['_', ...problems.path].join('.'));
}
......@@ -428,20 +452,29 @@ function fancyTypeOf(value: any): string {
* Выполнение валидации
*/
export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Problem, A> {
const pathLength = DecoderBase.path.length;
const cleanup = () => DecoderBase.path.splice(pathLength);
if (decoder instanceof Custom) {
return decoder._validate(value).mapLeft(projectProblem);
DecoderBase.path.push('<custom>');
const output = decoder._validate(value).mapLeft(projectProblem);
cleanup();
return output;
}
if (decoder instanceof ArrayDecoder) {
const output: any[] = [];
if (!Array.isArray(value)) return Either.failure(projectProblem('not an array'));
DecoderBase.path.push('0');
for (let i = 0; i < value.length; i++) {
DecoderBase.path[DecoderBase.path.length - 1] = String(i);
const ethr = doValidate(decoder._decoder, value[i]);
switch(ethr.tag) {
case 'Left': { return ethr; }
case 'Right': output.push(ethr.value); break;
}
}
cleanup();
return Either.of(output as any as A);
}
......@@ -449,14 +482,17 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
if (typeof (value) !== 'object') return Either.failure(projectProblem('not an object'));
if (value === null) return Either.failure(projectProblem('found null'));
const output: { [k: string]: A } = {};
DecoderBase.path.push('');
for (let key in value) {
if (!value.hasOwnProperty(key)) continue;
DecoderBase.path[DecoderBase.path.length - 1] = key;
const ethr = doValidate(decoder._decoder, value[key]);
switch(ethr.tag) {
case 'Left': { return ethr; }
case 'Right': output[key] = ethr.value; break;
}
}
cleanup();
return Either.of(output as any as A);
}
......@@ -464,7 +500,9 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
if (typeof (value) !== 'object') return Either.failure(projectProblem('not an object'));
if (value === null) return Either.failure(projectProblem('found null'));
const output: { [k: string]: any } = {};
DecoderBase.path.push('');
for (let key in decoder._description) {
DecoderBase.path[DecoderBase.path.length - 1] = key;
if (!decoder._description.hasOwnProperty(key)) continue;
const ethr = doValidate(decoder._description[key], value[key]);
switch(ethr.tag) {
......@@ -472,11 +510,13 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
case 'Right': output[key] = ethr.value; break;
}
}
cleanup();
return Either.of(output as any as A);
}
if (decoder instanceof AtDecoder) {
let iter = value as any;
decoder._path.forEach(k => DecoderBase.path.push(k + ''));
for (let i in decoder._path) {
if (iter === undefined || !iter.hasOwnProperty(decoder._path[i])) {
iter = undefined;
......@@ -484,6 +524,7 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
}
iter = iter[decoder._path[i]];
}
cleanup();
return doValidate(decoder._decoder, iter);
}
......@@ -517,7 +558,10 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
}
if (decoder instanceof Chain) {
return doValidate(decoder._then(doValidate(decoder._decoder, value)), value);
DecoderBase.path.push('<chain>');
const output = doValidate(decoder._then(doValidate(decoder._decoder, value)), value);
cleanup();
return output;
}
if (decoder instanceof Discriminate) {
......@@ -526,7 +570,9 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
if (typeof(value) !== 'object' || !value) return Either.failure(projectProblem('expected an object'));
if (!value.hasOwnProperty(key)) return Either.failure(projectProblem(`expected input would have key '${key}'`));
if (!(value[key] in record)) return Either.failure(projectProblem(`unknown value of discriminated key ${value[key]}`));
return record[value[key]].validate(value).map(x => ({ ...x, [key]: value[key] }));
const output = record[value[key]].validate(value).map(x => ({ ...x, [key]: value[key] }));
cleanup();
return output;
}
if (decoder instanceof ToDecoder) {
......@@ -537,7 +583,7 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
function projectProblem(problem: Problem|string): Problem {
if (typeof(problem) !== 'string') return problem;
return { value, rootValue: DecoderBase.rootValue, decoder, rootDecoder: DecoderBase.rootDecoder || decoder, message: problem, };
return { value, rootValue: DecoderBase.rootValue, decoder, rootDecoder: DecoderBase.rootDecoder || decoder, message: problem, path: DecoderBase.path };
}
}
......@@ -559,6 +605,7 @@ export function prettyPrint(decoder: Decoder<any>): string {
if (decoder instanceof Pure) return `t.of(${JSON.stringify(decoder._value)})`;
if (decoder instanceof Chain) return `${decoder.prettyPrint()}.chain(<func>)`;
if (decoder instanceof OneOf) return `t.oneOf(${decoder._alternatives.map(x => x.prettyPrint()).join(', ')})`;
if (decoder instanceof Variants) return `t.literals(${decoder._variants.map(x => JSON.stringify(x)).join(', ')})`;
if (decoder instanceof Discriminate) {
const discriminator = JSON.stringify(decoder._discriminator);
const alternatives = Object.keys(decoder._alternatives).map(k => JSON.stringify(k) + ": " + decoder._alternatives[k].prettyPrint()).join(', ');
......@@ -570,3 +617,7 @@ export function prettyPrint(decoder: Decoder<any>): string {
return absurd(decoder);
}
// Utilies types based on
// https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458
export type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment