Commit 444c887c by Vladislav Lagunov

Merge remote-tracking branch 'origin/develop'

parents 02a5ab2d 6edd1d06
import * as t from '../decoder';
import { camelCase } from 'lodash';
import { absurd } from '../types';
import { WithDefault, RecordDecoder, Decoder } from '../decoder';
import { Either } from '../either';
/**
......@@ -33,12 +35,12 @@ export function concat(...sources: ConfigSource[]): ConfigSource {
}
export function validate<A>(config: ConfigSource, decoder: t.RecordDecoder<A>) {
export function validate<A>(config: ConfigSource, decoder: RecordDecoder<A>) {
return decoder.validate(merge(decoder, config));
}
export function merge<A>(decoder: t.RecordDecoder<A>, ...srcs: ConfigSource[]): object {
export function merge<A>(decoder: RecordDecoder<A>, ...srcs: ConfigSource[]): object {
const source = srcs.length === 1 ? srcs[0] : concat(...srcs);
if (source.tag === 'Cli') {
const value: Record<string, any> = {};
......@@ -47,8 +49,8 @@ export function merge<A>(decoder: t.RecordDecoder<A>, ...srcs: ConfigSource[]):
const rest = source.args[i].substr(prefixLen);
const [k, v] = rest.indexOf('=') === -1 ? [rest, source.args[++i] || ''] : source.args[i].split('=');
// Проверка того что это известно
if (!decoder.description.hasOwnProperty(k)) continue;
value[k] = decoderFromString(decoder.description[k], v);
if (!decoder._description.hasOwnProperty(k)) continue;
value[k] = fromString(decoder._description[k], v);
}
return value;
}
......@@ -57,8 +59,8 @@ export function merge<A>(decoder: t.RecordDecoder<A>, ...srcs: ConfigSource[]):
const substring = source.value[0] === '?' ? source.value.substr(1) : source.value;
const value = substring.split('&').map(x => x.split('=')).reduce((acc, [k, v]: any) => {
const key = source.transformKey(decodeURIComponent(k));
if (!decoder.description.hasOwnProperty(key)) return acc;
acc[key] = decoderFromString(decoder.description[key], decodeURIComponent(v));
if (!decoder._description.hasOwnProperty(key)) return acc;
acc[key] = fromString(decoder._description[key], decodeURIComponent(v));
return acc;
}, {});
return value;
......@@ -77,16 +79,12 @@ export function merge<A>(decoder: t.RecordDecoder<A>, ...srcs: ConfigSource[]):
}
export function decoderFromString(decoder: t.Decoder<any>, value: string): any {
if (decoder instanceof t.CustomDecoder) {
throw new Error('[config-reader] CustomDecoder is not supported');
export function fromString(d: Decoder<any>, value: string): unknown {
if (d instanceof t.ArrayDecoder) {
return value.split(', ').map(x => fromString(d._decoder, x));
}
if (decoder instanceof t.ArrayDecoder) {
return value.split(', ').map(x => decoderFromString(decoder.decoder, x));
}
if (decoder instanceof t.DictionaryDecoder) {
if (d instanceof t.Dict) {
try {
return JSON.parse(value);
} catch(e) {
......@@ -94,61 +92,173 @@ export function decoderFromString(decoder: t.Decoder<any>, value: string): any {
}
}
if (decoder instanceof t.RecordDecoder) {
if (d instanceof t.RecordDecoder) {
try {
return JSON.parse(value);
} catch(e) {
return value;
}
}
if (d instanceof t.Primitive) {
if (d._type === 'string') return value;
if (d._type === 'boolean') return !(value === 'false' || value === '0');
if (d._type === 'undefined') return undefined;
if (d._type === 'null') return null;
if (d._type === 'any') return value;
if (d._type === 'nat') return Number(value);
if (d._type === 'int') return Number(value);
if (d._type === 'float') return Number(value);
return absurd(d._type);
}
if (d instanceof t.OneOf) {
throw new Error('[config-reader] OneOf is not supported');
}
if (decoder instanceof t.AtDecoder) {
if (d instanceof t.Discriminate) {
try {
return JSON.parse(value);
} catch(e) {
return value;
}
}
if (decoder instanceof t.PrimitiveDecoder) {
if (decoder.primitive === 'string') return value;
if (decoder.primitive === 'boolean') return !(value === 'false' || value === '0');
if (decoder.primitive === 'undefined') return undefined;
if (decoder.primitive === 'null') return null;
if (decoder.primitive === 'any') return value;
if (decoder.primitive === 'nat') return Number(value);
if (decoder.primitive === 'int') return Number(value);
if (decoder.primitive === 'float') return Number(value);
return absurd(decoder.primitive);
}
if (decoder instanceof t.PureDecoder) {
if (d instanceof t.Pure) {
return value;
}
if (decoder instanceof t.ChainDecoder) {
throw new Error('[config-reader] ChainDecoder is not supported');
if (d instanceof t.ToDecoder) {
if (d instanceof WithDefault) {
return fromString(d._decoder, value);
}
if (d instanceof t.Variants) {
return value;
}
return fromString(d.toDecoder(), value);
}
if (decoder instanceof t.OneOfDecoder) {
throw new Error('[config-reader] OneOfDecoder is not supported');
if (d instanceof t.Custom) {
throw new Error('[config-reader]: CustomDecoder is not supported');
}
if (decoder instanceof t.DiscriminatedDecoder) {
try {
return JSON.parse(value);
} catch(e) {
return value;
}
if (d instanceof t.AtDecoder) {
throw new Error('[config-reader]: AtDecoder is not supported');
}
if (d instanceof t.Chain) {
throw new Error('[config-reader]: ChainDecoder is not supported');
}
if (decoder instanceof t.HasDecoder) {
if (decoder instanceof t.WithDefaultDecoder) {
return decoderFromString(decoder.decoder, value);
return absurd(d);
}
export class ConfigDescription<A> extends t.ToDecoder<A> {
constructor(
readonly _decoder: Decoder<A>,
readonly _description: string,
) { super(); }
toDecoder() {
return this._decoder;
}
}
// Информация о cli параметрe
export type InfoItem = { type: string; default?: unknown; description?: string };
export function configInfo(decoder: RecordDecoder<any>, prefix=''): Record<string, InfoItem> {
return Object.keys(decoder._description).reduce((acc, k) => (acc[k] = go(decoder._description[k]), acc), {});
function go(d: Decoder<any>): InfoItem {
if (d instanceof t.ArrayDecoder) {
const { type } = go(d._decoder);
return { type: 'Array<' + type + '>' };
}
if (d instanceof t.Dict) {
const { type } = go(d._decoder);
return { type: 'Record<string, ' + type + '>' };
}
return decoderFromString(decoder.toDecoder(), value);
if (d instanceof t.RecordDecoder) {
// TODO: печать полей
return { type: 'Record<string, unknown>' };
}
if (d instanceof t.Primitive) {
if (d._type === 'string') return { type: 'string' };
if (d._type === 'boolean') return { type: 'boolean' };
if (d._type === 'undefined') return { type: 'undefined' };
if (d._type === 'null') return { type: 'null' };
if (d._type === 'any') return { type: 'any' };
if (d._type === 'nat') return { type: 'number' };
if (d._type === 'int') return { type: 'number' };
if (d._type === 'float') return { type: 'number' };
return absurd(d._type);
}
if (d instanceof t.OneOf) {
return { type: d._alternatives.map(go).map(x => x.type).join('|') };
}
if (d instanceof t.Discriminate) {
// TODO: печать дискриминаторов
return { type: Object.keys(d._alternatives).map(k => go(d._alternatives[k]).type).join('|') };
}
if (d instanceof t.Pure) {
return { type: JSON.stringify(d._value) };
}
if (d instanceof t.ToDecoder) {
if (d instanceof WithDefault) {
const info = go(d._decoder);
info.default = d._default;
return info;
}
if (d instanceof t.Variants) {
return { type: d._variants.map(x => typeof(x) === 'string' ? x : JSON.stringify(x)).join('|') };
}
const info = go(d.toDecoder());
if (d instanceof ConfigDescription) {
info.description = d._description;
}
return info;
}
if (d instanceof t.Custom) {
throw new Error('[config-reader]: CustomDecoder is not supported');
}
if (d instanceof t.AtDecoder) {
throw new Error('[config-reader]: AtDecoder is not supported');
}
if (d instanceof t.Chain) {
throw new Error('[config-reader]: ChainDecoder is not supported');
}
return absurd(d);
}
}
return absurd(decoder);
declare module "../decoder" {
interface DecoderBase<A> {
withDescription(description: string): Decoder<A>;
validateConfigs(...sources: ConfigSource[]): Either<Problem, A>;
}
}
t.DecoderBase.prototype.withDescription = function(description) {
return new ConfigDescription(this as any, description);
};
t.DecoderBase.prototype.validateConfigs = function(...sources) {
return this.validate(merge(this as any, ...sources));
};
......@@ -9,17 +9,18 @@ import { Expr, absurd } from '../types';
/** ADT */
export type Decoder<A> =
| CustomDecoder<A>
| Custom<A>
| ArrayDecoder<A>
| DictionaryDecoder<A>
| Dict<A>
| RecordDecoder<A>
| AtDecoder<A>
| PrimitiveDecoder<A>
| PureDecoder<A>
| ChainDecoder<A>
| OneOfDecoder<A>
| DiscriminatedDecoder<A>
| HasDecoder<A>
| Primitive<A>
| Pure<A>
| Chain<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[] = [];
/**
* Печать декодера в виде выражения которым он был создан
......@@ -89,193 +92,160 @@ export class DecoderBase<A> {
map<B>(f: (a: A) => B): Decoder<B> {
const self = this as any as Decoder<A>;
return new ChainDecoder(self, x => x.fold(fail, x => of(f(x))));
return new Chain(self, x => x.fold(fail, x => of(f(x))));
}
mapTo<B>(value: B): Decoder<B> {
const self = this as any as Decoder<A>;
return new ChainDecoder(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> {
const self = this as any as Decoder<A>;
return new ChainDecoder(self, x => x.fold(fail, f));
return new Chain(self, x => x.fold(fail, f));
}
chainTo<B>(value: Decoder<B>): Decoder<B> {
const self = this as any as Decoder<A>;
return new ChainDecoder(self, x => x.fold(fail, () => value));
return new Chain(self, x => x.fold(fail, () => value));
}
mapProblem(f: (a: Problem) => Problem): Decoder<A> {
const self = this as any as Decoder<A>;
return new ChainDecoder(self, x => x.fold(f, x => x));
return new Chain(self, x => x.fold(f, x => x));
}
mapProblemTo(value: Problem): Decoder<A> {
const self = this as any as Decoder<A>;
return new ChainDecoder(self, x => x.fold(() => value, x => x));
return new Chain(self, x => x.fold(() => value, x => x));
}
withDefault(defValue: A): Decoder<A>;
withDefault<B extends Expr>(defValue: B): Decoder<A|B>;
withDefault<B extends Expr>(defValue: B): Decoder<A|B> {
return new WithDefaultDecoder(this as any, defValue);
return new WithDefault(this as any, defValue);
}
refine(pred: (a: A) => boolean): Decoder<A> {
const self = this as any as Decoder<A>;
return new ChainDecoder(self, ethr => {
return new Chain(self, ethr => {
switch (ethr.tag) {
case 'Left': return new PureDecoder(ethr);
case 'Right': return pred(ethr.value) ? new PureDecoder(ethr) : fail('refinement failed');
case 'Left': return new Pure(ethr);
case 'Right': return pred(ethr.value) ? new Pure(ethr) : fail('refinement failed');
}
});
}
}
/**
* Декодер с произвольной функцией для валидации
*/
export class CustomDecoder<A> extends DecoderBase<A> {
readonly tag: 'CustomDecoder' = 'CustomDecoder';
export class Custom<A> extends DecoderBase<A> {
constructor(
readonly name: string,
readonly validateCustom: (val: any) => Either<Problem|string, A>,
readonly _name: string,
readonly _validate: (val: any) => Either<Problem|string, A>,
) { super(); }
}
/**
* Декодер массивов
*/
export class ArrayDecoder<A> extends DecoderBase<A> {
readonly tag: 'ArrayDecoder' = 'ArrayDecoder';
constructor(
readonly decoder: Decoder<any>,
readonly _decoder: Decoder<any>,
) { super(); }
}
/**
* Декодер словарей (или хешей, те объектов с произвольным кол-вом ключей)
*/
export class DictionaryDecoder<A> extends DecoderBase<A> {
readonly tag: 'DictionaryDecoder' = 'DictionaryDecoder';
export class Dict<A> extends DecoderBase<A> {
constructor(
readonly decoder: Decoder<any>,
readonly _decoder: Decoder<any>,
) { super(); }
}
/**
* Декодер записей (объекты с фиксированным количеством полей)
*/
export class RecordDecoder<A> extends DecoderBase<A> {
readonly tag: 'RecordDecoder' = 'RecordDecoder';
constructor(
readonly description: Record<string, Decoder<any>>,
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'] }> {
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> {
readonly tag: 'AtDecoder' = 'AtDecoder';
constructor(
readonly path: Array<string|number>,
readonly decoder: Decoder<A>,
readonly _path: Array<string|number>,
readonly _decoder: Decoder<A>,
) { super(); }
}
/**
* Декодер для примитовов
*/
export class PrimitiveDecoder<A> extends DecoderBase<A> {
export class Primitive<A> extends DecoderBase<A> {
readonly tag: 'PrimitiveDecoder' = 'PrimitiveDecoder';
constructor(
readonly primitive: 'null'|'undefined'|'string'|'boolean'|'any'|'nat'|'int'|'float'
readonly _type: 'null'|'undefined'|'string'|'boolean'|'any'|'nat'|'int'|'float'
) { super(); }
}
/**
* Тривиальный декодер
*/
export class PureDecoder<A> extends DecoderBase<A> {
export class Pure<A> extends DecoderBase<A> {
constructor(
readonly value: Either<Problem|string, A>,
readonly _value: Either<Problem|string, A>,
) { super(); }
}
/**
* Монадный комбинатор
*/
export class ChainDecoder<A> extends DecoderBase<A> {
export class Chain<A> extends DecoderBase<A> {
constructor(
readonly decoder: Decoder<any>,
readonly andThen: (x: Validation<any>) => Decoder<A>,
readonly _decoder: Decoder<any>,
readonly _then: (x: Validation<any>) => Decoder<A>,
) { super(); }
}
/**
* `oneOf` комбинатор
*/
export class OneOfDecoder<A> extends DecoderBase<A> {
export class OneOf<A> extends DecoderBase<A> {
constructor(
readonly alternatives: Decoder<any>[],
readonly _alternatives: Decoder<any>[],
) { super(); }
}
/**
* `discriminateOn` комбинатор
*/
export class DiscriminatedDecoder<A> extends DecoderBase<A> {
export class Discriminate<A> extends DecoderBase<A> {
constructor(
readonly discriminator: string|number,
readonly alternatives: Record<string|number, Decoder<any>>,
readonly _discriminator: string|number,
readonly _alternatives: Record<string|number, Decoder<any>>,
) { super(); }
}
/**
* `discriminateOn` комбинатор
*/
export abstract class HasDecoder<A> extends DecoderBase<A> {
export abstract class ToDecoder<A> extends DecoderBase<A> {
abstract toDecoder(): Decoder<A>;
}
/**
* `discriminateOn` комбинатор
*/
export class WithDefaultDecoder<A> extends HasDecoder<A> {
export class WithDefault<A> extends ToDecoder<A> {
constructor(
readonly decoder: Decoder<A>,
readonly defaultValue: A,
readonly _decoder: Decoder<A>,
readonly _default: A,
) { super(); }
toDecoder() {
return new OneOfDecoder<A>([this.decoder, of(this.defaultValue)]);
return new OneOf<A>([this._decoder, of(this._default)]);
}
}
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)}`))));
}
}
/**
* Тип результата для функции-валидатора
* Результат валидаии
*/
export type Validation<A> = Either<Problem|string, A>;
......@@ -283,11 +253,11 @@ export type Validation<A> = Either<Problem|string, A>;
/**
* Конструктир кастомных декодеров
*/
export function decoder<A>(validate: (value: any) => Either<Problem|string, A>): CustomDecoder<A>;
export function decoder<A>(name: string, validate: (value: any) => Either<Problem|string, A>): CustomDecoder<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 CustomDecoder('custom', arguments[0]);
if (arguments.length === 2) return new CustomDecoder(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`);
}
......@@ -295,8 +265,8 @@ export function decoder(): any {
/**
* Алиас для `x => new PureDecoder(Either.of(x))`
*/
export function of<A extends Expr>(a: A): PureDecoder<A> {
return new PureDecoder(Either.of(a));
export function of<A extends Expr>(a: A): Pure<A> {
return new Pure(Either.of(a));
}
......@@ -304,7 +274,7 @@ export function of<A extends Expr>(a: A): PureDecoder<A> {
* Алиас для `x => new PureDecoder(Either.failure(x))`
*/
export function fail(x: Problem|string): Decoder<never> {
return new PureDecoder(Either.failure(x));
return new Pure(Either.failure(x));
}
......@@ -312,16 +282,16 @@ export function fail(x: Problem|string): Decoder<never> {
* Аппликативный комбинатор
* TODO: сделать отдельным вариантом в ADT для реализации `prettyPrint`
*/
export function ap<A,B>(a: Decoder<A>, f:(a: A) => B): CustomDecoder<B>;
export function ap<A,B,C>(a: Decoder<A>, b: Decoder<B>, f:(a: A, b: B) => C):CustomDecoder<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):CustomDecoder<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):CustomDecoder<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):CustomDecoder<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):CustomDecoder<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):CustomDecoder<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):CustomDecoder<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):CustomDecoder<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):CustomDecoder<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>;
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(...args: Array<Decoder<any> | Function>): Decoder<any> {
return decoder('ap', (val) => {
const func = args[args.length - 1] as Function;
......@@ -340,14 +310,14 @@ export function ap(...args: Array<Decoder<any> | Function>): Decoder<any> {
// Примитивы
const anyDecoder = new PrimitiveDecoder<any>('any');
const stringDecoder = new PrimitiveDecoder<string>('string');
const booleanDecoder = new PrimitiveDecoder<boolean>('boolean');
const nullDecoder = new PrimitiveDecoder<null>('null');
const undefinedDecoder = new PrimitiveDecoder<undefined>('undefined');
export const nat = new PrimitiveDecoder<number>('nat');
export const int = new PrimitiveDecoder<number>('int');
export const float = new PrimitiveDecoder<number>('float');
const anyDecoder = new Primitive<any>('any');
const stringDecoder = new Primitive<string>('string');
const booleanDecoder = new Primitive<boolean>('boolean');
const nullDecoder = new Primitive<null>('null');
const undefinedDecoder = new Primitive<undefined>('undefined');
export const nat = new Primitive<number>('nat');
export const int = new Primitive<number>('int');
export const float = new Primitive<number>('float');
// Экспорт с переименованием
......@@ -358,16 +328,16 @@ export { anyDecoder as any, stringDecoder as string, booleanDecoder as boolean,
* Сопоставление с несколькими декодерами до первого успешного
* сравнвния
*/
export function oneOf<A>(a: Decoder<A>): OneOfDecoder<A>;
export function oneOf<A,B>(a: Decoder<A>, b: Decoder<B>): OneOfDecoder<A|B>;
export function oneOf<A,B,C>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>): OneOfDecoder<A|B|C>;
export function oneOf<A,B,C,D>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>): OneOfDecoder<A|B|C|D>;
export function oneOf<A,B,C,D,E>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>): OneOfDecoder<A|B|C|D|E>;
export function oneOf<A,B,C,D,E,F>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>): OneOfDecoder<A|B|C|D|E|F>;
export function oneOf<A>(a: Decoder<A>): OneOf<A>;
export function oneOf<A,B>(a: Decoder<A>, b: Decoder<B>): OneOf<A|B>;
export function oneOf<A,B,C>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>): OneOf<A|B|C>;
export function oneOf<A,B,C,D>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>): OneOf<A|B|C|D>;
export function oneOf<A,B,C,D,E>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>): OneOf<A|B|C|D|E>;
export function oneOf<A,B,C,D,E,F>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>): OneOf<A|B|C|D|E|F>;
export function oneOf<array extends Decoder<any>[]>(array: array): Decoder<array[number]['_A']>;
export function oneOf(): Decoder<any> {
const decoders = Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.call(arguments);
return new OneOfDecoder(decoders);
return new OneOf(decoders);
}
......@@ -379,21 +349,8 @@ export function record<fields extends { [k: string]: Decoder<any> }>(fields: fie
return new RecordDecoder(fields);
}
// Вложенные записи в декодерах
export function record2<fields extends { [k: string]: Decoder<any> }>(fields: fields): RecordDecoder<{[k in keyof fields]: fields[k]['_A'] }> {
return new RecordDecoder(fields);
}
// Вложенные записи в декодерах
export function record3<fields extends { [k: string]: Decoder<any> }>(fields: fields): RecordDecoder<{[k in keyof fields]: fields[k]['_A'] }> {
return new RecordDecoder(fields);
}
export const d = array(record({ a: array(record({ b: record({ c: stringDecoder }) })) } ));
export function dict<A>(decoder: Decoder<A>): Decoder<Record<string, A>> {
return new DictionaryDecoder(decoder);
return new Dict(decoder);
}
export function at<A>(path: string|string[]|number|number[], decoder: Decoder<A>): Decoder<A> {
......@@ -426,9 +383,9 @@ export function required<A>(key: string|string[], dec: Decoder<A>): Decoder<A> {
* // => Decoder<{ { type: 'director', ... } | { type: 'employee', ... }}>
* ```
*/
export type DiscriminateOn<TagKey extends string, Descriptor extends Record<string, Decoder<any>>> = DiscriminatedDecoder<{ [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]>;
export function discriminate<TagKey extends string, Descriptor extends Record<string, Decoder<any>>>(key: TagKey, record: Descriptor): DiscriminateOn<TagKey, Descriptor> {
return new DiscriminatedDecoder(key, record);
return new Discriminate(key, record);
}
......@@ -446,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 OneOfDecoder(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;
/**
* Кортежи разных размеров. Проверяемое значение необязательно должно
......@@ -462,19 +421,9 @@ export function literals(): Decoder<any> {
* const pair_2 = t.record({ '0': t.string, '1': t.number }); // тоже самое
* ```
*/
export function tuple<A>(a: Decoder<A>): Decoder<[A]>;
export function tuple<A, B>(a: Decoder<A>, b: Decoder<B>): Decoder<[A, B]>;
export function tuple<A, B, C>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>): Decoder<[A, B, C]>;
export function tuple<A, B, C, D>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>): Decoder<[A, B, C, D]>;
export function tuple<A, B, C, D, E>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>): Decoder<[A, B, C, D, E]>;
export function tuple<A, B, C, D, E, F>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>): Decoder<[A, B, C, D, E, F]>;
export function tuple<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>, g: Decoder<G>): Decoder<[A, B, C, D, E, F, G]>;
export function tuple<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>, h: Decoder<H>): Decoder<[A, B, C, D, E, F, G, H]>;
export function tuple<A, B, C, D, E, F, G, H, I>(a: Decoder<A>, b: Decoder<B>, c: Decoder<C>, d: Decoder<D>, e: Decoder<E>, f: Decoder<F>, g: Decoder<G>, h: Decoder<H>, i: Decoder<I>): Decoder<[A, B, C, D, E, F, G, H, I]>;
export function tuple<A, B, C, D, E, F, G, H, I, 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>, i: Decoder<I>, j: Decoder<J>): Decoder<[A, B, C, D, E, F, G, H, I, J]>;
export function tuple<A>(args: Decoder<A>[]): Decoder<A[]>;
export function tuple(): Decoder<any> {
const args: Decoder<any>[] = Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.call(arguments);
// @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> {
// @ts-ignore
return ap(...args.map((decoder, idx) => at(idx, decoder)), (...xs) => xs);
}
......@@ -489,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('.'));
}
......@@ -503,35 +452,47 @@ function fancyTypeOf(value: any): string {
* Выполнение валидации
*/
export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Problem, A> {
if (decoder instanceof CustomDecoder) {
return decoder.validateCustom(value).mapLeft(projectProblem);
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'));
DecoderBase.path.push('0');
for (let i = 0; i < value.length; i++) {
const ethr = doValidate(decoder.decoder, value[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);
}
if (decoder instanceof DictionaryDecoder) {
if (decoder instanceof Dict) {
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;
const ethr = doValidate(decoder.decoder, value[key]);
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);
}
......@@ -539,31 +500,36 @@ 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 } = {};
for (let key in decoder.description) {
if (!decoder.description.hasOwnProperty(key)) continue;
const ethr = doValidate(decoder.description[key], value[key]);
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) {
case 'Left': { return ethr; }
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;
for (let i in decoder.path) {
if (iter === undefined || !iter.hasOwnProperty(decoder.path[i])) {
decoder._path.forEach(k => DecoderBase.path.push(k + ''));
for (let i in decoder._path) {
if (iter === undefined || !iter.hasOwnProperty(decoder._path[i])) {
iter = undefined;
break;
}
iter = iter[decoder.path[i]];
iter = iter[decoder._path[i]];
}
return doValidate(decoder.decoder, iter);
cleanup();
return doValidate(decoder._decoder, iter);
}
if (decoder instanceof PrimitiveDecoder) {
switch (decoder.primitive) {
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)}`));
case 'undefined': return value === undefined ? Either.of(value as A) : Either.failure(projectProblem(`expected undefined, got ${fancyTypeOf(value)}`));
case 'string': return typeof(value) === 'string' ? Either.of(value as any) : Either.failure(projectProblem(`expected a string, got ${fancyTypeOf(value)}`));
......@@ -573,15 +539,15 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
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.primitive);
return absurd(decoder._type);
}
if (decoder instanceof PureDecoder) {
return decoder.value.mapLeft(projectProblem);
if (decoder instanceof Pure) {
return decoder._value.mapLeft(projectProblem);
}
if (decoder instanceof OneOfDecoder) {
for (const d of decoder.alternatives) {
if (decoder instanceof OneOf) {
for (const d of decoder._alternatives) {
const ethr = doValidate(d, value);
switch(ethr.tag) {
case 'Left': break;
......@@ -591,20 +557,25 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
return Either.failure(projectProblem('none of decoders succeded'));
}
if (decoder instanceof ChainDecoder) {
return doValidate(decoder.andThen(doValidate(decoder.decoder, value)), value);
if (decoder instanceof Chain) {
DecoderBase.path.push('<chain>');
const output = doValidate(decoder._then(doValidate(decoder._decoder, value)), value);
cleanup();
return output;
}
if (decoder instanceof DiscriminatedDecoder) {
const key = decoder.discriminator;
const record = decoder.alternatives;
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]}`));
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 HasDecoder) {
if (decoder instanceof ToDecoder) {
return doValidate(decoder.toDecoder(), value);
}
......@@ -612,12 +583,11 @@ 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 };
}
}
/**
* Печать декодера.
* ```ts
......@@ -626,23 +596,28 @@ export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Probl
* ```
*/
export function prettyPrint(decoder: Decoder<any>): string {
if (decoder instanceof CustomDecoder) return `t.decoder(${JSON.stringify(decoder.name)}, <func>)`;
if (decoder instanceof ArrayDecoder) return `t.array(${decoder.decoder.prettyPrint()})`;
if (decoder instanceof DictionaryDecoder) 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 PrimitiveDecoder) return `t.${decoder.primitive}`;
if (decoder instanceof PureDecoder) return `t.of(${JSON.stringify(decoder.value)})`;
if (decoder instanceof ChainDecoder) return `${decoder.prettyPrint()}.chain(<func>)`;
if (decoder instanceof OneOfDecoder) return `t.oneOf(${decoder.alternatives.map(x => x.prettyPrint()).join(', ')})`;
if (decoder instanceof DiscriminatedDecoder) {
const discriminator = JSON.stringify(decoder.discriminator);
const alternatives = Object.keys(decoder.alternatives).map(k => JSON.stringify(k) + ": " + decoder.alternatives[k].prettyPrint()).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(', ');
return `t.discriminateOn(${JSON.stringify(discriminator)}, ${alternatives})`;
}
if (decoder instanceof HasDecoder) {
if (decoder instanceof ToDecoder) {
return prettyPrint(decoder.toDecoder());
}
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