diff --git a/src/query/createFilter.ts b/src/query/createFilter.ts index 61b5d99..bdddbfc 100644 --- a/src/query/createFilter.ts +++ b/src/query/createFilter.ts @@ -26,7 +26,20 @@ export const enum BooleanOp { Or } -export type FilterChainMember = (SimpleFilter | BooleanOp); +function isBooleanOp(op: any): op is BooleanOp { + return op === BooleanOp.And || op === BooleanOp.Or; +} +export type FilterChainMember = SimpleFilter | BooleanOp; + +export interface FilterDescriptor { + readonly filterType: FilterType; + readonly path: ObjectPointer; + readonly value: any; +} + +export type FilterArrayEntry = FilterDescriptor | BooleanOp | FilterArray; + +interface FilterArray extends Array {} export interface SimpleFilter extends Query { readonly filterType: FilterType; @@ -35,6 +48,7 @@ export interface SimpleFilter extends Query { readonly path?: ObjectPointer; readonly value?: any; } + export interface BooleanFilter extends SimpleFilter { lessThan(path: ObjectPointer, value: number): Filter; lessThanOrEqualTo(path: ObjectPointer, value: number): Filter; @@ -63,9 +77,42 @@ function isFilter(filterOrFunction: FilterChainMember): filterOrFunction i return typeof filterOrFunction !== 'function' && ( filterOrFunction).apply; } -function createFilter(serializer?: (filter: Filter) => string): Filter { - // var subFilters: NestedFilter = subFilters || []; +function createFilterOrReturnOp(descriptorOrOp: FilterDescriptor | BooleanOp) { + if (isBooleanOp(descriptorOrOp)) { + return descriptorOrOp; + } + else { + return createComparator( + descriptorOrOp.filterType, + descriptorOrOp.value, + descriptorOrOp.path + ); + } +} + +function createFilter(filterDescriptors?: FilterDescriptor | FilterArray, serializer?: (filter: Filter) => string): Filter { let filters: FilterChainMember[] = []; + if (filterDescriptors) { + if (Array.isArray(filterDescriptors)) { + filters = filterDescriptors.map((descriptorChainMember) => { + if (Array.isArray(descriptorChainMember)) { + return createFilter(descriptorChainMember); + } + else { + return createFilterOrReturnOp(descriptorChainMember); + } + }); + } + else { + filters.push( + createComparator( + filterDescriptors.filterType, + filterDescriptors.value, + filterDescriptors.path + ) + ); + } + } return createFilterHelper(filters, serializer || serializeFilter); } diff --git a/tests/unit/query/createFilter.ts b/tests/unit/query/createFilter.ts index 7dbfaa8..7377ad5 100644 --- a/tests/unit/query/createFilter.ts +++ b/tests/unit/query/createFilter.ts @@ -260,6 +260,102 @@ registerSuite({ } }, + 'from objects': { + 'nested'() { + const individualFilter = { + filterType: FilterType.EqualTo, + value: 5, + path: createJsonPointer('key', 'key2') + }; + const pickFirstItem = [ + { + filterType: FilterType.LessThanOrEqualTo, + value: 5, + path: createJsonPointer('key', 'key2') + }, + BooleanOp.And, + { + filterType: FilterType.EqualTo, + value: 'item-1', + path: 'id' + }, + BooleanOp.Or, + { + filterType: FilterType.GreaterThanOrEqualTo, + value: 5, + path: createJsonPointer('key', 'key2') + }, + { + filterType: FilterType.EqualTo, + value: 'item-1', + path: 'id' + }, + BooleanOp.Or, + { + filterType: FilterType.GreaterThan, + value: 5, + path: createJsonPointer('key', 'key2') + }, + { + filterType: FilterType.EqualTo, + value: 'item-1', + path: 'id' + } + ]; + + const pickAllItems = [ + { + filterType: FilterType.LessThan, + value: 100, + path: createJsonPointer('key', 'key2') + } + ]; + + const pickNoItems = [ + { + filterType: FilterType.GreaterThan, + value: 100, + path: createJsonPointer('key', 'key2') + } + ]; + + const pickLastItem = [ + { + filterType: FilterType.EqualTo, + value: '3', + path: 'id' + } + ]; + + assert.deepEqual(createFilter(pickFirstItem).apply(nestedList), [ nestedList[0] ], 'Should pick first item'); + assert.deepEqual(createFilter(pickAllItems).apply(nestedList), nestedList, 'Should pick all items'); + assert.deepEqual(createFilter(pickNoItems).apply(nestedList), [], 'Should pick no items'); + assert.deepEqual(createFilter(pickLastItem).apply(nestedList), [ nestedList[2] ], 'Should pick last item'); + assert.deepEqual( + createFilter([ pickFirstItem, BooleanOp.And, pickLastItem ]).apply(nestedList), + [], + 'Shouldn\'t pick any items' + ); + assert.deepEqual( + createFilter([ pickFirstItem, BooleanOp.Or, pickLastItem ]).apply(nestedList), + [ nestedList[0], nestedList[2] ], + 'Should have picked first and last item' + ); + + assert.deepEqual( + createFilter( + [ pickFirstItem, BooleanOp.Or, [ pickAllItems, BooleanOp.And, pickNoItems ], BooleanOp.Or, pickLastItem ] + ).apply(nestedList), + [ nestedList[0], nestedList[2] ], + 'Should have picked first and last item' + ); + + assert.deepEqual( + createFilter(individualFilter).apply(nestedList), [ nestedList[0] ], 'Should have picked first item' + ); + } + }, + 'serializing': { 'simple - no path': { 'empty filter': function() { @@ -447,7 +543,7 @@ registerSuite({ return 'Return any item where' + recursivelySerialize(filter); } - assert.strictEqual(createFilter(serializeFilter) + assert.strictEqual(createFilter(undefined, serializeFilter) .greaterThan('key', 3) .lessThan('key', 5) .or()