import { isDefined, isEmpty, isTrue } from './Is'; import { isValid } from '../validation/IsValid'; import { Construct, Constructor, ofConstruct } from './Constructor'; import { Validatable } from './Validatable'; import { Get, ofGet } from './Get'; import { Func } from './Func'; import { Optional } from './Types'; export abstract class Try implements Validatable { is = { defined: (prop?: Func): Try => this.filter(v => isDefined(prop ? prop(v) : v)), empty: (prop?: Func): Try => this.filter(v => isEmpty(prop ? prop(v) : v)), valid: (prop?: Func): Try => this.filter(v => isValid(prop ? prop(v) : v)), true: (prop?: Func): Try => this.filter(v => isTrue(prop ? prop(v) : v)), false: (prop?: Func): Try => this.filter(v => !isTrue(prop ? prop(v) : v)), not: { defined: (prop?: Func): Try => this.filter(v => !isDefined(prop ? prop(v) : v)), empty: (prop?: Func): Try => this.filter(v => !isEmpty(prop ? prop(v) : v)), valid: (prop?: Func): Try => this.filter(v => !isValid(prop ? prop(v) : v)), }, }; if = this.is; abstract get value(): T; abstract get error(): Error; abstract get isValid(): boolean; static of = (c: Get>, ...args: unknown[]): Try => { try { const out = ofGet(c, ...args); return new Success(out instanceof Try ? out.value : out); } catch (e) { return new Failure(e as Error); } }; abstract map(f: Func, T>): Try; abstract recover(f: Func, Error>): Try; abstract recoverFrom(type: Constructor, f: Func, Error>): Try; abstract accept(f: Func): Try; abstract filter(predicate: Func): Try; abstract or(value: Get): T; abstract orElse(value?: Get): Optional; abstract orThrow(error: Construct): T; } class Success extends Try { constructor(readonly value: T) { super(); } get error(): Error { throw new Error('No error found'); } get isValid(): boolean { return true; } map(f: Func, T>): Try { return tryTo(f, this.value); } recover(f: Func, Error>): Try { return this; } recoverFrom(type: Constructor, f: Func, Error>): Try { return this; } accept(f: Func): Try { return tryTo(() => { f(this.value); return this; }); } filter(predicate: Func): Try { return tryTo(() => { if (predicate(this.value)) return this; throw new Error(`Applying filter(${predicate.toString()}) failed.`); }); } or(value: Get): T { return this.value; } orElse(value?: Get): Optional { return this.value; } orThrow(_error: Construct): T { return this.value; } } class Failure extends Try { constructor(readonly error: Error) { super(); } get value(): T { throw this.error; } get isValid(): boolean { return false; } map(f: Func, T>): Try { return new Failure(this.error); } recover(f: Func, Error>): Try { return tryTo(f, this.error); } recoverFrom(type: Constructor, f: Func, Error>): Try { return tryTo(() => (this.error instanceof type ? this.recover(f) : this)); } accept(f: Func): Try { return this; } filter(predicate: Func): Try { return this; } or(value: Get): T { return ofGet(value); } orElse(value?: Get): Optional { return ofGet(value); } orThrow(error: Construct): T { throw ofConstruct(error); } } export const tryTo = (c: Get>, ...args: unknown[]) => Try.of(c, ...args);