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