feat(llc): add support for type safe filters
Signed-off-by: Sahil Kumar <[email protected]>
This commit is contained in:
@@ -0,0 +1,197 @@
|
|||||||
|
// ignore_for_file: non_constant_identifier_names, constant_identifier_names
|
||||||
|
|
||||||
|
const _groupOperators = [
|
||||||
|
FilterOperator.and,
|
||||||
|
FilterOperator.or,
|
||||||
|
FilterOperator.nor,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Possible operators to use in filters.
|
||||||
|
enum FilterOperator {
|
||||||
|
/// Matches values that are equal to a specified value.
|
||||||
|
equal,
|
||||||
|
|
||||||
|
/// Matches all values that are not equal to a specified value.
|
||||||
|
notEqual,
|
||||||
|
|
||||||
|
/// Matches values that are greater than a specified value.
|
||||||
|
greater,
|
||||||
|
|
||||||
|
/// Matches values that are greater than a specified value.
|
||||||
|
greaterOrEqual,
|
||||||
|
|
||||||
|
/// Matches values that are less than a specified value.
|
||||||
|
less,
|
||||||
|
|
||||||
|
/// Matches values that are less than or equal to a specified value.
|
||||||
|
lessOrEqual,
|
||||||
|
|
||||||
|
/// Matches any of the values specified in an array.
|
||||||
|
in_,
|
||||||
|
|
||||||
|
/// Matches none of the values specified in an array.
|
||||||
|
notIn,
|
||||||
|
|
||||||
|
/// Matches values by performing text search with the specified value.
|
||||||
|
query,
|
||||||
|
|
||||||
|
/// Matches values with the specified prefix.
|
||||||
|
autoComplete,
|
||||||
|
|
||||||
|
/// Matches values that exist/don't exist based on the specified boolean value.
|
||||||
|
exists,
|
||||||
|
|
||||||
|
/// Matches all the values specified in an array.
|
||||||
|
and,
|
||||||
|
|
||||||
|
/// Matches at least one of the values specified in an array.
|
||||||
|
or,
|
||||||
|
|
||||||
|
/// Matches none of the values specified in an array.
|
||||||
|
nor,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper extension for [FilterOperator]
|
||||||
|
extension FilterOperatorX on FilterOperator {
|
||||||
|
/// Converts [FilterOperator] into rew values
|
||||||
|
String get rawValue => {
|
||||||
|
FilterOperator.equal: '\$eq',
|
||||||
|
FilterOperator.notEqual: '\$ne',
|
||||||
|
FilterOperator.greater: '\$gt',
|
||||||
|
FilterOperator.greaterOrEqual: '\$gte',
|
||||||
|
FilterOperator.less: '\$lt',
|
||||||
|
FilterOperator.lessOrEqual: '\$lte',
|
||||||
|
FilterOperator.in_: '\$in',
|
||||||
|
FilterOperator.notIn: '\$nin',
|
||||||
|
FilterOperator.query: '\$q',
|
||||||
|
FilterOperator.autoComplete: '\$autocomplete',
|
||||||
|
FilterOperator.exists: '\$exists',
|
||||||
|
FilterOperator.and: '\$and',
|
||||||
|
FilterOperator.or: '\$or',
|
||||||
|
FilterOperator.nor: '\$nor',
|
||||||
|
}[this]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stream supports a limited set of filters for querying channels,
|
||||||
|
/// users and members. The example below shows how to filter for channels
|
||||||
|
/// of type messaging where the current user is a member
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// val filter = Filter.and(
|
||||||
|
/// Filter.equal('type', 'messaging'),
|
||||||
|
/// Filter.in_('members', [user.id])
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
/// See <a href="https://getstream.io/chat/docs/query_channels/?language=dart" target="_top">Query Channels Documentation</a>
|
||||||
|
class Filter {
|
||||||
|
const Filter.__({
|
||||||
|
required this.operator,
|
||||||
|
required this.value,
|
||||||
|
this.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
Filter._({
|
||||||
|
required FilterOperator operator,
|
||||||
|
required this.value,
|
||||||
|
this.key,
|
||||||
|
}) : operator = operator.rawValue;
|
||||||
|
|
||||||
|
/// Combines the provided filters and matches the values
|
||||||
|
/// matched by all filters.
|
||||||
|
factory Filter.and(List<Filter> filters) =>
|
||||||
|
Filter._(operator: FilterOperator.and, value: filters);
|
||||||
|
|
||||||
|
/// Combines the provided filters and matches the values
|
||||||
|
/// matched by at least one of the filters.
|
||||||
|
factory Filter.or(List<Filter> filters) =>
|
||||||
|
Filter._(operator: FilterOperator.or, value: filters);
|
||||||
|
|
||||||
|
/// Combines the provided filters and matches the values
|
||||||
|
/// not matched by all the filters.
|
||||||
|
factory Filter.nor(List<Filter> filters) =>
|
||||||
|
Filter._(operator: FilterOperator.nor, value: filters);
|
||||||
|
|
||||||
|
/// Matches values that are equal to a specified value.
|
||||||
|
factory Filter.equal(String key, Object value) =>
|
||||||
|
Filter._(operator: FilterOperator.equal, key: key, value: value);
|
||||||
|
|
||||||
|
/// Matches all values that are not equal to a specified value.
|
||||||
|
factory Filter.notEqual(String key, Object value) =>
|
||||||
|
Filter._(operator: FilterOperator.notEqual, key: key, value: value);
|
||||||
|
|
||||||
|
/// Matches values that are greater than a specified value.
|
||||||
|
factory Filter.greater(String key, Object value) =>
|
||||||
|
Filter._(operator: FilterOperator.greater, key: key, value: value);
|
||||||
|
|
||||||
|
/// Matches values that are greater than a specified value.
|
||||||
|
factory Filter.greaterOrEqual(String key, Object value) =>
|
||||||
|
Filter._(operator: FilterOperator.greaterOrEqual, key: key, value: value);
|
||||||
|
|
||||||
|
/// Matches values that are less than a specified value.
|
||||||
|
factory Filter.less(String key, Object value) =>
|
||||||
|
Filter._(operator: FilterOperator.less, key: key, value: value);
|
||||||
|
|
||||||
|
/// Matches values that are less than or equal to a specified value.
|
||||||
|
factory Filter.lessOrEqual(String key, Object value) =>
|
||||||
|
Filter._(operator: FilterOperator.lessOrEqual, key: key, value: value);
|
||||||
|
|
||||||
|
/// Matches any of the values specified in an array.
|
||||||
|
factory Filter.in_(String key, List<Object> values) =>
|
||||||
|
Filter._(operator: FilterOperator.in_, key: key, value: values);
|
||||||
|
|
||||||
|
/// Matches none of the values specified in an array.
|
||||||
|
factory Filter.notIn(String key, List<Object> values) =>
|
||||||
|
Filter._(operator: FilterOperator.notIn, key: key, value: values);
|
||||||
|
|
||||||
|
/// Matches values by performing text search with the specified value.
|
||||||
|
factory Filter.query(String key, String text) =>
|
||||||
|
Filter._(operator: FilterOperator.query, key: key, value: text);
|
||||||
|
|
||||||
|
/// Matches values with the specified prefix.
|
||||||
|
factory Filter.autoComplete(String key, String text) =>
|
||||||
|
Filter._(operator: FilterOperator.autoComplete, key: key, value: text);
|
||||||
|
|
||||||
|
/// Matches values that exist/don't exist based on the specified boolean value.
|
||||||
|
factory Filter.exists(String key, {bool exists = true}) =>
|
||||||
|
Filter._(operator: FilterOperator.exists, key: key, value: exists);
|
||||||
|
|
||||||
|
/// Creates a custom [Filter] if there isn't one already available.
|
||||||
|
factory Filter.custom({
|
||||||
|
required String operator,
|
||||||
|
required Object value,
|
||||||
|
String? key,
|
||||||
|
}) = Filter.__;
|
||||||
|
|
||||||
|
/// Operator to use in filter.
|
||||||
|
final String operator;
|
||||||
|
|
||||||
|
///
|
||||||
|
final String? key;
|
||||||
|
|
||||||
|
///
|
||||||
|
final Object /*List<Object>|List<Filter>|String*/ value;
|
||||||
|
|
||||||
|
///
|
||||||
|
Map<String, Object> toJson() {
|
||||||
|
final json = <String, Object>{};
|
||||||
|
final groupOperators = _groupOperators.map((it) => it.rawValue);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
groupOperators.contains(operator) || key != null,
|
||||||
|
'Filter must contain the `key` when the operator is not a '
|
||||||
|
'group operator.',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (groupOperators.contains(operator)) {
|
||||||
|
// Filters with group operators are encoded in the following form:
|
||||||
|
// { $<operator>: [ <filter 1>, <filter 2> ] }
|
||||||
|
json[operator] = value;
|
||||||
|
} else {
|
||||||
|
// Normal filters are encoded in the following form:
|
||||||
|
// { key: { $<operator>: <value> } }
|
||||||
|
json[key!] = {operator: value};
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ export './src/models/channel_state.dart';
|
|||||||
export './src/models/command.dart';
|
export './src/models/command.dart';
|
||||||
export './src/models/device.dart';
|
export './src/models/device.dart';
|
||||||
export './src/models/event.dart';
|
export './src/models/event.dart';
|
||||||
|
export './src/models/filter.dart' show Filter;
|
||||||
export './src/models/member.dart';
|
export './src/models/member.dart';
|
||||||
export './src/models/message.dart';
|
export './src/models/message.dart';
|
||||||
export './src/models/mute.dart';
|
export './src/models/mute.dart';
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import 'package:stream_chat/src/models/filter.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('operators', () {
|
||||||
|
test('equal', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
final filter = Filter.equal(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.equal.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('notEqual', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
final filter = Filter.notEqual(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.notEqual.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('greater', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
final filter = Filter.greater(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.greater.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('greaterOrEqual', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
final filter = Filter.greaterOrEqual(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.greaterOrEqual.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('less', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
final filter = Filter.less(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.less.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lessOrEqual', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
final filter = Filter.lessOrEqual(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.lessOrEqual.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('in', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const values = ['testValue'];
|
||||||
|
final filter = Filter.in_(key, values);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, values);
|
||||||
|
expect(filter.operator, FilterOperator.in_.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('in', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const values = ['testValue'];
|
||||||
|
final filter = Filter.in_(key, values);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, values);
|
||||||
|
expect(filter.operator, FilterOperator.in_.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('notIn', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const values = ['testValue'];
|
||||||
|
final filter = Filter.notIn(key, values);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, values);
|
||||||
|
expect(filter.operator, FilterOperator.notIn.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('query', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testQuery';
|
||||||
|
final filter = Filter.query(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.query.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('autoComplete', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testQuery';
|
||||||
|
final filter = Filter.autoComplete(key, value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, FilterOperator.autoComplete.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('exists', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
final filter = Filter.exists(key);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, isTrue);
|
||||||
|
expect(filter.operator, FilterOperator.exists.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('notExists', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
final filter = Filter.exists(key, exists: false);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, isFalse);
|
||||||
|
expect(filter.operator, FilterOperator.exists.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('custom', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
const operator = '\$customOperator';
|
||||||
|
final filter = Filter.custom(operator: operator, key: key, value: value);
|
||||||
|
expect(filter.key, key);
|
||||||
|
expect(filter.value, value);
|
||||||
|
expect(filter.operator, operator);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('groupedOperator', () {
|
||||||
|
final filter1 = Filter.equal('testKey', 'testValue');
|
||||||
|
final filter2 = Filter.in_('testKey', ['testValue']);
|
||||||
|
final filters = [filter1, filter2];
|
||||||
|
|
||||||
|
test('and', () {
|
||||||
|
final filter = Filter.and(filters);
|
||||||
|
expect(filter.key, isNull);
|
||||||
|
expect(filter.value, filters);
|
||||||
|
expect(filter.operator, FilterOperator.and.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('or', () {
|
||||||
|
final filter = Filter.or(filters);
|
||||||
|
expect(filter.key, isNull);
|
||||||
|
expect(filter.value, filters);
|
||||||
|
expect(filter.operator, FilterOperator.or.rawValue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('nor', () {
|
||||||
|
final filter = Filter.nor(filters);
|
||||||
|
expect(filter.key, isNull);
|
||||||
|
expect(filter.value, filters);
|
||||||
|
expect(filter.operator, FilterOperator.nor.rawValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('encoding', () {
|
||||||
|
group('nonGroupedFilter', () {
|
||||||
|
test('simpleValue', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const value = 'testValue';
|
||||||
|
final filter = Filter.equal(key, value);
|
||||||
|
final encoded = json.encode(filter);
|
||||||
|
expect(
|
||||||
|
encoded,
|
||||||
|
'{"$key":{"${FilterOperator.equal.rawValue}":${json.encode(value)}}}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
test('listValue', () {
|
||||||
|
const key = 'testKey';
|
||||||
|
const values = ['testValue'];
|
||||||
|
final filter = Filter.in_(key, values);
|
||||||
|
final encoded = json.encode(filter);
|
||||||
|
expect(
|
||||||
|
encoded,
|
||||||
|
'{"$key":{"${FilterOperator.in_.rawValue}":${json.encode(values)}}}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('groupedFilter', () {
|
||||||
|
final filter1 = Filter.equal('testKey', 'testValue');
|
||||||
|
final filter2 = Filter.in_('testKey', ['testValue']);
|
||||||
|
final filters = [filter1, filter2];
|
||||||
|
|
||||||
|
final filter = Filter.and(filters);
|
||||||
|
final encoded = json.encode(filter);
|
||||||
|
expect(
|
||||||
|
encoded,
|
||||||
|
'{"${FilterOperator.and.rawValue}":${json.encode(filters)}}',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user