From d2edce6290a5d649d6cb0eb195a042ada15516de Mon Sep 17 00:00:00 2001 From: StevanFreeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sun, 12 Feb 2023 15:52:47 -0600 Subject: [PATCH] feat: added model to represent and construct filters when querying for records --- src/enums/FilterOperators.ts | 76 ++++++++++++++++---- src/models/QueryFilter.ts | 43 ++++++++++++ src/models/QueryRecordsRequest.ts | 111 +++++++++++++++--------------- tests/QueryFilter.spec.ts | 94 +++++++++++++++++++++++++ 4 files changed, 257 insertions(+), 67 deletions(-) create mode 100644 src/models/QueryFilter.ts create mode 100644 tests/QueryFilter.spec.ts diff --git a/src/enums/FilterOperators.ts b/src/enums/FilterOperators.ts index 6ca881c..5210291 100644 --- a/src/enums/FilterOperators.ts +++ b/src/enums/FilterOperators.ts @@ -1,12 +1,64 @@ -export enum FilterOperators { - Equal = 'eq', - NotEqual = 'ne', - Contains = 'contains', - IsNull = 'isnull', - NotNull = 'notnull', - GreaterThan = 'gt', - LessThan = 'lt', - And = 'and', - Or = 'or', - Not = 'not', -} +/** + * @enum FilterOperators - The operators that can be used in a query filter + */ +export enum FilterOperators { + /** + * @constant Equal - The equal operator + * @remarks Can be used with text, auto-number, date, and number fields as well as their formula equivalents. + */ + Equal = 'eq', + + /** + * @constant NotEqual - The not equal operator + * @remarks Can be used with text, auto-number, date, and number fields as well as their formula equivalents. + */ + NotEqual = 'ne', + + /** + * @constant Contains - The contains operator + * @remarks Can be used with list fields as well as their formula equivalents. + */ + Contains = 'contains', + + /** + * @constant IsNull - The is null operator + * @remarks Can be used with text, date, and number fields as well as their formula equivalents. + */ + IsNull = 'isnull', + + /** + * @constant NotNull - The not null operator + * @remarks Can be used with text, date, and number fields as well as their formula equivalents. + */ + NotNull = 'notnull', + + /** + * @constant GreaterThan - The greater than operator + * @remarks Can be used with auto-number, date, and number fields as well as their formula equivalents. + */ + GreaterThan = 'gt', + + /** + * @constant LessThan - The less than operator + * @remarks Can be used with auto-number, date, and number fields as well as their formula equivalents. + */ + LessThan = 'lt', + + /** + * @constant And - The and operator + * @remarks Can be used to create compound filters. + */ + And = 'and', + + /** + * @constant Or - The or operator + * @remarks Can be used to create compound filters. + */ + Or = 'or', + + /** + * @constant Not - The not operator + * @remarks Can be used to negate a filter and create compound filters. + */ + Not = 'not', +} diff --git a/src/models/QueryFilter.ts b/src/models/QueryFilter.ts new file mode 100644 index 0000000..8bc6e49 --- /dev/null +++ b/src/models/QueryFilter.ts @@ -0,0 +1,43 @@ +import { FilterOperators } from '../enums/FilterOperators'; + +export class QueryFilter { + public fieldId: number; + public operator: FilterOperators; + public value: string | number | Date | null; + + constructor( + fieldId: number, + operator: FilterOperators, + value: string | number | Date | null + ) { + if ( + value === null && + operator !== FilterOperators.IsNull && + operator !== FilterOperators.NotNull + ) { + throw new Error('Value cannot be null for this operator'); + } + + this.fieldId = fieldId; + this.operator = operator; + this.value = value; + } + + public toString(): string { + if (this.value == null) { + return `${this.fieldId} ${this.operator}`; + } + + if (this.value instanceof Date) { + return `${this.fieldId} ${ + this.operator + } datetime'${this.value.toISOString()}'`; + } + + if (typeof this.value === 'string') { + return `${this.fieldId} ${this.operator} '${this.value}'`; + } + + return `${this.fieldId} ${this.operator} ${this.value}`; + } +} diff --git a/src/models/QueryRecordsRequest.ts b/src/models/QueryRecordsRequest.ts index 361ad0a..fec0562 100644 --- a/src/models/QueryRecordsRequest.ts +++ b/src/models/QueryRecordsRequest.ts @@ -1,55 +1,56 @@ -import { DataFormat } from '../enums/DataFormat'; -import { PagingRequest } from './PagingRequest'; - -/** - * @class QueryRecordsRequest - Request to query for records. - */ -export class QueryRecordsRequest { - /** - * @property {number} appId - The id of the app the records belong to. - */ - appId: number; - - /** - * @property {string} filter - The filter to use to query for records. - */ - filter: string; - - /** - * @property {number[]} fieldIds - The ids of the fields to include in the response. - */ - fieldIds: number[]; - - /** - * @property {DataFormat} dataFormat - The format of the data in the response. - */ - dataFormat: DataFormat; - - /** - * @property {PagingRequest} pagingRequest - The paging request to use to query for records. - */ - pagingRequest: PagingRequest; - - /** - * @constructor - Creates a new instance of QueryRecordsRequest - * @param {number} appId - The id of the app the records belong to. - * @param {string} filter - The filter to use to query for records. - * @param {number[]} fieldIds - The ids of the fields to include in the response. - * @param {DataFormat} dataFormat - The format of the data in the response. - * @param {PagingRequest} pagingRequest - The paging request to use to query for records. - * @returns {QueryRecordsRequest} - A new instance of QueryRecordsRequest - */ - constructor( - appId: number, - filter: string, - fieldIds: number[] = [], - dataFormat: DataFormat = DataFormat.Raw, - pagingRequest: PagingRequest = new PagingRequest(1, 50) - ) { - this.appId = appId; - this.filter = filter; - this.fieldIds = fieldIds; - this.dataFormat = dataFormat; - this.pagingRequest = pagingRequest; - } -} +import { DataFormat } from '../enums/DataFormat'; +import { PagingRequest } from './PagingRequest'; +import { type QueryFilter } from './QueryFilter'; + +/** + * @class QueryRecordsRequest - Request to query for records. + */ +export class QueryRecordsRequest { + /** + * @property {number} appId - The id of the app the records belong to. + */ + appId: number; + + /** + * @property {string | QueryFilter} filter - The filter to use to query for records. + */ + filter: string | QueryFilter; + + /** + * @property {number[]} fieldIds - The ids of the fields to include in the response. + */ + fieldIds: number[]; + + /** + * @property {DataFormat} dataFormat - The format of the data in the response. + */ + dataFormat: DataFormat; + + /** + * @property {PagingRequest} pagingRequest - The paging request to use to query for records. + */ + pagingRequest: PagingRequest; + + /** + * @constructor - Creates a new instance of QueryRecordsRequest + * @param {number} appId - The id of the app the records belong to. + * @param {string | QueryFilter} filter - The filter to use to query for records. + * @param {number[]} fieldIds - The ids of the fields to include in the response. + * @param {DataFormat} dataFormat - The format of the data in the response. + * @param {PagingRequest} pagingRequest - The paging request to use to query for records. + * @returns {QueryRecordsRequest} - A new instance of QueryRecordsRequest + */ + constructor( + appId: number, + filter: string | QueryFilter, + fieldIds: number[] = [], + dataFormat: DataFormat = DataFormat.Raw, + pagingRequest: PagingRequest = new PagingRequest(1, 50) + ) { + this.appId = appId; + this.filter = filter.toString(); + this.fieldIds = fieldIds; + this.dataFormat = dataFormat; + this.pagingRequest = pagingRequest; + } +} diff --git a/tests/QueryFilter.spec.ts b/tests/QueryFilter.spec.ts new file mode 100644 index 0000000..cb42263 --- /dev/null +++ b/tests/QueryFilter.spec.ts @@ -0,0 +1,94 @@ +import { QueryFilter } from '../src/models/QueryFilter'; +import { expect } from 'chai'; +import { FilterOperators } from '../src/enums/FilterOperators'; + +describe('QueryFilter', function () { + it('should be defined', function () { + expect(QueryFilter).to.not.be.undefined; + }); + + it('should have a constructor', function () { + expect(QueryFilter).to.have.property('constructor'); + }); + + it('should have a constructor that takes 3 parameters', function () { + expect(QueryFilter).to.have.lengthOf(3); + }); + + it('should have a constructor that throws an error if value is null and operator is not IsNull or NotNull', function () { + expect(() => new QueryFilter(1, FilterOperators.Equal, null)).to.throw( + 'Value cannot be null for this operator' + ); + }); + + it('should have a fieldId property', function () { + expect(new QueryFilter(1, FilterOperators.Equal, 'test')).to.have.property( + 'fieldId' + ); + }); + + it('should have an operator property', function () { + expect(new QueryFilter(1, FilterOperators.Equal, 'test')).to.have.property( + 'operator' + ); + }); + + it('should have a value property', function () { + expect(new QueryFilter(1, FilterOperators.Equal, 'test')).to.have.property( + 'value' + ); + }); + + it('should have a constructor that sets its properties correctly', function () { + const fieldId = 1; + const operator = FilterOperators.Equal; + const value = 'test'; + + const filter = new QueryFilter(fieldId, operator, value); + + expect(filter).to.have.property('fieldId', fieldId); + expect(filter).to.have.property('operator', operator); + expect(filter).to.have.property('value', value); + }); + + describe('toString', function () { + it('should be defined', function () { + expect(QueryFilter.prototype.toString).to.not.be.undefined; + }); + + it('should be a function', function () { + expect(QueryFilter.prototype.toString).to.be.a('function'); + }); + + it('should return a string', function () { + expect( + new QueryFilter(1, FilterOperators.Equal, 'test').toString() + ).to.be.a('string'); + }); + + it('should return a properly constructed filter string if value is null', function () { + expect( + new QueryFilter(1, FilterOperators.IsNull, null).toString() + ).to.equal('1 isnull'); + }); + + it('should return a properly constructed filter string if value is a Date', function () { + const date = new Date(); + expect( + new QueryFilter(1, FilterOperators.Equal, date).toString() + ).to.equal(`1 eq datetime'${date.toISOString()}'`); + }); + + it('should return a properly constructed filter string if value is a string', function () { + expect( + new QueryFilter(1, FilterOperators.Equal, 'test').toString() + ).to.equal("1 eq 'test'"); + }); + + it('should return a properly constructed filter string if value is a number', function () { + expect(new QueryFilter(1, FilterOperators.Equal, 1).toString()).to.equal( + '1 eq 1' + ); + }); + }); +});