Commit 6369d976 by Vladislav Lagunov

Исправление ошибок

parent 2f06a7e2
import { Either } from '~/either'; import { Either } from '../either';
import { Expr, absurd } from '~/types'; import { Expr, absurd } from '../types';
// This library helps you declaratively construct type-safe validators for untyped JSON // This library helps you declaratively construct type-safe validators for untyped JSON
...@@ -18,7 +18,8 @@ export type Decoder<A> = ...@@ -18,7 +18,8 @@ export type Decoder<A> =
| PureDecoder<A> | PureDecoder<A>
| ChainDecoder<A> | ChainDecoder<A>
| OneOfDecoder<A> | OneOfDecoder<A>
| DiscriminateOnDecoder<A> | DiscriminatedDecoder<A>
| HasDecoder<A>
/** /**
...@@ -27,9 +28,9 @@ export type Decoder<A> = ...@@ -27,9 +28,9 @@ export type Decoder<A> =
export type Problem = { export type Problem = {
value: any; value: any;
rootValue: any; rootValue: any;
decoders: Decoder<any>[]; decoder: Decoder<any>;
rootDecoder: Decoder<any>;
message: string; message: string;
path: Array<string|number>;
} }
...@@ -48,60 +49,42 @@ const defaultValidateOptions: ValidateOptions = { ...@@ -48,60 +49,42 @@ const defaultValidateOptions: ValidateOptions = {
// Базовый класс для наследования методов // Базовый класс для наследования методов
export class DecoderBase<A> { export class DecoderBase<A> {
readonly _A: A; readonly _A: A;
private static path: Decoder<any>[] = []; static rootDecoder: Decoder<any>;
private static rootValue: any = undefined; static rootValue: any = undefined;
/** /**
* Печать декодера в виде выражения которым он был создан * Печать декодера в виде выражения которым он был создан
*/ */
prettyPrint(): string { prettyPrint(): string {
const self = this as any as Decoder<A>; const self = this as any as Decoder<A>;
return prettyPrint(self);
} }
/** /**
* Валидация произвольного значения * Валидация произвольного значения
*/ */
validate(value: any, options?: Partial<ValidateOptions>): Either<Problem, A> { 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 _options: ValidateOptions; let output: Either<Problem, A>;
let cleanUpRoot = false; if (DecoderBase.rootValue === undefined) {
DecoderBase.path.push(self); DecoderBase.rootValue = value;
if (DecoderBase.rootValue === undefined) { DecoderBase.rootValue = value; cleanUpRoot = true; } DecoderBase.rootDecoder = self;
const output = doValidate(self, value) output = doValidate(self, value)
DecoderBase.path.pop(); DecoderBase.rootValue = undefined;
if (cleanUpRoot) DecoderBase.rootValue = undefined; DecoderBase.rootDecoder = undefined;
} else {
output = doValidate(self, value)
}
// Печать проблемы в консоль если выставлена соответствующая опция // Печать проблемы в консоль если выставлена соответствующая опция
if (output.tag === 'Left' && makeOptions().printProblem) { if (output.tag === 'Left') {
const options = { ...defaultValidateOptions, ...options_ };
if (options.printProblem) {
printProblems(output.value); printProblems(output.value);
} }
return output;
function makeOptions(): ValidateOptions {
if (_options) return _options;
return _options = options ? { ...defaultValidateOptions, ...options } : defaultValidateOptions;
}
function doValidate(decoder: Decoder<A>, value: any): Either<Problem, A> {
switch(decoder.tag) {
case 'CustomDecoder': return decoder.validateCustom(value).mapLeft(decorateProblem);;
case 'ArrayDecoder': return decoder.validateArray(value).mapLeft(decorateProblem);;
case 'DictionaryDecoder': return decoder.validateDictionary(value).mapLeft(decorateProblem);;
case 'RecordDecoder': return decoder.validateRecord(value).mapLeft(decorateProblem);;
case 'AtDecoder': return decoder.validateAt(value).mapLeft(decorateProblem);;
case 'PrimitiveDecoder': return decoder.validatePrimitive(value).mapLeft(decorateProblem);;
case 'PureDecoder': return decoder.value.mapLeft(decorateProblem);;
case 'OneOfDecoder': return decoder.validateOneOf(value).mapLeft(decorateProblem);;
case 'ChainDecoder': return decoder.andThen(decoder.decoder.validate(value)).validate(value);;
}
}
function decorateProblem(problem: Problem|string): Problem {
return typeof(problem) === 'string'
? { value, rootValue: DecoderBase.rootValue, decoders: DecoderBase.path.slice(), message: problem, path: [] }
: problem;
} }
return output;
} }
map<B>(f: (a: A) => B): Decoder<B> { map<B>(f: (a: A) => B): Decoder<B> {
...@@ -152,8 +135,6 @@ export class DecoderBase<A> { ...@@ -152,8 +135,6 @@ export class DecoderBase<A> {
} }
/** /**
* Декодер с произвольной функцией для валидации * Декодер с произвольной функцией для валидации
*/ */
...@@ -176,19 +157,6 @@ export class ArrayDecoder<A> extends DecoderBase<A> { ...@@ -176,19 +157,6 @@ export class ArrayDecoder<A> extends DecoderBase<A> {
constructor( constructor(
readonly decoder: Decoder<any>, readonly decoder: Decoder<any>,
) { super(); } ) { super(); }
validateArray(value: any): Either<Problem|string, A> {
const output: any[] = [];
if (!Array.isArray(value)) return Either.failure('not an array');
for (let i = 0; i < value.length; i++) {
const ethr = this.decoder.validate(value[i]);
switch(ethr.tag) {
case 'Left': { ethr.value.path.push(i); return ethr; }
case 'Right': output.push(ethr.value); break;
}
}
return Either.of(output as any as A);
}
} }
...@@ -201,21 +169,6 @@ export class DictionaryDecoder<A> extends DecoderBase<A> { ...@@ -201,21 +169,6 @@ export class DictionaryDecoder<A> extends DecoderBase<A> {
constructor( constructor(
readonly decoder: Decoder<any>, readonly decoder: Decoder<any>,
) { super(); } ) { super(); }
validateDictionary(value: any): Either<Problem|string, A> {
if (value === null) return Either.failure('found null');
if (typeof (value) !== 'object') return Either.failure('not an object');
const output: { [k: string]: A } = {};
for (let key in value) {
if (!value.hasOwnProperty(key)) continue;
const ethr = this.decoder.validate(value[key]);
switch(ethr.tag) {
case 'Left': { ethr.value.path.push(key); return ethr; }
case 'Right': output[key] = ethr.value; break;
}
}
return Either.of(output as any as A);
}
} }
...@@ -228,21 +181,6 @@ export class RecordDecoder<A> extends DecoderBase<A> { ...@@ -228,21 +181,6 @@ export class RecordDecoder<A> extends DecoderBase<A> {
constructor( constructor(
readonly description: Record<string, Decoder<any>>, readonly description: Record<string, Decoder<any>>,
) { super(); } ) { super(); }
validateRecord(value: any): Either<Problem|string, A> {
if (value === null) return Either.failure('found null');
if (typeof (value) !== 'object') return Either.failure('not an object');
const output: { [k: string]: any } = {};
for (let key in this.description) {
if (!this.description.hasOwnProperty(key)) continue;
const ethr = this.description[key].validate(value[key]);
switch(ethr.tag) {
case 'Left': { ethr.value.path.push(key); return ethr; }
case 'Right': output[key] = ethr.value; break;
}
}
return Either.of(output as any as A);
}
} }
...@@ -256,19 +194,6 @@ export class AtDecoder<A> extends DecoderBase<A> { ...@@ -256,19 +194,6 @@ export class AtDecoder<A> extends DecoderBase<A> {
readonly path: Array<string|number>, readonly path: Array<string|number>,
readonly decoder: Decoder<A>, readonly decoder: Decoder<A>,
) { super(); } ) { super(); }
validateAt(value: any): Either<Problem|string, A> {
let iter = value;
for (let i in this.path) {
if (iter === undefined || !iter.hasOwnProperty(this.path[i])) {
iter = undefined;
break;
}
iter = iter[this.path[i]];
}
return this.decoder.validate(iter);
}
} }
...@@ -281,19 +206,6 @@ export class PrimitiveDecoder<A> extends DecoderBase<A> { ...@@ -281,19 +206,6 @@ export class PrimitiveDecoder<A> extends DecoderBase<A> {
constructor( constructor(
readonly primitive: 'null'|'undefined'|'string'|'boolean'|'any'|'nat'|'int'|'float' readonly primitive: 'null'|'undefined'|'string'|'boolean'|'any'|'nat'|'int'|'float'
) { super(); } ) { super(); }
validatePrimitive(value: any): Either<string, A> {
switch (this.primitive) {
case 'null': return value === null ? Either.of(value) : Either.failure(`expected null, got ${fancyTypeOf(value)}`);
case 'undefined': return value === undefined ? Either.of(value) : Either.failure(`expected undefined, got ${fancyTypeOf(value)}`);
case 'string': return typeof(value) === 'string' ? Either.of(value as any) : Either.failure(`expected a string, got ${fancyTypeOf(value)}`);
case 'boolean': return typeof(value) === 'boolean' ? Either.of(value as any) : Either.failure(`expected a boolean, got ${fancyTypeOf(value)}`);
case 'any': return Either.of(value);
case 'nat': return typeof (value) !== 'number' ? Either.failure('not a number') : (value|0) === value && value >= 0 ? Either.of(value as any) : Either.failure('not a natural number');
case 'int': return typeof (value) !== 'number' ? Either.failure('not a number') : (value|0) === value ? Either.of(value as any) : Either.failure('not an integer')
case 'float': return typeof (value) !== 'number' ? Either.failure('not a number') : Either.of(value as any);
}
}
} }
...@@ -325,27 +237,16 @@ export class OneOfDecoder<A> extends DecoderBase<A> { ...@@ -325,27 +237,16 @@ export class OneOfDecoder<A> extends DecoderBase<A> {
constructor( constructor(
readonly alternatives: Decoder<any>[], readonly alternatives: Decoder<any>[],
) { super(); } ) { super(); }
validateOneOf(value: any): Either<Problem|string, A> {
for (const decoder of this.alternatives) {
const ethr = decoder.validate(value);
switch(ethr.tag) {
case 'Left': break;
case 'Right': return ethr;
}
}
return Either.failure('none of decoders succeded');
}
} }
/** /**
* `discriminateOn` комбинатор * `discriminateOn` комбинатор
*/ */
export class DiscriminateOnDecoder<A> extends DecoderBase<A> { export class DiscriminatedDecoder<A> extends DecoderBase<A> {
constructor( constructor(
readonly discriminator: string|number, readonly discriminator: string|number,
readonly alternatives: Record<string|number, Decoder<unknown>>, readonly alternatives: Record<string|number, Decoder<any>>,
) { super(); } ) { super(); }
} }
...@@ -362,6 +263,14 @@ export class WithDefaultDecoder<A> extends DecoderBase<A> { ...@@ -362,6 +263,14 @@ export class WithDefaultDecoder<A> extends DecoderBase<A> {
/** /**
* `discriminateOn` комбинатор
*/
export abstract class HasDecoder<A> extends DecoderBase<A> {
abstract toDecoder(): Decoder<A>;
}
/**
* Тип результата для функции-валидатора * Тип результата для функции-валидатора
*/ */
export type Validation<A> = Either<Problem|string, A>; export type Validation<A> = Either<Problem|string, A>;
...@@ -504,6 +413,22 @@ export function required<A>(key: string|string[], dec: Decoder<A>): Decoder<A> { ...@@ -504,6 +413,22 @@ export function required<A>(key: string|string[], dec: Decoder<A>): Decoder<A> {
/** /**
* Декодер для дискриминированных объединений
* ```ts
* const roleDecoder = t.discrinimate('type', {
* 'director': record({ name: t.string, private_info: t.string }),
* 'employee': record({ name: t.string }),
* });
* // => 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 function discriminate<TagKey extends string, Descriptor extends Record<string, Decoder<any>>>(key: TagKey, record: Descriptor): DiscriminateOn<TagKey, Descriptor> {
return new DiscriminatedDecoder(key, record);
}
/**
* Хелпер для объявления декодеров в стиле Elm. Используется вместе в * Хелпер для объявления декодеров в стиле Elm. Используется вместе в
* `ap` * `ap`
*/ */
...@@ -517,16 +442,8 @@ export function optional(key: string|string[], dec: Decoder<any>, def: any): Dec ...@@ -517,16 +442,8 @@ export function optional(key: string|string[], dec: Decoder<any>, def: any): Dec
/** /**
* Создание декодера перечислением всех допустимых значений * Создание декодера перечислением всех допустимых значений
*/ */
export type L = string|number|boolean|null; export function literals<A extends Expr[]>(...array: A): Decoder<A[number]>;
export function literals<A extends L>(a: A): Decoder<A>; export function literals<A extends Expr[]>(array: A): Decoder<A[number]>;
export function literals<A extends L, B extends L>(a: A, b: B): Decoder<A|B>;
export function literals<A extends L, B extends L, C extends L>(a: A, b: B, c: C): Decoder<A|B|C>;
export function literals<A extends L, B extends L, C extends L, D extends L>(a: A, b: B, c: C, d: D): Decoder<A|B|C|D>;
export function literals<A extends L, B extends L, C extends L, D extends L, E extends L>(a: A, b: B, c: C, d: D, e: E): Decoder<A|B|C|D|E>;
export function literals<A extends L, B extends L, C extends L, D extends L, E extends L, F extends L>(a: A, b: B, c: C, d: D, e: E, f: F): Decoder<A|B|C|D|E|F>;
export function literals<A extends L, B extends L, C extends L, D extends L, E extends L, F extends L, G extends L>(a: A, b: B, c: C, d: D, e: E, f: F, g: G): Decoder<A|B|C|D|E|F|G>;
export function literals<A extends L, B extends L, C extends L, D extends L, E extends L, F extends L, G extends L, H extends L>(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H): Decoder<A|B|C|D|E|F|G|H>;
export function literals<A extends L[]>(array: A): Decoder<A[number]>;
export function literals(): Decoder<any> { export function literals(): Decoder<any> {
const literals: Expr[] = Array.isArray(arguments[0]) ? arguments[0] : Array.prototype.slice.call(arguments); 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)}`)))); return new OneOfDecoder(literals.map(x => decoder(v => v === x ? Either.of(v) : Either.failure(`expected ${x}, got ${fancyTypeOf(v)}`))));
...@@ -563,12 +480,11 @@ export function tuple(): Decoder<any> { ...@@ -563,12 +480,11 @@ export function tuple(): Decoder<any> {
* Печать проблемы с консоль * Печать проблемы с консоль
*/ */
export function printProblems(problems: Problem): void { export function printProblems(problems: Problem): void {
const lastDecoder = problems.decoders[problems.decoders.length - 1];
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;', lastDecoder.prettyPrint()); 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); console.log('%cfailed value:', 'font-weight: 600;', problems.value);
console.log('%croot value:', 'font-weight: 600;', problems.rootValue); console.log('%croot value:', 'font-weight: 600;', problems.rootValue);
console.log('%cproblem path:', 'font-weight: 600;', prettyPrintPath(problems.path));
//console.log('decoders ', problems.decoders.map(x => x.prettyPrint())); //console.log('decoders ', problems.decoders.map(x => x.prettyPrint()));
} }
...@@ -591,6 +507,125 @@ function prettyPrintPath(path: Array<string|number>): string { ...@@ -591,6 +507,125 @@ function prettyPrintPath(path: Array<string|number>): string {
/** /**
* Выполнение валидации
*/
export function doValidate<A>(decoder: Decoder<A>, value: unknown): Either<Problem, A> {
if (decoder instanceof CustomDecoder) {
return decoder.validateCustom(value).mapLeft(projectProblem);
}
if (decoder instanceof ArrayDecoder) {
const output: any[] = [];
if (!Array.isArray(value)) return Either.failure(projectProblem('not an array'));
for (let i = 0; i < value.length; i++) {
const ethr = this.decoder.validate(value[i]);
switch(ethr.tag) {
case 'Left': { ethr.value.path.push(i); return ethr; }
case 'Right': output.push(ethr.value); break;
}
}
return Either.of(output as any as A);
}
if (decoder instanceof DictionaryDecoder) {
if (value === null) return Either.failure(projectProblem('found null'));
if (typeof (value) !== 'object') return Either.failure(projectProblem('not an object'));
const output: { [k: string]: A } = {};
for (let key in value) {
if (!value.hasOwnProperty(key)) continue;
const ethr = this.decoder.validate(value[key]);
switch(ethr.tag) {
case 'Left': { ethr.value.path.push(key); return ethr; }
case 'Right': output[key] = ethr.value; break;
}
}
return Either.of(output as any as A);
}
if (decoder instanceof RecordDecoder) {
if (value === null) return Either.failure(projectProblem('found null'));
if (typeof (value) !== 'object') return Either.failure(projectProblem('not an object'));
const output: { [k: string]: any } = {};
for (let key in this.description) {
if (!this.description.hasOwnProperty(key)) continue;
const ethr = this.description[key].validate(value[key]);
switch(ethr.tag) {
case 'Left': { ethr.value.path.push(key); return ethr; }
case 'Right': output[key] = ethr.value; break;
}
}
return Either.of(output as any as A);
}
if (decoder instanceof AtDecoder) {
let iter = value as any;
for (let i in this.path) {
if (iter === undefined || !iter.hasOwnProperty(this.path[i])) {
iter = undefined;
break;
}
iter = iter[this.path[i]];
}
return this.decoder.validate(iter);
}
if (decoder instanceof PrimitiveDecoder) {
switch (decoder.primitive) {
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)}`));
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 'float': return typeof (value) !== 'number' ? Either.failure(projectProblem('not a number')) : Either.of(value as any);
}
return absurd(decoder.primitive);
}
if (decoder instanceof PureDecoder) {
return decoder.value.mapLeft(projectProblem);
}
if (decoder instanceof OneOfDecoder) {
for (const decoder of this.alternatives) {
const ethr = decoder.validate(value);
switch(ethr.tag) {
case 'Left': break;
case 'Right': return ethr;
}
}
return Either.failure(projectProblem('none of decoders succeded'));
}
if (decoder instanceof ChainDecoder) {
return doValidate(decoder.andThen(doValidate(decoder.decoder, value)), value);
}
if (decoder instanceof DiscriminatedDecoder) {
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] }));
}
if (decoder instanceof HasDecoder) {
return doValidate(decoder.toDecoder(), value);
}
return absurd(decoder);
function projectProblem(problem: Problem|string): Problem {
if (typeof(problem) !== 'string') return problem;
return { value, rootValue: DecoderBase.rootValue, decoder, rootDecoder: DecoderBase.rootDecoder, message: problem, };
}
}
/**
* Печать декодера. * Печать декодера.
* ```ts * ```ts
* const user = t.record({ name: t.string }); * const user = t.record({ name: t.string });
...@@ -607,10 +642,14 @@ export function prettyPrint(decoder: Decoder<any>): string { ...@@ -607,10 +642,14 @@ export function prettyPrint(decoder: Decoder<any>): string {
if (decoder instanceof PureDecoder) return `t.of(${JSON.stringify(decoder.value)})`; if (decoder instanceof PureDecoder) return `t.of(${JSON.stringify(decoder.value)})`;
if (decoder instanceof ChainDecoder) return `${decoder.prettyPrint()}.chain(<func>)`; 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 OneOfDecoder) return `t.oneOf(${decoder.alternatives.map(x => x.prettyPrint()).join(', ')})`;
if (decoder instanceof DiscriminateOnDecoder) { if (decoder instanceof DiscriminatedDecoder) {
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(${}, ${alternatives})`; return `t.discriminateOn(${JSON.stringify(discriminator)}, ${alternatives})`;
}
if (decoder instanceof HasDecoder) {
return prettyPrint(decoder.toDecoder());
} }
return absurd(decoder); return absurd(decoder);
} }
import * as either from './either'; import * as either from '../either';
import { Either } from './either'; import { Either } from '../either';
import { absurd } from './internal'; import { absurd } from '../types';
// Alias // Alias
...@@ -66,6 +66,14 @@ export class EffBase<Error, Success> { ...@@ -66,6 +66,14 @@ export class EffBase<Error, Success> {
return new Chain(this.toEff(), andThen); return new Chain(this.toEff(), andThen);
} }
run(onNext: (x: Either<Error, Success>) => void, onComplete: () => void): () => void {
return go(this.toEff(), onNext, onComplete);
}
subscribe(onNext: (x: Either<Error, Success>) => void, onComplete: () => void): () => void {
return go(this.toEff(), onNext, onComplete);
}
toEff() { toEff() {
return this as any as Eff<Error, Success>; return this as any as Eff<Error, Success>;
} }
...@@ -197,7 +205,7 @@ export function go<Error, Success>(effect: Eff<Error, Success>, onNext: (x: Eith ...@@ -197,7 +205,7 @@ export function go<Error, Success>(effect: Eff<Error, Success>, onNext: (x: Eith
} }
if (effect instanceof Thunk) { if (effect instanceof Thunk) {
onNext(effect.run.apply(_this, effect.args)); onNext(effect._run.apply(_this, effect.args));
onComplete(); onComplete();
return noopFunc; return noopFunc;
} }
...@@ -295,7 +303,7 @@ export class Pure<Error, Success> extends EffBase<Error, Success> { ...@@ -295,7 +303,7 @@ export class Pure<Error, Success> extends EffBase<Error, Success> {
export class Thunk<Error, Success> extends EffBase<Error, Success> { export class Thunk<Error, Success> extends EffBase<Error, Success> {
constructor( constructor(
readonly run: (...args: unknown[]) => Either<Error, Success>, readonly _run: (...args: unknown[]) => Either<Error, Success>,
readonly args: unknown[], readonly args: unknown[],
) { super(); } ) { super(); }
} }
......
import { Expr } from '~/types'; import { Expr } from '../types';
/** convenient instance methods for `Either` */ /** convenient instance methods for `Either` */
...@@ -183,7 +183,6 @@ export function traverse<L,A,B>(arr: Array<unknown>, f?: Function): Either<L,unk ...@@ -183,7 +183,6 @@ export function traverse<L,A,B>(arr: Array<unknown>, f?: Function): Either<L,unk
} }
export const Either = { export const Either = {
Left, Right, failure, success, left, of, ap, traverse, Left, Right, failure, success, left, of, ap, traverse,
}; };
import { HasEffect, Subscribe, Eff } from './eff'; import { HasEffect, Subscribe, Eff } from '../eff';
import * as eff from './eff'; import * as eff from '../eff';
import { Decoder, Problem } from './decode'; import { Decoder, Problem } from '../decoder';
import { Either } from './either'; import { Either } from '../either';
import * as either from './either'; import * as either from '../either';
/** http method */ /** http method */
...@@ -24,7 +24,7 @@ export interface RequestProgress extends Request { ...@@ -24,7 +24,7 @@ export interface RequestProgress extends Request {
} }
/** raw error */ /** Raw error */
export type HttpError = export type HttpError =
| { tag: 'BadUrl', desc: string } | { tag: 'BadUrl', desc: string }
| { tag: 'BadPayload', desc: string } | { tag: 'BadPayload', desc: string }
...@@ -34,7 +34,7 @@ export type HttpError = ...@@ -34,7 +34,7 @@ export type HttpError =
| { tag: 'NetworkError' } | { tag: 'NetworkError' }
/** responce */ /** Responce */
export interface Response { export interface Response {
url: string; url: string;
status: number; status: number;
...@@ -44,12 +44,12 @@ export interface Response { ...@@ -44,12 +44,12 @@ export interface Response {
} }
/** query params */ /** Query params */
export type ParamsPrimitive = number|string|undefined|null; export type ParamsPrimitive = number|string|undefined|null;
export type Params = Record<string, ParamsPrimitive|ParamsPrimitive[]>; export type Params = Record<string, ParamsPrimitive|ParamsPrimitive[]>;
/** progress */ /** Progress */
export type Progress = export type Progress =
| { tag: 'Computable', total: number, loaded: number } | { tag: 'Computable', total: number, loaded: number }
| { tag: 'Uncomputable' } | { tag: 'Uncomputable' }
...@@ -58,12 +58,44 @@ export type Progress = ...@@ -58,12 +58,44 @@ export type Progress =
export class HttpEffect<A> extends HasEffect<HttpError, A> { export class HttpEffect<A> extends HasEffect<HttpError, A> {
constructor( constructor(
readonly request: Request|RequestProgress, readonly request: Request|RequestProgress,
) { super(); }; ) { super(); };
toEffect() { toEffect() {
return new Subscribe<HttpError, any>((onNext, onComplete) => { return new Subscribe<HttpError, any>((onNext, onComplete) => doXHR(this.request, onNext, onComplete));
const req = this.request; }
}
/** send a request */
export function send(req: RequestProgress): HttpEffect<Either<Progress, Response>>;
export function send(req: Request): HttpEffect<Response>;
export function send(req: Request|RequestProgress): HttpEffect<unknown> {
return new HttpEffect(req);
}
/** shortcut for GET requests */
export function get(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function get(url: string, request?: Omit<Request, 'url'|'method'>): HttpEffect<Response>;
export function get(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>): HttpEffect<unknown> {
return send({ ...request, method: 'GET', url });
}
/** shortcut for POST requests */
export function post(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function post(url: string, request?: Omit<Request, 'url'|'method'>): HttpEffect<Response>;
export function post(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>): HttpEffect<unknown> {
return send({ ...request, method: 'POST', url });
}
/**
* Реализания запроса
*/
export function doXHR(req: Request, onNext: (x: Either<HttpError, Response>) => void, onComplete: () => void): () => void;
export function doXHR(req: RequestProgress, onNext: (x: Either<HttpError, Either<Progress, Response>>) => void, onComplete: () => void): () => void;
export function doXHR(req: Request|RequestProgress, onNext: (x: Either<HttpError, any>) => void, onComplete: () => void): () => void {
const onSuccess = (x: Response) => ('progress' in req && req.progress ? onNext(either.right(either.right(x))) : onNext(either.right(x)), onComplete()); const onSuccess = (x: Response) => ('progress' in req && req.progress ? onNext(either.right(either.right(x))) : onNext(either.right(x)), onComplete());
const onProgress = (x: Progress) => 'progress' in req && req.progress ? onNext(either.right(either.left(x))) : void 0; const onProgress = (x: Progress) => 'progress' in req && req.progress ? onNext(either.right(either.left(x))) : void 0;
const onFailure = (x: HttpError) => (onNext(either.left(x)), onComplete()); const onFailure = (x: HttpError) => (onNext(either.left(x)), onComplete());
...@@ -100,32 +132,6 @@ export class HttpEffect<A> extends HasEffect<HttpError, A> { ...@@ -100,32 +132,6 @@ export class HttpEffect<A> extends HasEffect<HttpError, A> {
const body = Object.prototype.toString.apply(req.body) === '[object Object]' ? JSON.stringify(req.body) : req.body; const body = Object.prototype.toString.apply(req.body) === '[object Object]' ? JSON.stringify(req.body) : req.body;
xhr.send(body); xhr.send(body);
return () => xhr.abort(); return () => xhr.abort();
});
}
}
/** send a request */
export function send(req: RequestProgress): HttpEffect<Either<Progress, Response>>;
export function send(req: Request): HttpEffect<Response>;
export function send(req: Request|RequestProgress): HttpEffect<unknown> {
return new HttpEffect(req);
}
/** shortcut for GET requests */
export function get(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function get(url: string, request?: Omit<Request, 'url'|'method'>): HttpEffect<Response>;
export function get(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>): HttpEffect<unknown> {
return send({ ...request, method: 'GET', url });
}
/** shortcut for POST requests */
export function post(url: string, request?: Omit<RequestProgress, 'url'|'method'>): HttpEffect<Either<Progress, Response>>;
export function post(url: string, request?: Omit<Request, 'url'|'method'>): HttpEffect<Response>;
export function post(url: string, request?: Omit<Request|RequestProgress, 'url'|'method'>): HttpEffect<unknown> {
return send({ ...request, method: 'POST', url });
} }
...@@ -146,7 +152,7 @@ export function expectJSON<A>(decoder: Decoder<A>): (resp: Response) => Eff<Http ...@@ -146,7 +152,7 @@ export function expectJSON<A>(decoder: Decoder<A>): (resp: Response) => Eff<Http
} }
/** parse headers from string to dict */ /** Parse headers from string to dict */
function parseHeaders(rawHeaders: string): Record<string, string> { function parseHeaders(rawHeaders: string): Record<string, string> {
const output = {}; const output = {};
const lines = rawHeaders.split('\r\n'); const lines = rawHeaders.split('\r\n');
...@@ -161,7 +167,7 @@ function parseHeaders(rawHeaders: string): Record<string, string> { ...@@ -161,7 +167,7 @@ function parseHeaders(rawHeaders: string): Record<string, string> {
} }
/** build an url */ /** Build an url */
export function join(...args: Array<string|Params>): string { export function join(...args: Array<string|Params>): string {
let path = ''; let path = '';
let params = {} as Record<string, string>; let params = {} as Record<string, string>;
......
import { Expr } from './internal/expr'; import { Expr } from '../types';
/** adt */ /** adt */
...@@ -7,7 +7,7 @@ export type Maybe<A> = Option<A> ...@@ -7,7 +7,7 @@ export type Maybe<A> = Option<A>
/** instance method for convenience */ /** instance method for convenience */
export class OptionBase<A> { export class MaybeBase<A> {
readonly _A: A; readonly _A: A;
/** map */ /** map */
...@@ -63,14 +63,14 @@ export class OptionBase<A> { ...@@ -63,14 +63,14 @@ export class OptionBase<A> {
/** empty container */ /** empty container */
export class None<A> extends OptionBase<A> { export class None<A> extends MaybeBase<A> {
readonly _a: A; readonly _a: A;
readonly tag: 'None' = 'None'; readonly tag: 'None' = 'None';
} }
/** container with a value */ /** container with a value */
export class Some<A> extends OptionBase<A> { export class Some<A> extends MaybeBase<A> {
readonly _a: A; readonly _a: A;
readonly tag: 'Some' = 'Some'; readonly tag: 'Some' = 'Some';
constructor( constructor(
......
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