tackler_api/
filters.rs

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