Skip to main content

tvdata_rs/scanner/
filter.rs

1use serde::Serialize;
2use serde::ser::Serializer;
3use serde_json::Value;
4
5use crate::scanner::field::Column;
6
7pub trait IntoFilterValue {
8    fn into_filter_value(self) -> Value;
9}
10
11macro_rules! impl_numeric_filter_value {
12    ($($ty:ty),+ $(,)?) => {
13        $(
14            impl IntoFilterValue for $ty {
15                fn into_filter_value(self) -> Value {
16                    Value::from(self)
17                }
18            }
19        )+
20    };
21}
22
23impl_numeric_filter_value!(i8, i16, i32, i64, isize, u8, u16, u32, u64);
24
25impl IntoFilterValue for f32 {
26    fn into_filter_value(self) -> Value {
27        Value::from(self as f64)
28    }
29}
30
31impl IntoFilterValue for f64 {
32    fn into_filter_value(self) -> Value {
33        Value::from(self)
34    }
35}
36
37impl IntoFilterValue for bool {
38    fn into_filter_value(self) -> Value {
39        Value::from(self)
40    }
41}
42
43impl IntoFilterValue for Value {
44    fn into_filter_value(self) -> Value {
45        self
46    }
47}
48
49impl IntoFilterValue for String {
50    fn into_filter_value(self) -> Value {
51        Value::from(self)
52    }
53}
54
55impl IntoFilterValue for &str {
56    fn into_filter_value(self) -> Value {
57        Value::from(self)
58    }
59}
60
61impl IntoFilterValue for Column {
62    fn into_filter_value(self) -> Value {
63        Value::from(self.as_str().to_owned())
64    }
65}
66
67impl IntoFilterValue for &Column {
68    fn into_filter_value(self) -> Value {
69        Value::from(self.as_str().to_owned())
70    }
71}
72
73impl<T> IntoFilterValue for Option<T>
74where
75    T: IntoFilterValue,
76{
77    fn into_filter_value(self) -> Value {
78        self.map(IntoFilterValue::into_filter_value)
79            .unwrap_or(Value::Null)
80    }
81}
82
83impl<T> IntoFilterValue for Vec<T>
84where
85    T: IntoFilterValue,
86{
87    fn into_filter_value(self) -> Value {
88        Value::Array(
89            self.into_iter()
90                .map(IntoFilterValue::into_filter_value)
91                .collect(),
92        )
93    }
94}
95
96impl<T, const N: usize> IntoFilterValue for [T; N]
97where
98    T: IntoFilterValue,
99{
100    fn into_filter_value(self) -> Value {
101        Value::Array(
102            self.into_iter()
103                .map(IntoFilterValue::into_filter_value)
104                .collect(),
105        )
106    }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
110pub enum FilterOperator {
111    #[serde(rename = "greater")]
112    Greater,
113    #[serde(rename = "egreater")]
114    EGreater,
115    #[serde(rename = "less")]
116    Less,
117    #[serde(rename = "eless")]
118    ELess,
119    #[serde(rename = "equal")]
120    Equal,
121    #[serde(rename = "nequal")]
122    NotEqual,
123    #[serde(rename = "in_range")]
124    InRange,
125    #[serde(rename = "not_in_range")]
126    NotInRange,
127    #[serde(rename = "empty")]
128    Empty,
129    #[serde(rename = "nempty")]
130    NotEmpty,
131    #[serde(rename = "crosses")]
132    Crosses,
133    #[serde(rename = "crosses_above")]
134    CrossesAbove,
135    #[serde(rename = "crosses_below")]
136    CrossesBelow,
137    #[serde(rename = "match")]
138    Match,
139    #[serde(rename = "nmatch")]
140    NotMatch,
141    #[serde(rename = "has")]
142    Has,
143    #[serde(rename = "has_none_of")]
144    HasNoneOf,
145    #[serde(rename = "above%")]
146    AbovePercent,
147    #[serde(rename = "below%")]
148    BelowPercent,
149    #[serde(rename = "in_range%")]
150    InRangePercent,
151    #[serde(rename = "not_in_range%")]
152    NotInRangePercent,
153    #[serde(rename = "in_day_range")]
154    InDayRange,
155    #[serde(rename = "in_week_range")]
156    InWeekRange,
157    #[serde(rename = "in_month_range")]
158    InMonthRange,
159}
160
161#[derive(Debug, Clone, PartialEq, Serialize)]
162pub struct FilterCondition {
163    pub left: Column,
164    pub operation: FilterOperator,
165    pub right: Value,
166}
167
168impl FilterCondition {
169    pub fn new(left: Column, operation: FilterOperator, right: Value) -> Self {
170        Self {
171            left,
172            operation,
173            right,
174        }
175    }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
179pub enum LogicalOperator {
180    #[serde(rename = "and")]
181    And,
182    #[serde(rename = "or")]
183    Or,
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize)]
187pub struct FilterTree {
188    pub operator: LogicalOperator,
189    pub operands: Vec<FilterOperand>,
190}
191
192impl FilterTree {
193    pub fn and<I, O>(operands: I) -> Self
194    where
195        I: IntoIterator<Item = O>,
196        O: Into<FilterOperand>,
197    {
198        Self {
199            operator: LogicalOperator::And,
200            operands: operands.into_iter().map(Into::into).collect(),
201        }
202    }
203
204    pub fn or<I, O>(operands: I) -> Self
205    where
206        I: IntoIterator<Item = O>,
207        O: Into<FilterOperand>,
208    {
209        Self {
210            operator: LogicalOperator::Or,
211            operands: operands.into_iter().map(Into::into).collect(),
212        }
213    }
214}
215
216#[derive(Debug, Clone, PartialEq)]
217pub enum FilterOperand {
218    Expression(FilterExpressionOperand),
219    Operation(FilterOperationOperand),
220}
221
222impl Serialize for FilterOperand {
223    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224    where
225        S: Serializer,
226    {
227        match self {
228            Self::Expression(expression) => expression.serialize(serializer),
229            Self::Operation(operation) => operation.serialize(serializer),
230        }
231    }
232}
233
234impl From<FilterCondition> for FilterOperand {
235    fn from(value: FilterCondition) -> Self {
236        Self::Expression(FilterExpressionOperand { expression: value })
237    }
238}
239
240impl From<FilterTree> for FilterOperand {
241    fn from(value: FilterTree) -> Self {
242        Self::Operation(FilterOperationOperand { operation: value })
243    }
244}
245
246#[derive(Debug, Clone, PartialEq, Serialize)]
247pub struct FilterExpressionOperand {
248    pub expression: FilterCondition,
249}
250
251#[derive(Debug, Clone, PartialEq, Serialize)]
252pub struct FilterOperationOperand {
253    pub operation: FilterTree,
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
257pub enum SortOrder {
258    #[serde(rename = "asc")]
259    Asc,
260    #[serde(rename = "desc")]
261    Desc,
262}
263
264#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
265pub struct SortSpec {
266    #[serde(rename = "sortBy")]
267    pub sort_by: Column,
268    #[serde(rename = "sortOrder")]
269    pub sort_order: SortOrder,
270    #[serde(skip_serializing_if = "Option::is_none", rename = "nullsFirst")]
271    pub nulls_first: Option<bool>,
272}
273
274impl SortSpec {
275    pub fn new(sort_by: Column, sort_order: SortOrder) -> Self {
276        Self {
277            sort_by,
278            sort_order,
279            nulls_first: None,
280        }
281    }
282
283    pub fn nulls_first(mut self, nulls_first: bool) -> Self {
284        self.nulls_first = Some(nulls_first);
285        self
286    }
287}
288
289#[cfg(test)]
290mod tests {
291    use serde_json::json;
292
293    use super::*;
294
295    #[test]
296    fn serializes_nested_filter_tree_like_tradingview() {
297        let tree = FilterTree::and(vec![
298            FilterOperand::from(Column::from_static("close").ge(200)),
299            FilterOperand::from(FilterTree::or(vec![
300                FilterOperand::from(Column::from_static("RSI").lt(60)),
301                FilterOperand::from(Column::from_static("market_cap_basic").gt(1_000_000_000_u64)),
302            ])),
303        ]);
304
305        let value = serde_json::to_value(tree).unwrap();
306        assert_eq!(
307            value,
308            json!({
309                "operator": "and",
310                "operands": [
311                    { "expression": { "left": "close", "operation": "egreater", "right": 200 } },
312                    {
313                        "operation": {
314                            "operator": "or",
315                            "operands": [
316                                { "expression": { "left": "RSI", "operation": "less", "right": 60 } },
317                                { "expression": { "left": "market_cap_basic", "operation": "greater", "right": 1_000_000_000_u64 } }
318                            ]
319                        }
320                    }
321                ]
322            })
323        );
324    }
325}