133 lines
3.7 KiB
TypeScript
133 lines
3.7 KiB
TypeScript
export interface IBaseSpecification<T> {
|
|
isSatisfiedBy(candidate: T): Promise<boolean>;
|
|
}
|
|
|
|
export interface ICompositeSpecification<T> extends IBaseSpecification<T> {
|
|
and(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
|
andNot(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
|
or(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
|
orNot(other: ICompositeSpecification<T>): ICompositeSpecification<T>;
|
|
not(): ICompositeSpecification<T>;
|
|
}
|
|
|
|
export abstract class CompositeSpecification<T> implements ICompositeSpecification<T> {
|
|
abstract isSatisfiedBy(candidate: T): Promise<boolean>;
|
|
|
|
public and(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
|
return new AndSpecification<T>(this, other);
|
|
}
|
|
|
|
public andNot(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
|
return new AndNotSpecification<T>(this, other);
|
|
}
|
|
|
|
public or(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
|
return new OrSpecification<T>(this, other);
|
|
}
|
|
|
|
public orNot(other: ICompositeSpecification<T>): ICompositeSpecification<T> {
|
|
return new OrNotSpecification<T>(this, other);
|
|
}
|
|
|
|
public not(): ICompositeSpecification<T> {
|
|
return new NotSpecification<T>(this);
|
|
}
|
|
}
|
|
|
|
class AndSpecification<T> extends CompositeSpecification<T> {
|
|
constructor(
|
|
private readonly left: ICompositeSpecification<T>,
|
|
private readonly right: ICompositeSpecification<T>
|
|
) {
|
|
super();
|
|
}
|
|
|
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
|
const leftResult = await this.left.isSatisfiedBy(candidate);
|
|
if (!leftResult) return false;
|
|
|
|
const rightResult = await this.right.isSatisfiedBy(candidate);
|
|
return rightResult;
|
|
}
|
|
|
|
toString(): string {
|
|
return `(${this.left.toString()} and ${this.right.toString()})`;
|
|
}
|
|
}
|
|
|
|
class AndNotSpecification<T> extends AndSpecification<T> {
|
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
|
return (await super.isSatisfiedBy(candidate)) !== true;
|
|
}
|
|
|
|
toString(): string {
|
|
return `not ${super.toString()}`;
|
|
}
|
|
}
|
|
|
|
class OrSpecification<T> extends CompositeSpecification<T> {
|
|
constructor(
|
|
private readonly left: ICompositeSpecification<T>,
|
|
private readonly right: ICompositeSpecification<T>
|
|
) {
|
|
super();
|
|
}
|
|
|
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
|
const leftResult = await this.left.isSatisfiedBy(candidate);
|
|
if (leftResult) return true;
|
|
|
|
const rightResult = await this.right.isSatisfiedBy(candidate);
|
|
return rightResult;
|
|
}
|
|
|
|
toString(): string {
|
|
return `(${this.left.toString()} or ${this.right.toString()})`;
|
|
}
|
|
}
|
|
|
|
class OrNotSpecification<T> extends OrSpecification<T> {
|
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
|
return (await super.isSatisfiedBy(candidate)) !== true;
|
|
}
|
|
|
|
toString(): string {
|
|
return `not ${super.toString()}`;
|
|
}
|
|
}
|
|
|
|
export class NotSpecification<T> extends CompositeSpecification<T> {
|
|
constructor(private readonly spec: ICompositeSpecification<T>) {
|
|
super();
|
|
}
|
|
|
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
|
return !this.spec.isSatisfiedBy(candidate);
|
|
}
|
|
|
|
toString(): string {
|
|
return `(not ${this.spec.toString()})`;
|
|
}
|
|
}
|
|
|
|
export class RangeSpecification<T> extends CompositeSpecification<T> {
|
|
private readonly min: T;
|
|
private readonly max: T;
|
|
private readonly compareFn: (a: T, b: T) => number;
|
|
|
|
constructor(min: T, max: T, compareFn: (a: T, b: T) => number) {
|
|
super();
|
|
this.min = min;
|
|
this.max = max;
|
|
this.compareFn = compareFn;
|
|
}
|
|
|
|
public async isSatisfiedBy(candidate: T): Promise<boolean> {
|
|
return this.compareFn(candidate, this.min) >= 0 && this.compareFn(candidate, this.max) <= 0;
|
|
}
|
|
|
|
toString(): string {
|
|
return `range [${this.min}, ${this.max}]`;
|
|
}
|
|
}
|