feat(dispatcher): allow predicates in CallbackDataBuilder .filter()

This commit is contained in:
alina 🌸 2024-05-27 00:06:31 +03:00
parent f0a63f3301
commit 0960dae59b
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
2 changed files with 109 additions and 16 deletions

View file

@ -58,44 +58,84 @@ describe('CallbackDataBuilder', () => {
new PeersIndex(),
)
const getFilterMatch = (filter: UpdateFilter<CallbackQuery>, data: string) => {
const getFilterMatch = async (filter: UpdateFilter<CallbackQuery>, data: string) => {
const cb = createCb(data)
const matched = filter(cb)
const matched = await filter(cb)
if (!matched) return null
// eslint-disable-next-line
return (cb as any).match
}
it('should create a filter without params', () => {
it('should create a filter without params', async () => {
const cdb = new CallbackDataBuilder('prefix', 'foo', 'bar')
expect(getFilterMatch(cdb.filter(), 'prefix:foo:bar')).toEqual({
expect(await getFilterMatch(cdb.filter(), 'prefix:foo:bar')).toEqual({
foo: 'foo',
bar: 'bar',
})
expect(getFilterMatch(cdb.filter(), 'prefix:foo:bar:baz')).toEqual(null)
expect(await getFilterMatch(cdb.filter(), 'prefix:foo:bar:baz')).toEqual(null)
})
it('should create a filter with params', () => {
it('should create a filter with params', async () => {
const cdb = new CallbackDataBuilder('prefix', 'foo', 'bar')
expect(getFilterMatch(cdb.filter({ foo: 'foo' }), 'prefix:foo:bar')).toEqual({
expect(await getFilterMatch(cdb.filter({ foo: 'foo' }), 'prefix:foo:bar')).toEqual({
foo: 'foo',
bar: 'bar',
})
expect(getFilterMatch(cdb.filter({ foo: 'foo' }), 'prefix:bar:bar')).toEqual(null)
expect(await getFilterMatch(cdb.filter({ foo: 'foo' }), 'prefix:bar:bar')).toEqual(null)
})
it('should create a filter with regex params', () => {
it('should create a filter with regex params', async () => {
const cdb = new CallbackDataBuilder('prefix', 'foo', 'bar')
expect(getFilterMatch(cdb.filter({ foo: /\d+/ }), 'prefix:123:bar')).toEqual({
expect(await getFilterMatch(cdb.filter({ foo: /\d+/ }), 'prefix:123:bar')).toEqual({
foo: '123',
bar: 'bar',
})
expect(getFilterMatch(cdb.filter({ foo: /\d+/ }), 'prefix:bar:bar')).toEqual(null)
expect(await getFilterMatch(cdb.filter({ foo: /\d+/ }), 'prefix:bar:bar')).toEqual(null)
})
it('should create a filter with dynamic params', async () => {
const cdb = new CallbackDataBuilder('prefix', 'foo', 'bar')
expect(
await getFilterMatch(
cdb.filter(() => ({ foo: 'foo' })),
'prefix:foo:bar',
),
).toEqual({
foo: 'foo',
bar: 'bar',
})
expect(
await getFilterMatch(
cdb.filter(() => ({ foo: 'foo' })),
'prefix:bar:baz',
),
).toEqual(null)
})
it('should create a filter with a predicate matcher', async () => {
const cdb = new CallbackDataBuilder('prefix', 'foo', 'bar')
expect(
await getFilterMatch(
cdb.filter((_, data) => data.foo === 'foo'),
'prefix:foo:bar',
),
).toEqual({
foo: 'foo',
bar: 'bar',
})
expect(
await getFilterMatch(
cdb.filter((_, data) => data.foo === 'foo'),
'prefix:bar:baz',
),
).toEqual(null)
})
})
})

View file

@ -1,4 +1,4 @@
import { CallbackQuery, MaybeArray, MtArgumentError } from '@mtcute/core'
import { CallbackQuery, MaybeArray, MaybePromise, MtArgumentError } from '@mtcute/core'
import { UpdateFilter } from './filters/types.js'
@ -84,18 +84,71 @@ export class CallbackDataBuilder<T extends string> {
/**
* Create a filter for this callback data.
*
* > **Note**: `params` object will be compiled to a RegExp,
* > so avoid using characters that have special meaning in regex,
* > or use RegExp directly to let the IDE guide you.
* You can either pass an object with field names as keys and values as strings or regexes,
* which will be compiled to a RegExp, or a function that will be called with the parsed data.
* Note that the strings will be passed to `RegExp` **directly**, so you may want to escape them.
*
* When using a function, you can either return a boolean, or an object with field names as keys
* and values as strings or regexes. In the latter case, the resulting object will be matched
* against the parsed data the same way as if you passed it directly.
*
* @param params
*/
filter(params: Partial<Record<T, MaybeArray<string | RegExp>>> = {}): UpdateFilter<
filter(
params:
| ((
upd: CallbackQuery,
parsed: Record<T, string>,
) => MaybePromise<Partial<Record<T, MaybeArray<string | RegExp>>> | boolean>)
| Partial<Record<T, MaybeArray<string | RegExp>>> = {},
): UpdateFilter<
CallbackQuery,
{
match: Record<T, string>
}
> {
if (typeof params === 'function') {
return async (query) => {
if (!query.dataStr) return false
const data = this.parse(query.dataStr)
const fnResult = await params(query, data)
if (typeof fnResult === 'boolean') {
(
query as CallbackQuery & {
match: Record<T, string>
}
).match = data
return fnResult
}
// validate result
for (const key in fnResult) {
const value = data[key]
if (value === undefined) return false
let matchers = fnResult[key] as MaybeArray<string | RegExp>
if (!Array.isArray(matchers)) matchers = [matchers]
for (const matcher of matchers) {
if (typeof matcher === 'string') {
if (value !== matcher) return false
} else if (!matcher.test(value)) return false
}
}
(
query as CallbackQuery & {
match: Record<T, string>
}
).match = data
return true
}
}
const parts: string[] = []
this._fields.forEach((field) => {