feat(dispatcher): allow predicates in CallbackDataBuilder .filter()
This commit is contained in:
parent
f0a63f3301
commit
0960dae59b
2 changed files with 109 additions and 16 deletions
|
@ -58,44 +58,84 @@ describe('CallbackDataBuilder', () => {
|
||||||
new PeersIndex(),
|
new PeersIndex(),
|
||||||
)
|
)
|
||||||
|
|
||||||
const getFilterMatch = (filter: UpdateFilter<CallbackQuery>, data: string) => {
|
const getFilterMatch = async (filter: UpdateFilter<CallbackQuery>, data: string) => {
|
||||||
const cb = createCb(data)
|
const cb = createCb(data)
|
||||||
|
|
||||||
const matched = filter(cb)
|
const matched = await filter(cb)
|
||||||
if (!matched) return null
|
if (!matched) return null
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
return (cb as any).match
|
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')
|
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',
|
foo: 'foo',
|
||||||
bar: 'bar',
|
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')
|
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',
|
foo: 'foo',
|
||||||
bar: 'bar',
|
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')
|
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',
|
foo: '123',
|
||||||
bar: 'bar',
|
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)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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'
|
import { UpdateFilter } from './filters/types.js'
|
||||||
|
|
||||||
|
@ -84,18 +84,71 @@ export class CallbackDataBuilder<T extends string> {
|
||||||
/**
|
/**
|
||||||
* Create a filter for this callback data.
|
* Create a filter for this callback data.
|
||||||
*
|
*
|
||||||
* > **Note**: `params` object will be compiled to a RegExp,
|
* You can either pass an object with field names as keys and values as strings or regexes,
|
||||||
* > so avoid using characters that have special meaning in regex,
|
* which will be compiled to a RegExp, or a function that will be called with the parsed data.
|
||||||
* > or use RegExp directly to let the IDE guide you.
|
* 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
|
* @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,
|
CallbackQuery,
|
||||||
{
|
{
|
||||||
match: Record<T, string>
|
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[] = []
|
const parts: string[] = []
|
||||||
|
|
||||||
this._fields.forEach((field) => {
|
this._fields.forEach((field) => {
|
||||||
|
|
Loading…
Reference in a new issue