Skip to content

Commit

Permalink
Merge pull request #24 from AlexcastroDev/feat/add-custom-filter
Browse files Browse the repository at this point in the history
feat: search by custom filter
  • Loading branch information
alexcastrodev authored Sep 19, 2022
2 parents 7a23ada + cd41c8e commit 572c3c4
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 12 deletions.
12 changes: 8 additions & 4 deletions interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type IRuleType = 'string' | 'number' | 'date'
export type IRuleType = 'string' | 'number' | 'date' | 'custom'

export enum RuleStringOptions {
contains = 'contains',
Expand Down Expand Up @@ -32,11 +32,15 @@ export enum RuleOperator {
}

export type IRoles = RuleStringOptions | RuleNumberOptions | RuleDateOptions

export type IRuleFilter<T = any> = (datum: T) => boolean

export interface IRule {
field: string
term?: any
role: IRoles
type: IRuleType
operator: RuleOperator
field?: string
term?: any
role?: IRoles
caseSensitive?: boolean
filter?: IRuleFilter
}
17 changes: 13 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IRule, RuleOperator } from '../interfaces'
import { NumberProcessor } from './utils/number'
import { hashCode } from './utils/hash'
import { DateProcessor } from './utils/dates'
import { omit } from './utils/helpers/objects'
class SearchEngine {
private shouldHave: any[] = []
private mustHave: any[] = []
Expand All @@ -17,7 +18,7 @@ class SearchEngine {
this.mustHave = collectionToBeStored
}

search(queries: IRule[]) {
public search(queries: IRule[]) {
const query = {
'@or': queries.filter((item) => item.operator === RuleOperator.OR),
'@and': queries.filter((item) => item.operator === RuleOperator.AND),
Expand Down Expand Up @@ -51,20 +52,28 @@ class SearchEngine {
}

private someDataIsValid(queryCurrent: IRule, data: Record<string, string>) {
const field = queryCurrent.field || ''
switch (queryCurrent.type) {
case 'string':
return new StringProcessor(queryCurrent?.term || null, queryCurrent.role).compareWith(
data[queryCurrent.field],
data[field],
queryCurrent.caseSensitive || false,
)
case 'number':
return new NumberProcessor(queryCurrent?.term || null, queryCurrent.role).compareWith(
data[queryCurrent.field],
data[field],
)
case 'date':
return new DateProcessor(queryCurrent?.term || null, queryCurrent.role).compareWith(
data[queryCurrent.field],
data[field],
)
case 'custom':
if (queryCurrent.filter && typeof queryCurrent.filter === 'function') {
const datum = omit(data, ['fs_uuid'])

return queryCurrent.filter(datum)
}
throw new Error('[flexysearch]: Custom filter not valid')
default:
throw new Error('[flexysearch]: Processor not found')
}
Expand Down
47 changes: 47 additions & 0 deletions src/tests/custom.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import SearchEngine from '..'
import { RuleOperator } from '../../interfaces'
import collection from '../../__mocks__/movies.json'

interface IMovie {
id: number
title: string
year: number
}

describe('Should match strings', () => {
it('[String]: Should cause exception', () => {
const results = () =>
new SearchEngine(collection).search([
{
type: 'custom',
operator: RuleOperator.AND,
},
])

expect(results).toThrow('[flexysearch]: Custom filter not valid')
})
it('[String]: Should cause exception', () => {
const results = new SearchEngine(collection).search([
{
type: 'custom',
operator: RuleOperator.AND,
filter: (datum: IMovie) => {
return datum.year === 2009
},
},
])

expect(results).toStrictEqual([
{
id: 1,
title: 'Film 1',
year: 2009,
},
{
id: 7,
title: 'Film 7',
year: 2009,
},
])
})
})
4 changes: 2 additions & 2 deletions src/utils/dates.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { RuleDateOptions } from '../../interfaces/index'
import { IRoles } from '../../interfaces'
import { VALIDATE_DATE_REGEXP } from './regexp'
import { VALIDATE_DATE_REGEXP } from './helpers/regexp'

export class DateProcessor {
private term: string
private role: IRoles

constructor(value: string, role: IRoles) {
constructor(value: string, role?: IRoles) {
this.term = value
this.role = role as RuleDateOptions
}
Expand Down
15 changes: 15 additions & 0 deletions src/utils/helpers/objects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const omit = (object: Record<string, any>, keys: Array<string>): Record<string, any> => {
if (!object) return {}
try {
const payload = Object.entries(object).filter(([key]) => !keys.includes(key))
const data: { [key: string]: any } = {}

payload.forEach((item) => {
const [key, value] = item
data[key] = value
})
return data
} catch {
return {}
}
}
File renamed without changes.
2 changes: 1 addition & 1 deletion src/utils/number.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class NumberProcessor {
private term: number
private role: IRoles

constructor(value: number, role: IRoles) {
constructor(value: number, role?: IRoles) {
this.term = Number(value)
this.role = role as RuleNumberOptions
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export class StringProcessor {
private caseSensitive = false
private role: IRoles

constructor(value: string, role: IRoles) {
constructor(value: string, role?: IRoles) {
this.term = value
this.role = role as RuleStringOptions
}
Expand Down

0 comments on commit 572c3c4

Please sign in to comment.