@thisisagile/easy
Version:
Straightforward library for building domain-driven microservice architectures
156 lines (118 loc) • 3.93 kB
text/typescript
import { isDefined, isEmpty, isTrue } from './Is';
import { validate } from '../validation/Validate';
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<T = unknown> implements Validatable {
is = {
defined: (prop?: Func<unknown, T>): Try<T> => this.filter(v => isDefined(prop ? prop(v) : v)),
empty: (prop?: Func<unknown, T>): Try<T> => this.filter(v => isEmpty(prop ? prop(v) : v)),
valid: (prop?: Func<unknown, T>): Try<T> => this.filter(v => validate(prop ? prop(v) : v).isValid),
true: (prop?: Func<unknown, T>): Try<T> => this.filter(v => isTrue(prop ? prop(v) : v)),
false: (prop?: Func<unknown, T>): Try<T> => this.filter(v => !isTrue(prop ? prop(v) : v)),
not: {
defined: (prop?: Func<unknown, T>): Try<T> => this.filter(v => !isDefined(prop ? prop(v) : v)),
empty: (prop?: Func<unknown, T>): Try<T> => this.filter(v => !isEmpty(prop ? prop(v) : v)),
valid: (prop?: Func<unknown, T>): Try<T> => this.filter(v => !validate(prop ? prop(v) : v).isValid),
},
};
if = this.is;
abstract get value(): T;
abstract get error(): Error;
abstract get isValid(): boolean;
static of = <T>(c: Get<T | Try<T>>, ...args: unknown[]): Try<T> => {
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<U>(f: Func<U | Try<U>, T>): Try<U>;
abstract recover(f: Func<T | Try<T>, Error>): Try<T>;
abstract recoverFrom(type: Constructor<Error>, f: Func<T | Try<T>, Error>): Try<T>;
abstract accept(f: Func<void, T>): Try<T>;
abstract filter(predicate: Func<boolean, T>): Try<T>;
abstract or(value: Get<T>): T;
abstract orElse(value?: Get<T>): Optional<T>;
abstract orThrow(error: Construct<Error>): T;
}
class Success<T> extends Try<T> {
constructor(readonly value: T) {
super();
}
get error(): Error {
throw new Error('No error found');
}
get isValid(): boolean {
return true;
}
map<U>(f: Func<U | Try<U>, T>): Try<U> {
return tryTo<U>(f, this.value);
}
recover(f: Func<T | Try<T>, Error>): Try<T> {
return this;
}
recoverFrom(type: Constructor<Error>, f: Func<T | Try<T>, Error>): Try<T> {
return this;
}
accept(f: Func<void, T>): Try<T> {
return tryTo(() => {
f(this.value);
return this;
});
}
filter(predicate: Func<boolean, T>): Try<T> {
return tryTo(() => {
if (predicate(this.value)) return this;
throw new Error(`Applying filter(${predicate.toString()}) failed.`);
});
}
or(value: Get<T>): T {
return this.value;
}
orElse(value?: Get<T>): Optional<T> {
return this.value;
}
orThrow(_error: Construct<Error>): T {
return this.value;
}
}
class Failure<T> extends Try<T> {
constructor(readonly error: Error) {
super();
}
get value(): T {
throw this.error;
}
get isValid(): boolean {
return false;
}
map<U>(f: Func<U | Try<U>, T>): Try<U> {
return new Failure<U>(this.error);
}
recover<U>(f: Func<U | Try<U>, Error>): Try<U> {
return tryTo<U>(f, this.error);
}
recoverFrom<U>(type: Constructor<Error>, f: Func<T | Try<T>, Error>): Try<T> {
return tryTo(() => (this.error instanceof type ? this.recover(f) : this));
}
accept(f: Func<void, T>): Try<T> {
return this;
}
filter(predicate: Func<boolean, T>): Try<T> {
return this;
}
or(value: Get<T>): T {
return ofGet(value);
}
orElse(value?: Get<T>): Optional<T> {
return ofGet(value);
}
orThrow(error: Construct<Error>): T {
throw ofConstruct(error);
}
}
export const tryTo = <T>(c: Get<T | Try<T>>, ...args: unknown[]) => Try.of<T>(c, ...args);