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