Skip to main content

cqrs_rust_lib/read/
query.rs

1use crate::read::Sorter;
2use crate::{MaybeSend, MaybeSync};
3use serde::Serialize;
4use serde_json::Value as JsonValue;
5use std::fmt::Debug;
6
7#[derive(Debug, Clone, Default)]
8pub struct Pagination {
9    pub skip: Option<i64>,
10    pub limit: Option<i64>,
11}
12
13/// Query abstraction for read-side storage.
14///
15/// All three methods have default implementations so minimal structs need zero
16/// boilerplate:
17///
18/// ```rust,ignore
19/// #[derive(Debug, Serialize, Deserialize)]
20/// struct GameQuery { category: Option<String>, available: Option<bool> }
21/// impl Query for GameQuery {}
22/// ```
23///
24/// The default `filter()` converts every non-`None` field to an equality
25/// constraint (`field == value`) ANDed together. Override only when you need
26/// non-equality operators (`Gte`, `Like`, …) or field-name remapping.
27pub trait Query: Debug + Serialize + MaybeSend + MaybeSync {
28    /// Returns a filter derived from the struct's serializable fields.
29    /// Override when you need operators other than `==` or custom field names.
30    fn filter(&self) -> Option<rest_sql::RestSql> {
31        derive_filter_from_serde(self)
32    }
33
34    /// Pagination hint for the storage layer. Defaults to `None` (let the
35    /// storage use its own defaults or rely on `CqrsHttpQuery` page params).
36    fn pagination(&self) -> Option<Pagination> {
37        None
38    }
39
40    /// Static default sort for this view type, applied when no explicit sort
41    /// is requested (neither HTTP `sort` param nor `sort()` override).
42    /// Use this instead of `sort()` when the sort is unconditional.
43    fn default_sort() -> Option<Vec<Sorter>>
44    where
45        Self: Sized,
46    {
47        None
48    }
49
50    /// Dynamic sort order. Falls back to `default_sort()`.
51    /// Override only when the sort depends on query field values.
52    fn sort(&self) -> Option<Vec<Sorter>>
53    where
54        Self: Sized,
55    {
56        Self::default_sort()
57    }
58}
59
60/// Converts every non-`null` scalar field of a serializable struct into an
61/// equality constraint and ANDs them together.
62///
63/// Useful in `Query::filter()` overrides that need to combine the auto-derived
64/// filter with custom logic.
65pub fn derive_filter_from_serde<T: Serialize + ?Sized>(val: &T) -> Option<rest_sql::RestSql> {
66    use rest_sql::{Ast, Constraint, Operator};
67
68    let json = serde_json::to_value(val).ok()?;
69    let JsonValue::Object(map) = json else {
70        return None;
71    };
72
73    let constraints: Vec<Ast> = map
74        .into_iter()
75        .filter(|(_, v)| !v.is_null())
76        .filter_map(|(k, v)| {
77            json_to_rsql_value(v).map(|rv| {
78                Ast::Constraint(Constraint {
79                    field: k,
80                    operator: Operator::Eq,
81                    value: rv,
82                })
83            })
84        })
85        .collect();
86
87    Ast::try_and(constraints).and_then(|ast| rest_sql::RestSql::from_ast(ast).ok())
88}
89
90fn json_to_rsql_value(v: JsonValue) -> Option<rest_sql::Value> {
91    use rest_sql::Value;
92    match v {
93        JsonValue::Bool(b) => Some(Value::Bool(b)),
94        JsonValue::Number(n) => {
95            if let Some(i) = n.as_i64() {
96                Some(Value::Int(i))
97            } else {
98                n.as_f64().map(Value::Float)
99            }
100        }
101        JsonValue::String(s) => Some(Value::String(s)),
102        JsonValue::Array(arr) => {
103            let items: Option<Vec<_>> = arr.into_iter().map(json_to_rsql_value).collect();
104            items.map(Value::List)
105        }
106        JsonValue::Null | JsonValue::Object(_) => None,
107    }
108}