Commit 95f2960c by Andrey Golubov

Fix commit

parent 628f5576
import { Async, DecoderError } from '~/common/application/error';
import * as cr from '~/common/config-reader';
import * as eff from '~/common/eff';
import * as either from '~/common/either';
import { configDecoder, infoDecoder, presetKeyDecoder } from '~/config';
import { configDecoder, infoDecoder, presetKeyDecoder } from '../../../config';
import * as cr from '../../config-reader';
import * as eff from '../../eff';
import * as either from '../../either';
import { Async, DecoderError } from '../error';
export type Config = typeof configDecoder['_A'];
export type Info = typeof infoDecoder['_A'];
......
import * as React from 'react';
import { Err } from './index';
export type Props = { error: Err };
export function View({ error }: Props) {
return (
<div>
<h2 style={{ textAlign: 'center', padding: 16, fontWeight: 500, fontSize: 22 }}>
{'Sorry, an error occurred :('}
</h2>
<p style={{ textAlign: 'center', padding: 16, fontSize: 16 }}>{JSON.stringify(error)}</p>
</div>
);
}
import { Problem } from '../decoder';
import { Eff } from '../eff';
import { Err as LocalStorageErr } from '../persistent';
import { literal } from '../types';
import { Problem } from '../../decoder';
import { Eff } from '../../eff';
import { Err as LocalStorageErr } from '../../persistent';
import { literal } from '../../types';
/**
* Data конструктор `UnexpectedError` ошибок
......
......@@ -33,8 +33,7 @@ export type Problem = {
decoder: Decoder<any>;
rootDecoder: Decoder<any>;
message: string;
}
};
/**
* Опции для `DecoderBase.prototype.validate`
......@@ -47,18 +46,17 @@ const defaultValidateOptions: ValidateOptions = {
printProblem: false,
};
// Базовый класс для наследования методов
export class DecoderBase<A> {
readonly _A: A;
static rootDecoder: Decoder<any>|undefined = undefined;
static rootValue: any = undefined;
static path: string[] = [];
public static path: string[] = [];
public static rootDecoder: Decoder<any>|undefined = undefined;
public static rootValue: any = undefined;
public readonly _A: A;
/**
* Печать декодера в виде выражения которым он был создан
*/
prettyPrint(): string {
public prettyPrint(): string {
const self = this as any as Decoder<A>;
return prettyPrint(self);
}
......@@ -66,18 +64,18 @@ export class DecoderBase<A> {
/**
* Валидация произвольного значения
*/
validate(value: any, options_?: Partial<ValidateOptions>): Either<Problem, A> {
public validate(value: any, options_?: Partial<ValidateOptions>): Either<Problem, A> {
const self = this as any as Decoder<A>;
let output: Either<Problem, A>;
if (DecoderBase.rootValue === undefined) {
DecoderBase.rootValue = value;
DecoderBase.rootDecoder = self;
output = doValidate(self, value)
output = doValidate(self, value);
DecoderBase.rootValue = undefined;
DecoderBase.rootDecoder = undefined;
} else {
output = doValidate(self, value)
output = doValidate(self, value);
}
// Печать проблемы в консоль если выставлена соответствующая опция
......@@ -90,43 +88,43 @@ export class DecoderBase<A> {
return output;
}
map<B>(f: (a: A) => B): Decoder<B> {
public map<B>(f: (a: A) => B): Decoder<B> {
const self = this as any as Decoder<A>;
return new Chain(self, x => x.fold(fail, x => of(f(x))));
return new Chain(self, x => x.fold(fail, y => of(f(y))));
}
mapTo<B>(value: B): Decoder<B> {
public mapTo<B>(value: B): Decoder<B> {
const self = this as any as Decoder<A>;
return new Chain(self, x => x.fold(fail, () => of(value)));
}
chain<B>(f: (a: A) => Decoder<B>): Decoder<B> {
public chain<B>(f: (a: A) => Decoder<B>): Decoder<B> {
const self = this as any as Decoder<A>;
return new Chain(self, x => x.fold(fail, f));
}
chainTo<B>(value: Decoder<B>): Decoder<B> {
public chainTo<B>(value: Decoder<B>): Decoder<B> {
const self = this as any as Decoder<A>;
return new Chain(self, x => x.fold(fail, () => value));
}
mapProblem(f: (a: Problem) => Problem): Decoder<A> {
public mapProblem(f: (a: string | Problem) => Problem): Decoder<A> {
const self = this as any as Decoder<A>;
return new Chain(self, x => x.fold(f, x => x));
return new Chain(self, x => x.fold(f, y => y));
}
mapProblemTo(value: Problem): Decoder<A> {
public mapProblemTo(value: Problem): Decoder<A> {
const self = this as any as Decoder<A>;
return new Chain(self, x => x.fold(() => value, x => x));
return new Chain(self, x => x.fold(() => value, y => y));
}
withDefault(defValue: A): Decoder<A>;
withDefault<B extends Expr>(defValue: B): Decoder<A|B>;
withDefault<B extends Expr>(defValue: B): Decoder<A|B> {
public withDefault(defValue: A): Decoder<A>;
public withDefault<B extends Expr>(defValue: B): Decoder<A|B>;
public withDefault<B extends Expr>(defValue: B): Decoder<A|B> {
return new WithDefault(this as any, defValue);
}
refine(pred: (a: A) => boolean): Decoder<A> {
public refine(pred: (a: A) => boolean): Decoder<A> {
const self = this as any as Decoder<A>;
return new Chain(self, ethr => {
switch (ethr.tag) {
......@@ -161,22 +159,23 @@ export class RecordDecoder<A> extends DecoderBase<A> {
readonly _description: Record<string, Decoder<any>>,
) { super(); }
extend<F extends { [K: string]: Decoder<any> }>(fields: F): RecordDecoder<A & { [K in keyof F]: F[K]['_A'] }> {
public 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>> {
public 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>> {
public omit<K extends keyof A>(...keys: K[]): RecordDecoder<Omit<A, K>> {
// tslint:disable-next-line:max-line-length
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> {
readonly tag: 'AtDecoder' = 'AtDecoder';
public readonly tag: 'AtDecoder' = 'AtDecoder';
constructor(
readonly _path: Array<string|number>,
......@@ -185,10 +184,10 @@ export class AtDecoder<A> extends DecoderBase<A> {
}
export class Primitive<A> extends DecoderBase<A> {
readonly tag: 'PrimitiveDecoder' = 'PrimitiveDecoder';
public readonly tag: 'PrimitiveDecoder' = 'PrimitiveDecoder';
constructor(
readonly _type: 'null'|'undefined'|'string'|'boolean'|'any'|'nat'|'int'|'float'
readonly _type: 'null'|'undefined'|'string'|'boolean'|'any'|'nat'|'int'|'float',
) { super(); }
}
......@@ -207,7 +206,7 @@ export class Chain<A> extends DecoderBase<A> {
export class OneOf<A> extends DecoderBase<A> {
constructor(
readonly _alternatives: Decoder<any>[],
readonly _alternatives: Array<Decoder<any>>,
) { super(); }
}
......@@ -219,7 +218,7 @@ export class Discriminate<A> extends DecoderBase<A> {
}
export abstract class ToDecoder<A> extends DecoderBase<A> {
abstract toDecoder(): Decoder<A>;
public abstract toDecoder(): Decoder<A>;
}
export class WithDefault<A> extends ToDecoder<A> {
......@@ -228,7 +227,7 @@ export class WithDefault<A> extends ToDecoder<A> {
readonly _default: A,
) { super(); }
toDecoder() {
public toDecoder() {
return new OneOf<A>([this._decoder, of(this._default)]);
}
}
......@@ -238,30 +237,28 @@ export class Variants<A> extends ToDecoder<A> {
readonly _variants: A[],
) { super(); }
toDecoder() {
public toDecoder() {
// tslint:disable-next-line:max-line-length
return new OneOf<A>(this._variants.map(x => decoder(v => v === x ? Either.of(v) : Either.failure(`expected ${x}, got ${fancyTypeOf(v)}`))));
}
}
}
/**
/**
* Результат валидаии
*/
export type Validation<A> = Either<Problem|string, A>;
/**
* Конструктир кастомных декодеров
*/
export function decoder<A>(validate: (value: any) => Either<Problem|string, A>): Custom<A>;
export function decoder<A>(name: string, validate: (value: any) => Either<Problem|string, A>): Custom<A>;
export function decoder(): any {
if (arguments.length === 1) return new Custom('custom', arguments[0]);
if (arguments.length === 2) return new Custom(arguments[0], arguments[1]);
if (arguments.length === 1) { return new Custom('custom', arguments[0]); }
if (arguments.length === 2) { return new Custom(arguments[0], arguments[1]); }
throw new TypeError(`decoder: invalid number of arguments`);
}
/**
* Алиас для `x => new PureDecoder(Either.of(x))`
*/
......@@ -269,7 +266,6 @@ export function of<A extends Expr>(a: A): Pure<A> {
return new Pure(Either.of(a));
}
/**
* Алиас для `x => new PureDecoder(Either.failure(x))`
*/
......@@ -277,29 +273,35 @@ export function fail(x: Problem|string): Decoder<never> {
return new Pure(Either.failure(x));
}
/**
/**
* Аппликативный комбинатор
* TODO: сделать отдельным вариантом в ADT для реализации `prettyPrint`
*/
export function ap<A,B>(a: Decoder<A>, f:(a: A) => B): Custom<B>;
export function ap<A,B,C>(a: Decoder<A>, b: Decoder<B>, f:(a: A, b: B) => C):Custom<C>;
export function ap<A,B,C,D>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, f:(a: A, b: B, c: C) => D):Custom<D>;
export function ap<A,B,C,D,E>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, f:(a: A, b: B, c: C, d: D) => E):Custom<E>;
export function ap<A,B,C,D,E,F>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f:(a: A, b: B, c: C, d: D, e: E) => F):Custom<F>;
export function ap<A,B,C,D,E,F,G>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_:Decoder<F>, f:(a: A, b: B, c: C, d: D, e: E, f: F) => G):Custom<G>;
export function ap<A,B,C,D,E,F,G,H>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_:Decoder<F>, g: Decoder<G>, f:(a: A, b: B, c: C, d: D, e: E, f: F, g: G) => H):Custom<H>;
export function ap<A,B,C,D,E,F,G,H,J>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_:Decoder<F>, g: Decoder<G>, h: Decoder<H>, f:(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => J):Custom<J>;
export function ap<A,B,C,D,E,F,G,H,J,K>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_:Decoder<F>, g: Decoder<G>, h: Decoder<H>, j: Decoder<J>, f:(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, j: J) => K):Custom<K>;
export function ap<A,B,C,D,E,F,G,H,J,K,L>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_:Decoder<F>, g: Decoder<G>, h: Decoder<H>, j: Decoder<J>, k: Decoder<K>, f:(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, j: J, k: K) => L):Custom<L>;
export function ap<A, B>(a: Decoder<A>, f: (a: A) => B): Custom<B>;
export function ap<A, B, C>(a: Decoder<A>, b: Decoder<B>, f: (a: A, b: B) => C): Custom<C>;
export function ap<A, B, C, D>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, f: (a: A, b: B, c: C) => D): Custom<D>;
// tslint:disable-next-line:max-line-length
export function ap<A, B, C, D, E>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, f: (a: A, b: B, c: C, d: D) => E): Custom<E>;
// tslint:disable-next-line:max-line-length
export function ap<A, B, C, D, E, F>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: (a: A, b: B, c: C, d: D, e: E) => F): Custom<F>;
// tslint:disable-next-line:max-line-length
export function ap<A, B, C, D, E, F, G>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_: Decoder<F>, f: (a: A, b: B, c: C, d: D, e: E, f: F) => G): Custom<G>;
// tslint:disable-next-line:max-line-length
export function ap<A, B, C, D, E, F, G, H>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_: Decoder<F>, g: Decoder<G>, f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => H): Custom<H>;
// tslint:disable-next-line:max-line-length
export function ap<A, B, C, D, E, F, G, H, J>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_: Decoder<F>, g: Decoder<G>, h: Decoder<H>, f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => J): Custom<J>;
// tslint:disable-next-line:max-line-length
export function ap<A, B, C, D, E, F, G, H, J, K>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_: Decoder<F>, g: Decoder<G>, h: Decoder<H>, j: Decoder<J>, f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, j: J) => K): Custom<K>;
// tslint:disable-next-line:max-line-length
export function ap<A, B, C, D, E, F, G, H, J, K, L>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f_: Decoder<F>, g: Decoder<G>, h: Decoder<H>, j: Decoder<J>, k: Decoder<K>, f: (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, j: J, k: K) => L): Custom<L>;
export function ap(...args: Array<Decoder<any> | Function>): Decoder<any> {
return decoder('ap', (val) => {
const func = args[args.length - 1] as Function;
const results: Array<any> = [];
const results: any[] = [];
for (let i = 0; i < args.length - 1; i++) {
const dec = args[i] as Decoder<any>;
const ethr = dec.validate(val);
switch(ethr.tag) {
switch (ethr.tag) {
case 'Left': return ethr;
case 'Right': results.push(ethr.value); break;
}
......@@ -308,7 +310,6 @@ export function ap(...args: Array<Decoder<any> | Function>): Decoder<any> {
});
}
// Примитивы
const anyDecoder = new Primitive<any>('any');
const stringDecoder = new Primitive<string>('string');
......@@ -319,23 +320,21 @@ export const nat = new Primitive<number>('nat');
export const int = new Primitive<number>('int');
export const float = new Primitive<number>('float');
// Экспорт с переименованием
// tslint:disable-next-line:max-line-length
export { anyDecoder as any, stringDecoder as string, booleanDecoder as boolean, nullDecoder as null, undefinedDecoder as undefined };
/**
* Сопоставление с несколькими декодерами до первого успешного
* сравнвния
*/
export function oneOf<XS extends Decoder<any>[]>(...array: XS): OneOf<XS[number]['_A']>;
export function oneOf<XS extends Decoder<any>[]>(array: XS): OneOf<XS[number]['_A']>;
export function oneOf<XS extends Array<Decoder<any>>>(...array: XS): OneOf<XS[number]['_A']>;
export function oneOf<XS extends Array<Decoder<any>>>(array: XS): OneOf<XS[number]['_A']>;
export function oneOf(): OneOf<any> {
const decoders = Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.call(arguments);
return new OneOf(decoders);
}
export function array<A>(decoder: Decoder<A>): Decoder<A[]> {
return new ArrayDecoder(decoder);
}
......@@ -353,13 +352,12 @@ export function at<A>(path: string|string[]|number|number[], decoder: Decoder<A>
}
export const date: Decoder<Date> = decoder('date', (value) => {
if (typeof (value) !== 'string') return Either.failure('not a string');
if (typeof (value) !== 'string') { return Either.failure('not a string'); }
const d = new Date(value);
return isNaN(d.getTime()) ? Either.failure('error parsing date from string') : Either.of(d);
});
/**
/**
* Хелпер для объявления декодеров в стиле Elm. Используется вместе в
* `ap`
*/
......@@ -367,7 +365,6 @@ export function required<A>(key: string|string[], dec: Decoder<A>): Decoder<A> {
return at(key, dec);
}
/**
* Декодер для дискриминированных объединений
* ```ts
......@@ -378,25 +375,25 @@ export function required<A>(key: string|string[], dec: Decoder<A>): Decoder<A> {
* // => Decoder<{ { type: 'director', ... } | { type: 'employee', ... }}>
* ```
*/
// tslint:disable-next-line:max-line-length
export type DiscriminateOn<TagKey extends string, Descriptor extends Record<string, Decoder<any>>> = Discriminate<{ [K in keyof Descriptor]: { [K2 in TagKey]: K } & Descriptor[K]['_A']}[keyof Descriptor]>;
// tslint:disable-next-line:max-line-length
export function discriminate<TagKey extends string, Descriptor extends Record<string, Decoder<any>>>(key: TagKey, record: Descriptor): DiscriminateOn<TagKey, Descriptor> {
return new Discriminate(key, record);
}
/**
/**
* Хелпер для объявления декодеров в стиле Elm. Используется вместе в
* `ap`
*/
export function optional<A>(key: string|string[], dec: Decoder<A>, def: A): Decoder<A>;
export function optional<A,B>(key: string|string[], dec: Decoder<A>, def: B): Decoder<A|B>;
export function optional<A, B>(key: string|string[], dec: Decoder<A>, def: B): Decoder<A|B>;
export function optional(key: string|string[], dec: Decoder<any>, def: any): Decoder<any> {
return required(key, dec).withDefault(def);
}
/**
* Создание декодера перечислением всех допустимых значений
/**
* Создание декодера перечислением всех допустимых значений
*/
export function variants<A extends Expr[]>(array: A): Variants<A[number]>;
export function variants<A extends Expr[]>(...array: A): Variants<A[number]>;
......@@ -407,7 +404,6 @@ export function variants(): Variants<any> {
// @deprecated
export const literals = variants;
/**
* Кортежи разных размеров. Проверяемое значение необязательно должно
* быть массивом
......@@ -417,18 +413,17 @@ export const literals = variants;
* ```
*/
// @ts-ignore
export function tuple<A extends Decoder<any>[]>(...reps: A): Decoder<{ [K in keyof A]: A[K]['_A'] }>;
export function tuple(...args): Decoder<any> {
export function tuple<A extends Array<Decoder<any>>>(...reps: A): Decoder<{ [K in keyof A]: A[K]['_A'] }>;
export function tuple(...args: any[]): Decoder<any> {
// @ts-ignore
return ap(...args.map((decoder, idx) => at(idx, decoder)), (...xs) => xs);
}
/**
/**
* Печать проблемы с консоль
*/
export function printProblems(problems: Problem): void {
console.log(`%cValidation error: ${problems.message}`, 'color: #f00; font-size: 16px; font-weight: 600;')
console.log(`%cValidation error: ${problems.message}`, 'color: #f00; font-size: 16px; font-weight: 600;');
console.log('%cfailed decoder:', 'font-weight: 600;', problems.decoder.prettyPrint());
console.log('%croot decoder:', 'font-weight: 600;', problems.rootDecoder.prettyPrint());
console.log('%cfailed value:', 'font-weight: 600;', problems.value);
......@@ -436,35 +431,33 @@ export function printProblems(problems: Problem): void {
console.log('%cproblem path:', 'font-weight: 600;', ['_', ...problems.path].join('.'));
}
// Хелпер
function fancyTypeOf(value: any): string {
return Object.prototype.toString.call(value);
}
/**
* Выполнение валидации
*/
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) {
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'));
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) {
switch (ethr.tag) {
case 'Left': { return ethr; }
case 'Right': output.push(ethr.value); break;
}
......@@ -474,15 +467,15 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
}
if (decoder instanceof Dict) {
if (typeof (value) !== 'object') return Either.failure(projectProblem('not an object'));
if (value === null) return Either.failure(projectProblem('found null'));
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;
for (const 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) {
switch (ethr.tag) {
case 'Left': { return ethr; }
case 'Right': output[key] = ethr.value; break;
}
......@@ -492,15 +485,15 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
}
if (decoder instanceof RecordDecoder) {
if (typeof (value) !== 'object') return Either.failure(projectProblem('not an object'));
if (value === null) return Either.failure(projectProblem('found null'));
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) {
for (const key in decoder._description) {
DecoderBase.path[DecoderBase.path.length - 1] = key;
if (!decoder._description.hasOwnProperty(key)) continue;
if (!decoder._description.hasOwnProperty(key)) { continue; }
const ethr = doValidate(decoder._description[key], value[key]);
switch(ethr.tag) {
switch (ethr.tag) {
case 'Left': { return ethr; }
case 'Right': output[key] = ethr.value; break;
}
......@@ -512,7 +505,7 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
if (decoder instanceof AtDecoder) {
let iter = value as any;
decoder._path.forEach(k => DecoderBase.path.push(k + ''));
for (let i in decoder._path) {
for (const i in decoder._path) {
if (iter === undefined || !iter.hasOwnProperty(decoder._path[i])) {
iter = undefined;
break;
......@@ -522,7 +515,7 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
cleanup();
return doValidate(decoder._decoder, iter);
}
if (decoder instanceof Primitive) {
switch (decoder._type) {
case 'null': return value === null ? Either.of(value as A) : Either.failure(projectProblem(`expected null, got ${fancyTypeOf(value)}`));
......@@ -530,8 +523,8 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
case 'string': return typeof(value) === 'string' ? Either.of(value as any) : Either.failure(projectProblem(`expected a string, got ${fancyTypeOf(value)}`));
case 'boolean': return typeof(value) === 'boolean' ? Either.of(value as any) : Either.failure(projectProblem(`expected a boolean, got ${fancyTypeOf(value)}`));
case 'any': return Either.of(value as A);
case 'nat': return typeof (value) !== 'number' ? Either.failure(projectProblem('not a number')) : (value|0) === value && value >= 0 ? Either.of(value as any) : Either.failure(projectProblem('not a natural number'));
case 'int': return typeof (value) !== 'number' ? Either.failure(projectProblem('not a number')) : (value|0) === value ? Either.of(value as any) : Either.failure(projectProblem('not an integer'))
case 'nat': return typeof (value) !== 'number' ? Either.failure(projectProblem('not a number')) : (value | 0) === value && value >= 0 ? Either.of(value as any) : Either.failure(projectProblem('not a natural number'));
case 'int': return typeof (value) !== 'number' ? Either.failure(projectProblem('not a number')) : (value | 0) === value ? Either.of(value as any) : Either.failure(projectProblem('not an integer'));
case 'float': return typeof (value) !== 'number' ? Either.failure(projectProblem('not a number')) : Either.of(value as any);
}
return absurd(decoder._type);
......@@ -540,11 +533,11 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
if (decoder instanceof Pure) {
return decoder._value.mapLeft(projectProblem);
}
if (decoder instanceof OneOf) {
for (const d of decoder._alternatives) {
const ethr = doValidate(d, value);
switch(ethr.tag) {
switch (ethr.tag) {
case 'Left': break;
case 'Right': return ethr;
}
......@@ -562,9 +555,9 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
if (decoder instanceof Discriminate) {
const key = decoder._discriminator;
const record = decoder._alternatives;
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]}`));
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]}`)); }
const output = record[value[key]].validate(value).map(x => ({ ...x, [key]: value[key] }));
cleanup();
return output;
......@@ -577,12 +570,11 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
return absurd(decoder);
function projectProblem(problem: Problem|string): Problem {
if (typeof(problem) !== 'string') return problem;
if (typeof(problem) !== 'string') { return problem; }
return { value, rootValue: DecoderBase.rootValue, decoder, rootDecoder: DecoderBase.rootDecoder || decoder, message: problem, path: DecoderBase.path };
}
}
}
/**
* Печать декодера.
* ```ts
......@@ -591,19 +583,19 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
* ```
*/
export function prettyPrint(decoder: Decoder<any>): string {
if (decoder instanceof Custom) return `t.decoder(${JSON.stringify(decoder._name)}, <func>)`;
if (decoder instanceof ArrayDecoder) return `t.array(${decoder._decoder.prettyPrint()})`;
if (decoder instanceof Dict) return `t.dict(${decoder._decoder.prettyPrint()})`;
if (decoder instanceof RecordDecoder) return `t.record({ ${Object.keys(decoder._description).map(k => decoder._description[k].prettyPrint()).join(', ')} })`;
if (decoder instanceof AtDecoder) return `t.at(${JSON.stringify(decoder._path)}, ${decoder._decoder.prettyPrint()})`;
if (decoder instanceof Primitive) return `t.${decoder._type}`;
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 Custom) { return `t.decoder(${JSON.stringify(decoder._name)}, <func>)`; }
if (decoder instanceof ArrayDecoder) { return `t.array(${decoder._decoder.prettyPrint()})`; }
if (decoder instanceof Dict) { return `t.dict(${decoder._decoder.prettyPrint()})`; }
if (decoder instanceof RecordDecoder) { return `t.record({ ${Object.keys(decoder._description).map(k => decoder._description[k].prettyPrint()).join(', ')} })`; }
if (decoder instanceof AtDecoder) { return `t.at(${JSON.stringify(decoder._path)}, ${decoder._decoder.prettyPrint()})`; }
if (decoder instanceof Primitive) { return `t.${decoder._type}`; }
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(', ');
const alternatives = Object.keys(decoder._alternatives).map(k => JSON.stringify(k) + ': ' + decoder._alternatives[k].prettyPrint()).join(', ');
return `t.discriminateOn(${JSON.stringify(discriminator)}, ${alternatives})`;
}
if (decoder instanceof ToDecoder) {
......@@ -612,7 +604,6 @@ export function prettyPrint(decoder: Decoder<any>): string {
return absurd(decoder);
}
// Utilies types based on
// https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-307871458
// 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