tackler_api/
filters.rs

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