Skip to content

Commit

Permalink
feat(operators): make each operator a standlone class to facilitate q…
Browse files Browse the repository at this point in the history
…uery parsing and validation
  • Loading branch information
khaledosama999 committed Mar 28, 2021
1 parent 0f35a9a commit 64df123
Show file tree
Hide file tree
Showing 21 changed files with 229 additions and 103 deletions.
6 changes: 6 additions & 0 deletions src/operators/abstract/base.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
abstract class Operator {
protected readonly operator

constructor(operator:string) {
this.operator = operator;
}

abstract exec():string

abstract validate ():void
Expand Down
8 changes: 6 additions & 2 deletions src/operators/abstract/logical-operator.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import Operator from './base';

abstract class LogicalOperator extends Operator {
constructor(protected values: Operator []) {
super();
constructor(protected values: Operator [], operator: string) {
super(operator);
}

public validate() {
this.values.forEach((operator) => operator.validate());
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/operators/abstract/numeric-operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import Operator from './base';

abstract class NumericOperator extends Operator {
constructor(protected fieldName: string, protected value: number) {
super();
constructor(protected fieldName: string, protected value: number, operator:string) {
super(operator);
}

public validate() {
return true;
if (typeof this.value !== 'number') { throw new Error(`The ${this.operator} accepts numeric values only`); }
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/operators/abstract/range-operator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import Operator from './base';

abstract class RangeOperator extends Operator {
constructor(protected fieldName: string, protected value: [number, number]) {
super();
constructor(protected fieldName: string, protected value: [number, number], operator:string) {
super(operator);
}

public validate() {
return true;
const allAreNumeric = this.value.every((number) => typeof number === 'number');

if (!allAreNumeric) throw new Error(`The ${this.operator} accepts only array of numeric values`);
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/operators/and.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
/* eslint-disable constructor-super */
/* eslint-disable class-methods-use-this */
import { LogicalOperator } from './abstract';
import { LogicalOperator, Operator } from './abstract';

class AndOperator extends LogicalOperator {
constructor(protected values: Operator []) {
super(values, 'and');
}

public exec() {
return this.values
.map((operator) => operator.exec())
.join(' AND ');
}

public validate() {
return true;
}
}

export default AndOperator;
4 changes: 4 additions & 0 deletions src/operators/between.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import { RangeOperator } from './abstract';

class BetweenOperator extends RangeOperator {
constructor(protected fieldName: string, protected value: [number, number]) {
super(fieldName, value, 'between');
}

exec() {
return `${this.fieldName}: ${this.value[0]} TO ${this.value[1]}`;
}
Expand Down
9 changes: 9 additions & 0 deletions src/operators/equal-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NumericEqual } from './index';
import NonNumericEqual from './no-numeric-equal';

const EqualFactory = (fieldName:string, value: string| number) => {
if (typeof value === 'number') return new NumericEqual(fieldName, value);
return new NonNumericEqual(fieldName, value);
};

export default EqualFactory;
21 changes: 0 additions & 21 deletions src/operators/equal.ts

This file was deleted.

4 changes: 4 additions & 0 deletions src/operators/greater-than-or-equal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import NumericOperator from './abstract/numeric-operator';

class GreaterThanOrEqualOperator extends NumericOperator {
constructor(protected fieldName: string, protected value: number) {
super(fieldName, value, '>=');
}

exec() {
return `${this.fieldName} >= ${this.value}`;
}
Expand Down
4 changes: 4 additions & 0 deletions src/operators/greater-than.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import NumericOperator from './abstract/numeric-operator';

class GreaterThanOperator extends NumericOperator {
constructor(protected fieldName: string, protected value: number) {
super(fieldName, value, '>');
}

exec() {
return `${this.fieldName} > ${this.value}`;
}
Expand Down
2 changes: 1 addition & 1 deletion src/operators/in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import AndOperator from './and';

class InOperator extends Operator {
constructor(private value: Operator[]) {
super();
super('in');
}

public exec() {
Expand Down
4 changes: 3 additions & 1 deletion src/operators/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export { default as AndOperator } from './and';
export { default as BetweenOperator } from './between';
export { default as EqualOperator } from './equal';
export { default as NumericEqual } from './numeric-equal';
export { default as NonNumericEqual } from './no-numeric-equal';
export { default as EqualFactory } from './equal-factory';
export { default as GreaterThanOrEqualOperator } from './greater-than-or-equal';
export { default as GreaterThanOperator } from './greater-than';
export { default as LessThanOrEqualOperator } from './less-than-or-equal';
Expand Down
4 changes: 4 additions & 0 deletions src/operators/less-than-or-equal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import NumericOperator from './abstract/numeric-operator';

class LessThanOrEqualOperator extends NumericOperator {
constructor(protected fieldName: string, protected value: number) {
super(fieldName, value, '<=');
}

exec() {
return `${this.fieldName} <= ${this.value}`;
}
Expand Down
4 changes: 4 additions & 0 deletions src/operators/less-than.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import NumericOperator from './abstract/numeric-operator';

class LessThanOperator extends NumericOperator {
constructor(protected fieldName: string, protected value: number) {
super(fieldName, value, '<=');
}

exec() {
return `${this.fieldName} < ${this.value}`;
}
Expand Down
18 changes: 18 additions & 0 deletions src/operators/no-numeric-equal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable class-methods-use-this */
import { Operator } from './abstract';

class NonNumericEqual extends Operator {
constructor(private fieldName:string, private value:string) {
super('eq');
}

exec() {
return `${this.fieldName} : "${this.value}"`;
}

validate() {
return true;
}
}

export default NonNumericEqual;
4 changes: 4 additions & 0 deletions src/operators/not-equal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import NumericOperator from './abstract/numeric-operator';

class NotEqualOperator extends NumericOperator {
constructor(protected fieldName: string, protected value: number) {
super(fieldName, value, '!=');
}

exec() {
return `${this.fieldName} != ${this.value}`;
}
Expand Down
8 changes: 5 additions & 3 deletions src/operators/not.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* eslint-disable constructor-super */
/* eslint-disable class-methods-use-this */
import { Operator } from './abstract';
import { LogicalOperator, Operator } from './abstract';

class NotOperator extends Operator {
constructor(private values: Operator []) {
super();
super('not');
}

public exec() {
Expand All @@ -17,7 +17,9 @@ class NotOperator extends Operator {
}

public validate() {
return true;
const hasLogicalOperator = this.values.some((operator) => operator instanceof LogicalOperator);

if (hasLogicalOperator) throw new Error(`The ${this.operator} can not contain logical operators (and, or)`);
}
}

Expand Down
14 changes: 14 additions & 0 deletions src/operators/numeric-equal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/* eslint-disable class-methods-use-this */
import { NumericalOperator } from './abstract';

class NumericEqual extends NumericalOperator {
constructor(fieldName:string, value: number) {
super(fieldName, value, 'eq');
}

exec() {
return `${this.fieldName} = ${this.value}`;
}
}

export default NumericEqual;
39 changes: 37 additions & 2 deletions src/operators/or.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
/* eslint-disable constructor-super */
/* eslint-disable class-methods-use-this */
import { LogicalOperator } from './abstract';
import { LogicalOperator, Operator } from './abstract';
import NumericOperator from './abstract/numeric-operator';
import AndOperator from './and';
import BetweenOperator from './between';
import InOperator from './in';
import NonNumericEqual from './no-numeric-equal';
import TagOperator from './tag';

class OrOperator extends LogicalOperator {
constructor(protected values: Operator []) {
super(values, 'or');
}

public exec() {
const orQuery = this.values
.map((operator) => operator.exec())
Expand All @@ -13,7 +23,32 @@ class OrOperator extends LogicalOperator {
}

public validate() {
return true;
// So far algolia doesn't support ands inside of ors
const hasAnd = this.values.some((value) => value instanceof AndOperator);
if (hasAnd) throw new Error(`The ${this.operator} can not contain an and operator`);

// A list of or-ed commands cannot have mix of tags, facet search and numeric operators
const numericOperationsBucket = [NumericOperator, InOperator, BetweenOperator];
const facetOperationsBucket = [NonNumericEqual];
const tagsOperationsBucket = [TagOperator];

const operationsBuckets = [numericOperationsBucket, facetOperationsBucket, tagsOperationsBucket];

const operationsBucketSet = new Set();

this.values.forEach((operator) => {
operationsBuckets.forEach((bucket) => {
bucket.forEach((operatorType:any) => {
if (operator instanceof operatorType) operationsBucketSet.add(bucket);
});
});
});

if (operationsBucketSet.size > 1) {
throw new Error(`The ${this.operator} cannot contain a mix of numeric, facet and tags operations`);
}

super.validate();
}
}

Expand Down
18 changes: 18 additions & 0 deletions src/operators/tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/* eslint-disable class-methods-use-this */
import { Operator } from './abstract';

class TagOperator extends Operator {
constructor(private value:string | number) {
super('_tags');
}

exec() {
return `_tags : "${this.value}"`;
}

validate() {
return true;
}
}

export default TagOperator;

0 comments on commit 64df123

Please sign in to comment.