Commit 95f2960c by Andrey Golubov

Fix commit

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