tackler_api/metadata/
items.rs

1/*
2 * Tackler-NG 2022-2025
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! This module contains various Metadata items
7//!
8
9use crate::filters::{FilterDefZoned, FilterDefinition};
10use crate::metadata::Checksum;
11use crate::txn_ts;
12use jiff::Zoned;
13use jiff::tz::TimeZone;
14use serde::Serialize;
15
16#[doc(hidden)]
17pub type MetadataItems = Vec<MetadataItem>;
18
19#[doc(hidden)]
20pub trait Text: std::fmt::Debug {
21    /// Get metadata item as text
22    #[must_use]
23    fn text(&self, tz: TimeZone) -> Vec<String>;
24}
25
26#[doc(hidden)]
27#[derive(Serialize, Debug, Clone)]
28pub enum MetadataItem {
29    #[doc(hidden)]
30    TxnSetChecksum(TxnSetChecksum),
31    #[doc(hidden)]
32    TimeZoneInfo(TimeZoneInfo),
33    #[doc(hidden)]
34    CreditAccountReport(CreditAccountReport),
35    #[doc(hidden)]
36    AccountSelectorChecksum(AccountSelectorChecksum),
37    #[doc(hidden)]
38    GitInputReference(GitInputReference),
39    #[doc(hidden)]
40    TxnFilterDescription(TxnFilterDescription),
41    #[doc(hidden)]
42    PriceRecords(PriceRecords),
43}
44
45impl MetadataItem {
46    pub const ITEM_PAD: usize = 15;
47}
48
49impl Text for MetadataItem {
50    fn text(&self, tz: TimeZone) -> Vec<String> {
51        match self {
52            Self::GitInputReference(gif) => gif.text(tz),
53            Self::TxnSetChecksum(tscs) => tscs.text(tz),
54            Self::TimeZoneInfo(tzinfo) => tzinfo.text(tz),
55            Self::CreditAccountReport(credit) => credit.text(tz),
56            Self::AccountSelectorChecksum(asc) => asc.text(tz),
57            Self::TxnFilterDescription(tfd) => tfd.text(tz),
58            Self::PriceRecords(pr) => pr.text(tz),
59        }
60    }
61}
62
63/// Txn Set Checksum metadata item
64#[derive(Serialize, Debug, Clone)]
65pub struct TxnSetChecksum {
66    /// size of transaction set
67    pub size: usize,
68    /// hash of Txn Set Checksum
69    pub hash: Checksum,
70}
71impl Text for TxnSetChecksum {
72    fn text(&self, _tz: TimeZone) -> Vec<String> {
73        // echo -n "SHA-512/256" | wc -c => 11
74        let pad = MetadataItem::ITEM_PAD;
75        vec![
76            format!("Txn Set Checksum"),
77            format!("{:>pad$} : {}", self.hash.algorithm, &self.hash.value),
78            format!("{:>pad$} : {}", "set size", self.size),
79        ]
80    }
81}
82
83/*
84/// Report timezone information
85
86#[derive(Serialize, Debug, Clone)]
87pub struct TimeZoneInfo {
88    #[serde(rename = "zoneId")]
89    /// IANA ZoneID
90    pub zone_id: String,
91}
92*/
93
94/// Account Selector Checksum item
95#[derive(Serialize, Debug, Clone)]
96pub struct AccountSelectorChecksum {
97    /// Account selector checksum
98    pub hash: Checksum,
99    /// Account selectors
100    pub selectors: Vec<String>,
101}
102impl Text for AccountSelectorChecksum {
103    fn text(&self, _tz: TimeZone) -> Vec<String> {
104        // echo -n "SHA-512/256" | wc -c => 11
105        let pad = MetadataItem::ITEM_PAD;
106        let mut t = vec![
107            format!("Account Selector Checksum"),
108            format!("{:>pad$} : {}", self.hash.algorithm, &self.hash.value),
109        ];
110        if !self.selectors.is_empty() {
111            let sel_txt = if self.selectors.len() > 1 {
112                "selectors"
113            } else {
114                "selector"
115            };
116            let l = format!(
117                "{:>pad$} : '{}'",
118                sel_txt,
119                &self.selectors.first().unwrap(/*:ok*/)
120            );
121            t.push(l);
122            for s in self.selectors.iter().skip(1) {
123                let l = format!("{:>pad$} | '{}'", "", s);
124                t.push(l);
125            }
126        }
127        t
128    }
129}
130
131/// Credit Account Report
132///
133/// Report of credit (usually negative) account
134#[derive(Serialize, Debug, Clone)]
135pub struct CreditAccountReport {}
136
137impl Text for CreditAccountReport {
138    fn text(&self, _tz: TimeZone) -> Vec<String> {
139        let pad = MetadataItem::ITEM_PAD;
140        vec![
141            "Credit Account Report".to_string(),
142            format!("{:>pad$} : {}", "NOTE", "All amounts are inverted"),
143        ]
144    }
145}
146
147/// Report timezone item
148#[derive(Serialize, Debug, Clone)]
149pub struct TimeZoneInfo {
150    /// Timezone name
151    #[serde(rename = "zoneId")]
152    pub zone_id: String,
153}
154impl Text for TimeZoneInfo {
155    fn text(&self, _tz: TimeZone) -> Vec<String> {
156        let pad = MetadataItem::ITEM_PAD;
157        vec![
158            "Report Time Zone".to_string(),
159            format!("{:>pad$} : {}", "TZ name", &self.zone_id),
160        ]
161    }
162}
163/// Metadata information about active Txn Filters
164///
165#[derive(Serialize, Debug, Clone)]
166pub struct TxnFilterDescription {
167    #[doc(hidden)]
168    #[serde(rename = "txnFilterDef")]
169    txn_filter_def: FilterDefinition,
170}
171
172impl TxnFilterDescription {
173    /// Make Txn filter Description from Filter Definition
174    ///
175    #[must_use]
176    pub fn from(tf: FilterDefinition) -> TxnFilterDescription {
177        TxnFilterDescription { txn_filter_def: tf }
178    }
179}
180impl Text for TxnFilterDescription {
181    fn text(&self, tz: TimeZone) -> Vec<String> {
182        // todo: TxnFilterDescription needs proper implementation for Text
183        //       See equity_exporter::write_export
184        format!(
185            "{}",
186            FilterDefZoned {
187                filt_def: &self.txn_filter_def,
188                tz
189            }
190        )
191        .trim_end()
192        .split('\n')
193        .map(String::from)
194        .collect::<Vec<String>>()
195    }
196}
197
198/// Metadata information about Git Txn input
199///
200#[derive(Serialize, Debug, Clone)]
201pub struct GitInputReference {
202    /// commit id
203    pub commit: String,
204
205    /// Symbolic git reference `main`, `Y2023`, etc.
206    #[serde(rename = "ref")]
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub reference: Option<String>,
209
210    /// Transaction directory ("journal") inside repository
211    pub dir: String,
212
213    /// Extension of journal filenames
214    pub extension: String,
215
216    /// Commit author
217    pub author: String,
218
219    /// Commit date
220    pub date: String,
221
222    /// Subject line of selected commit
223    pub subject: String,
224}
225
226impl Text for GitInputReference {
227    fn text(&self, _tz: TimeZone) -> Vec<String> {
228        let pad = MetadataItem::ITEM_PAD;
229        vec![
230            format!("Git Storage"),
231            format!(
232                "{:>pad$} : {}",
233                "reference",
234                self.reference
235                    .as_ref()
236                    .unwrap_or(&"FIXED by commit".to_string())
237            ),
238            format!("{:>pad$} : {}", "directory", self.dir),
239            format!("{:>pad$} : {}", "extension", self.extension),
240            format!("{:>pad$} : {}", "commit", self.commit),
241            format!("{:>pad$} : {}", "author", self.author),
242            format!("{:>pad$} : {}", "date", self.date),
243            format!("{:>pad$} : {}", "subject", self.subject),
244        ]
245    }
246}
247
248/// Metadata item for one commodity conversion
249#[derive(Serialize, Debug, Clone)]
250pub struct PriceRecord {
251    /// Time of price record
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub ts: Option<Zoned>,
254    /// Source (from) commodity
255    pub source: String,
256    /// Conversion rate (value in target commodity)
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub rate: Option<String>,
259    /// Target (to) commodity
260    pub target: String,
261}
262impl Text for PriceRecord {
263    fn text(&self, tz: TimeZone) -> Vec<String> {
264        let pad = MetadataItem::ITEM_PAD;
265        vec![
266            format!(
267                "{:>pad$} : {}",
268                "Time",
269                self.ts.as_ref().map_or("At txn time".to_string(), |ts| {
270                    txn_ts::as_tz_full(ts, tz)
271                })
272            ),
273            format!("{:>pad$} : {}", "Commodity", self.source),
274            format!(
275                "{:>pad$} : {} {}",
276                "Value",
277                self.rate.clone().map_or("-".to_string(), |v| v),
278                self.target
279            ),
280        ]
281    }
282}
283/// Metadata information of used commodity conversions
284#[derive(Serialize, Debug, Clone)]
285pub struct PriceRecords {
286    /// Collection of used commodity conversions prices / rates
287    pub rates: Vec<PriceRecord>,
288}
289impl Text for PriceRecords {
290    fn text(&self, tz: TimeZone) -> Vec<String> {
291        let pad = MetadataItem::ITEM_PAD;
292
293        let mut txt = Vec::new();
294
295        if let Some(pr) = self.rates.first() {
296            txt.push("Commodity Prices".to_string());
297            txt.extend(pr.text(tz.clone()));
298
299            if self.rates.len() > 1 {
300                for pr in &self.rates[1..] {
301                    txt.push(format!("{:>pad$} -", ""));
302                    txt.extend(pr.text(tz.clone()));
303                }
304            }
305        }
306        txt
307    }
308}