Skip to main content

tackler_api/
filters.rs

1/*
2 * Tackler-NG 2023-2026
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! Transaction filters
7//!
8//! The filtering logic is implemented in [`tackler-core`].
9//!
10//! [`tackler-core`]: ../../tackler_core/index.html
11mod filter_definition;
12pub mod logic;
13pub mod posting;
14pub mod txn;
15
16pub use crate::filters::filter_definition::FilterDefZoned;
17pub use crate::filters::filter_definition::FilterDefinition;
18use jiff::tz::TimeZone;
19use rust_decimal::Decimal;
20use serde::{Deserialize, Serialize};
21use std::fmt::{Error, Formatter};
22
23use logic::TxnFilterAND;
24use logic::TxnFilterNOT;
25use logic::TxnFilterOR;
26
27use txn::TxnFilterBBoxLatLon;
28use txn::TxnFilterBBoxLatLonAlt;
29use txn::TxnFilterTxnCode;
30use txn::TxnFilterTxnComments;
31use txn::TxnFilterTxnDescription;
32use txn::TxnFilterTxnExtId;
33use txn::TxnFilterTxnTSBegin;
34use txn::TxnFilterTxnTSEnd;
35use txn::TxnFilterTxnTags;
36use txn::TxnFilterTxnUUID;
37
38use posting::TxnFilterPostingAccount;
39use posting::TxnFilterPostingAmountEqual;
40use posting::TxnFilterPostingAmountGreater;
41use posting::TxnFilterPostingAmountLess;
42use posting::TxnFilterPostingComment;
43use posting::TxnFilterPostingCommodity;
44
45/// fmt with prefix indent
46///
47pub trait IndentDisplay {
48    /// format with indent
49    ///
50    /// # Errors
51    ///
52    /// Returns `Err` if formatter fails
53    fn i_fmt(&self, indent: &str, _tz: TimeZone, f: &mut Formatter<'_>) -> std::fmt::Result;
54}
55
56/// Enum of all Transaction filters.
57///
58/// See [logic], [txn] and [posting] modules
59/// for the documentation of transaction filters.
60#[derive(Serialize, Deserialize, Clone, Debug)]
61pub enum TxnFilter {
62    // Nullary test filters
63    #[doc(hidden)]
64    NullaryTRUE(NullaryTRUE),
65    #[doc(hidden)]
66    NullaryFALSE(NullaryFALSE),
67
68    // Logic filters
69    #[doc(hidden)]
70    TxnFilterAND(TxnFilterAND),
71    #[doc(hidden)]
72    TxnFilterOR(TxnFilterOR),
73    #[doc(hidden)]
74    TxnFilterNOT(TxnFilterNOT),
75
76    // TXN Header filters
77    #[doc(hidden)]
78    TxnFilterTxnTSBegin(TxnFilterTxnTSBegin),
79    #[doc(hidden)]
80    TxnFilterTxnTSEnd(TxnFilterTxnTSEnd),
81    #[doc(hidden)]
82    TxnFilterTxnCode(TxnFilterTxnCode),
83    #[doc(hidden)]
84    TxnFilterTxnDescription(TxnFilterTxnDescription),
85    #[doc(hidden)]
86    TxnFilterTxnUUID(TxnFilterTxnUUID),
87    #[doc(hidden)]
88    TxnFilterTxnExtId(TxnFilterTxnExtId),
89    #[doc(hidden)]
90    TxnFilterBBoxLatLon(TxnFilterBBoxLatLon),
91    #[doc(hidden)]
92    TxnFilterBBoxLatLonAlt(TxnFilterBBoxLatLonAlt),
93    #[doc(hidden)]
94    TxnFilterTxnTags(TxnFilterTxnTags),
95    #[doc(hidden)]
96    TxnFilterTxnComments(TxnFilterTxnComments),
97
98    // TXN Postings
99    #[doc(hidden)]
100    TxnFilterPostingAccount(TxnFilterPostingAccount),
101    #[doc(hidden)]
102    TxnFilterPostingComment(TxnFilterPostingComment),
103    #[doc(hidden)]
104    TxnFilterPostingAmountEqual(TxnFilterPostingAmountEqual),
105    #[doc(hidden)]
106    TxnFilterPostingAmountLess(TxnFilterPostingAmountLess),
107    #[doc(hidden)]
108    TxnFilterPostingAmountGreater(TxnFilterPostingAmountGreater),
109    #[doc(hidden)]
110    TxnFilterPostingCommodity(TxnFilterPostingCommodity),
111}
112
113/*
114impl Display for TxnFilter {
115    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
116        self.i_fmt("", f)
117    }
118}
119*/
120impl IndentDisplay for TxnFilter {
121    fn i_fmt(&self, indent: &str, tz: TimeZone, f: &mut Formatter<'_>) -> std::fmt::Result {
122        match self {
123            // specials
124            TxnFilter::NullaryTRUE(tf) => tf.i_fmt(indent, tz, f),
125            TxnFilter::NullaryFALSE(tf) => tf.i_fmt(indent, tz, f),
126
127            // logic filters
128            TxnFilter::TxnFilterAND(tf) => tf.i_fmt(indent, tz, f),
129            TxnFilter::TxnFilterOR(tf) => tf.i_fmt(indent, tz, f),
130            TxnFilter::TxnFilterNOT(tf) => tf.i_fmt(indent, tz, f),
131
132            // txn header filters
133            TxnFilter::TxnFilterTxnTSBegin(tf) => tf.i_fmt(indent, tz, f),
134            TxnFilter::TxnFilterTxnTSEnd(tf) => tf.i_fmt(indent, tz, f),
135            TxnFilter::TxnFilterTxnCode(tf) => tf.i_fmt(indent, tz, f),
136            TxnFilter::TxnFilterTxnDescription(tf) => tf.i_fmt(indent, tz, f),
137            TxnFilter::TxnFilterTxnUUID(tf) => tf.i_fmt(indent, tz, f),
138            TxnFilter::TxnFilterTxnExtId(tf) => tf.i_fmt(indent, tz, f),
139            TxnFilter::TxnFilterBBoxLatLon(tf) => tf.i_fmt(indent, tz, f),
140            TxnFilter::TxnFilterBBoxLatLonAlt(tf) => tf.i_fmt(indent, tz, f),
141            TxnFilter::TxnFilterTxnTags(tf) => tf.i_fmt(indent, tz, f),
142            TxnFilter::TxnFilterTxnComments(tf) => tf.i_fmt(indent, tz, f),
143
144            // posting filters
145            TxnFilter::TxnFilterPostingAccount(tf) => tf.i_fmt(indent, tz, f),
146            TxnFilter::TxnFilterPostingComment(tf) => tf.i_fmt(indent, tz, f),
147            TxnFilter::TxnFilterPostingAmountEqual(tf) => tf.i_fmt(indent, tz, f),
148            TxnFilter::TxnFilterPostingAmountLess(tf) => tf.i_fmt(indent, tz, f),
149            TxnFilter::TxnFilterPostingAmountGreater(tf) => tf.i_fmt(indent, tz, f),
150            TxnFilter::TxnFilterPostingCommodity(tf) => tf.i_fmt(indent, tz, f),
151        }
152    }
153}
154
155/// Special always true filter (e.g. selects always)
156#[derive(Serialize, Deserialize, Clone, Debug)]
157pub struct NullaryTRUE {}
158
159impl IndentDisplay for NullaryTRUE {
160    fn i_fmt(&self, indent: &str, _tz: TimeZone, f: &mut Formatter<'_>) -> std::fmt::Result {
161        writeln!(f, "{indent}All pass")
162    }
163}
164
165/// Special always false filter (e.g. selects nothing)
166#[derive(Serialize, Deserialize, Clone, Debug)]
167pub struct NullaryFALSE {}
168
169impl IndentDisplay for NullaryFALSE {
170    fn i_fmt(&self, indent: &str, _tz: TimeZone, f: &mut Formatter<'_>) -> std::fmt::Result {
171        writeln!(f, "{indent}None pass")
172    }
173}
174
175#[allow(clippy::needless_pass_by_value)]
176fn logic_filter_indent_fmt(
177    op: &str,
178    indent: &str,
179    tz: TimeZone,
180    filters: &[TxnFilter],
181    f: &mut Formatter<'_>,
182) -> std::fmt::Result {
183    let new_ident = format!("{indent}  ");
184
185    writeln!(f, "{indent}{op}")?;
186    let result: Result<Vec<()>, Error> = filters
187        .iter()
188        .map(|tf| tf.i_fmt(&new_ident, tz.clone(), f))
189        .collect();
190
191    match result {
192        Ok(_) => Ok(()),
193        Err(err) => Err(err),
194    }
195}
196
197fn posting_filter_indent_fmt(
198    indent: &str,
199    target: &str,
200    regex: &str,
201    op: &str,
202    amount: &Decimal,
203    f: &mut Formatter<'_>,
204) -> std::fmt::Result {
205    let my_indent = format!("{indent}  ");
206    writeln!(f, "{indent}{target}")?;
207    writeln!(f, "{my_indent}account: \"{regex}\"")?;
208    writeln!(f, "{my_indent}amount {op} {amount}")
209}