1use crate::filter::{Filter, FilterValue, ValueList};
33use std::borrow::Cow;
34
35#[derive(Debug, Clone, PartialEq)]
40pub struct StaticFilter {
41 inner: Filter,
42}
43
44impl StaticFilter {
45 #[inline]
47 pub const fn new(inner: Filter) -> Self {
48 Self { inner }
49 }
50
51 #[inline]
53 pub fn into_filter(self) -> Filter {
54 self.inner
55 }
56
57 #[inline]
59 pub fn as_filter(&self) -> &Filter {
60 &self.inner
61 }
62}
63
64impl From<StaticFilter> for Filter {
65 #[inline]
66 fn from(f: StaticFilter) -> Self {
67 f.inner
68 }
69}
70
71pub mod fields {
76 pub const ID: &str = "id";
78 pub const UUID: &str = "uuid";
80 pub const NAME: &str = "name";
82 pub const EMAIL: &str = "email";
84 pub const USERNAME: &str = "username";
86 pub const PASSWORD: &str = "password";
88 pub const TITLE: &str = "title";
90 pub const DESCRIPTION: &str = "description";
92 pub const CONTENT: &str = "content";
94 pub const BODY: &str = "body";
96 pub const STATUS: &str = "status";
98 pub const TYPE: &str = "type";
100 pub const ROLE: &str = "role";
102 pub const ACTIVE: &str = "active";
104 pub const ENABLED: &str = "enabled";
106 pub const DELETED: &str = "deleted";
108 pub const VERIFIED: &str = "verified";
110 pub const PUBLISHED: &str = "published";
112 pub const COUNT: &str = "count";
114 pub const SCORE: &str = "score";
116 pub const PRIORITY: &str = "priority";
118 pub const ORDER: &str = "order";
120 pub const POSITION: &str = "position";
122 pub const AGE: &str = "age";
124 pub const AMOUNT: &str = "amount";
126 pub const PRICE: &str = "price";
128 pub const QUANTITY: &str = "quantity";
130 pub const USER_ID: &str = "user_id";
132 pub const POST_ID: &str = "post_id";
134 pub const COMMENT_ID: &str = "comment_id";
136 pub const CATEGORY_ID: &str = "category_id";
138 pub const PARENT_ID: &str = "parent_id";
140 pub const AUTHOR_ID: &str = "author_id";
142 pub const OWNER_ID: &str = "owner_id";
144 pub const CREATED_AT: &str = "created_at";
146 pub const UPDATED_AT: &str = "updated_at";
148 pub const DELETED_AT: &str = "deleted_at";
150 pub const PUBLISHED_AT: &str = "published_at";
152 pub const EXPIRES_AT: &str = "expires_at";
154 pub const STARTS_AT: &str = "starts_at";
156 pub const ENDS_AT: &str = "ends_at";
158 pub const LAST_LOGIN_AT: &str = "last_login_at";
160 pub const VERIFIED_AT: &str = "verified_at";
162 pub const SLUG: &str = "slug";
164 pub const URL: &str = "url";
166 pub const PATH: &str = "path";
168 pub const KEY: &str = "key";
170 pub const VALUE: &str = "value";
172 pub const TOKEN: &str = "token";
174 pub const CODE: &str = "code";
176 pub const VERSION: &str = "version";
178}
179
180#[inline]
199pub fn eq(field: &'static str, value: impl Into<FilterValue>) -> Filter {
200 Filter::Equals(Cow::Borrowed(field), value.into())
201}
202
203#[inline]
205pub fn ne(field: &'static str, value: impl Into<FilterValue>) -> Filter {
206 Filter::NotEquals(Cow::Borrowed(field), value.into())
207}
208
209#[inline]
211pub fn lt(field: &'static str, value: impl Into<FilterValue>) -> Filter {
212 Filter::Lt(Cow::Borrowed(field), value.into())
213}
214
215#[inline]
217pub fn lte(field: &'static str, value: impl Into<FilterValue>) -> Filter {
218 Filter::Lte(Cow::Borrowed(field), value.into())
219}
220
221#[inline]
223pub fn gt(field: &'static str, value: impl Into<FilterValue>) -> Filter {
224 Filter::Gt(Cow::Borrowed(field), value.into())
225}
226
227#[inline]
229pub fn gte(field: &'static str, value: impl Into<FilterValue>) -> Filter {
230 Filter::Gte(Cow::Borrowed(field), value.into())
231}
232
233#[inline]
235pub const fn is_null(field: &'static str) -> Filter {
236 Filter::IsNull(Cow::Borrowed(field))
237}
238
239#[inline]
241pub const fn is_not_null(field: &'static str) -> Filter {
242 Filter::IsNotNull(Cow::Borrowed(field))
243}
244
245#[inline]
247pub fn contains(field: &'static str, value: impl Into<FilterValue>) -> Filter {
248 Filter::Contains(Cow::Borrowed(field), value.into())
249}
250
251#[inline]
253pub fn starts_with(field: &'static str, value: impl Into<FilterValue>) -> Filter {
254 Filter::StartsWith(Cow::Borrowed(field), value.into())
255}
256
257#[inline]
259pub fn ends_with(field: &'static str, value: impl Into<FilterValue>) -> Filter {
260 Filter::EndsWith(Cow::Borrowed(field), value.into())
261}
262
263#[inline]
265pub fn in_list(field: &'static str, values: impl Into<ValueList>) -> Filter {
266 Filter::In(Cow::Borrowed(field), values.into())
267}
268
269#[inline]
271pub fn not_in_list(field: &'static str, values: impl Into<ValueList>) -> Filter {
272 Filter::NotIn(Cow::Borrowed(field), values.into())
273}
274
275#[inline]
281pub fn and2(a: Filter, b: Filter) -> Filter {
282 Filter::And(Box::new([a, b]))
283}
284
285#[inline]
287pub fn and3(a: Filter, b: Filter, c: Filter) -> Filter {
288 Filter::And(Box::new([a, b, c]))
289}
290
291#[inline]
293pub fn and4(a: Filter, b: Filter, c: Filter, d: Filter) -> Filter {
294 Filter::And(Box::new([a, b, c, d]))
295}
296
297#[inline]
299pub fn and5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Filter {
300 Filter::And(Box::new([a, b, c, d, e]))
301}
302
303#[inline]
305pub fn or2(a: Filter, b: Filter) -> Filter {
306 Filter::Or(Box::new([a, b]))
307}
308
309#[inline]
311pub fn or3(a: Filter, b: Filter, c: Filter) -> Filter {
312 Filter::Or(Box::new([a, b, c]))
313}
314
315#[inline]
317pub fn or4(a: Filter, b: Filter, c: Filter, d: Filter) -> Filter {
318 Filter::Or(Box::new([a, b, c, d]))
319}
320
321#[inline]
323pub fn or5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Filter {
324 Filter::Or(Box::new([a, b, c, d, e]))
325}
326
327#[inline]
329pub fn not(filter: Filter) -> Filter {
330 Filter::Not(Box::new(filter))
331}
332
333#[derive(Debug, Clone, PartialEq)]
343#[repr(u8)]
344pub enum CompactValue {
345 Null = 0,
347 True = 1,
349 False = 2,
351 SmallInt(i8) = 3,
353 Int(i64) = 4,
355 Float(f64) = 5,
357 String(String) = 6,
359}
360
361impl CompactValue {
362 #[inline]
364 pub fn into_filter_value(self) -> FilterValue {
365 match self {
366 Self::Null => FilterValue::Null,
367 Self::True => FilterValue::Bool(true),
368 Self::False => FilterValue::Bool(false),
369 Self::SmallInt(v) => FilterValue::Int(v as i64),
370 Self::Int(v) => FilterValue::Int(v),
371 Self::Float(v) => FilterValue::Float(v),
372 Self::String(v) => FilterValue::String(v),
373 }
374 }
375}
376
377impl From<bool> for CompactValue {
378 #[inline]
379 fn from(v: bool) -> Self {
380 if v { Self::True } else { Self::False }
381 }
382}
383
384impl From<i32> for CompactValue {
385 #[inline]
386 fn from(v: i32) -> Self {
387 if (-128..=127).contains(&v) {
388 Self::SmallInt(v as i8)
389 } else {
390 Self::Int(v as i64)
391 }
392 }
393}
394
395impl From<i64> for CompactValue {
396 #[inline]
397 fn from(v: i64) -> Self {
398 if (-128..=127).contains(&v) {
399 Self::SmallInt(v as i8)
400 } else {
401 Self::Int(v)
402 }
403 }
404}
405
406impl From<f64> for CompactValue {
407 #[inline]
408 fn from(v: f64) -> Self {
409 Self::Float(v)
410 }
411}
412
413impl From<String> for CompactValue {
414 #[inline]
415 fn from(v: String) -> Self {
416 Self::String(v)
417 }
418}
419
420impl From<&str> for CompactValue {
421 #[inline]
422 fn from(v: &str) -> Self {
423 Self::String(v.to_string())
424 }
425}
426
427impl From<CompactValue> for FilterValue {
428 #[inline]
429 fn from(v: CompactValue) -> Self {
430 v.into_filter_value()
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn test_eq_filter() {
440 let filter = eq(fields::ID, 42);
441 assert!(matches!(filter, Filter::Equals(_, FilterValue::Int(42))));
442 }
443
444 #[test]
445 fn test_gt_filter() {
446 let filter = gt(fields::AGE, 18);
447 assert!(matches!(filter, Filter::Gt(_, FilterValue::Int(18))));
448 }
449
450 #[test]
451 fn test_is_null_filter() {
452 let filter = is_null(fields::DELETED_AT);
453 assert!(matches!(filter, Filter::IsNull(_)));
454 }
455
456 #[test]
457 fn test_and2_filter() {
458 let filter = and2(eq(fields::ACTIVE, true), gt(fields::SCORE, 100));
459 assert!(matches!(filter, Filter::And(_)));
460 }
461
462 #[test]
463 fn test_or2_filter() {
464 let filter = or2(eq(fields::STATUS, "active"), eq(fields::STATUS, "pending"));
465 assert!(matches!(filter, Filter::Or(_)));
466 }
467
468 #[test]
469 fn test_compact_value_bool() {
470 let v: CompactValue = true.into();
471 assert!(matches!(v, CompactValue::True));
472 assert_eq!(v.into_filter_value(), FilterValue::Bool(true));
473 }
474
475 #[test]
476 fn test_compact_value_small_int() {
477 let v: CompactValue = 42i32.into();
478 assert!(matches!(v, CompactValue::SmallInt(42)));
479 }
480
481 #[test]
482 fn test_compact_value_large_int() {
483 let v: CompactValue = 1000i32.into();
484 assert!(matches!(v, CompactValue::Int(1000)));
485 }
486
487 #[test]
488 fn test_field_constants() {
489 assert_eq!(fields::ID, "id");
490 assert_eq!(fields::EMAIL, "email");
491 assert_eq!(fields::CREATED_AT, "created_at");
492 }
493}