mithril_persistence/database/
hydrator.rs

1//! Shared hydrator helpers for persistence
2
3use serde::Deserialize;
4
5use mithril_common::entities::{
6    BlockNumber, CardanoDbBeacon, Epoch, SignedEntityType, SignedEntityTypeDiscriminants,
7};
8
9use crate::sqlite::HydrationError;
10
11/// Helper struct to hydrate common data.
12pub struct Hydrator;
13
14impl Hydrator {
15    /// Read a signed entity beacon column from the database
16    pub fn read_signed_entity_beacon_column<U: sqlite::RowIndex + Clone>(
17        row: &sqlite::Row,
18        column_index: U,
19    ) -> String {
20        // We need to check first that the cell can be read as a string first
21        // (e.g. when beacon json is '{"epoch": 1, "immutable_file_number": 2}').
22        // If it fails, we fallback on reading the cell as an integer (e.g. when beacon json is '5').
23        // TODO: Maybe there is a better way of doing this.
24        match row.try_read::<&str, _>(column_index.clone()) {
25            Ok(value) => value.to_string(),
26            Err(_) => (row.read::<i64, _>(column_index)).to_string(),
27        }
28    }
29
30    /// Try to convert an i64 field from the database to a u64
31    pub fn try_to_u64(field: &str, value: i64) -> Result<u64, HydrationError> {
32        u64::try_from(value)
33            .map_err(|e|
34                HydrationError::InvalidData(
35                    format!("Integer field {field} (value={value}) is incompatible with u64 representation. Error = {e}")
36                )
37            )
38    }
39
40    /// Create a [SignedEntityType] from data coming from the database
41    pub fn hydrate_signed_entity_type(
42        signed_entity_type_id: usize,
43        beacon_str: &str,
44    ) -> Result<SignedEntityType, HydrationError> {
45        let signed_entity = match SignedEntityTypeDiscriminants::from_id(signed_entity_type_id)
46            .map_err(|e| {
47                HydrationError::InvalidData(format!("Unknown signed entity. Error: {e}."))
48            })? {
49            SignedEntityTypeDiscriminants::MithrilStakeDistribution => {
50                let epoch: Epoch = serde_json::from_str(beacon_str).map_err(|e| {
51                    HydrationError::InvalidData(format!(
52                        "Invalid Epoch JSON representation '{beacon_str}. Error: {e}'."
53                    ))
54                })?;
55                SignedEntityType::MithrilStakeDistribution(epoch)
56            }
57            SignedEntityTypeDiscriminants::CardanoStakeDistribution => {
58                let epoch: Epoch = serde_json::from_str(beacon_str).map_err(|e| {
59                    HydrationError::InvalidData(format!(
60                        "Invalid Epoch JSON representation '{beacon_str}. Error: {e}'."
61                    ))
62                })?;
63                SignedEntityType::CardanoStakeDistribution(epoch)
64            }
65            SignedEntityTypeDiscriminants::CardanoImmutableFilesFull => {
66                let beacon: CardanoDbBeacon = serde_json::from_str(beacon_str).map_err(|e| {
67                    HydrationError::InvalidData(format!(
68                        "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
69                    ))
70                })?;
71                SignedEntityType::CardanoImmutableFilesFull(beacon)
72            }
73            SignedEntityTypeDiscriminants::CardanoTransactions => {
74                #[derive(Deserialize)]
75                struct CardanoTransactionsBeacon {
76                    epoch: Epoch,
77                    block_number: BlockNumber,
78                }
79
80                let beacon: CardanoTransactionsBeacon =
81                    serde_json::from_str(beacon_str).map_err(|e| {
82                        HydrationError::InvalidData(format!(
83                            "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
84                        ))
85                    })?;
86                SignedEntityType::CardanoTransactions(beacon.epoch, beacon.block_number)
87            }
88            SignedEntityTypeDiscriminants::CardanoBlocksTransactions => {
89                #[derive(Deserialize)]
90                struct CardanoBlocksTransactionsBeacon {
91                    epoch: Epoch,
92                    block_number: BlockNumber,
93                }
94
95                let beacon: CardanoBlocksTransactionsBeacon = serde_json::from_str(beacon_str)
96                    .map_err(|e| {
97                        HydrationError::InvalidData(format!(
98                            "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
99                        ))
100                    })?;
101                SignedEntityType::CardanoBlocksTransactions(beacon.epoch, beacon.block_number)
102            }
103            SignedEntityTypeDiscriminants::CardanoDatabase => {
104                let beacon: CardanoDbBeacon = serde_json::from_str(beacon_str).map_err(|e| {
105                    HydrationError::InvalidData(format!(
106                        "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
107                    ))
108                })?;
109                SignedEntityType::CardanoDatabase(beacon)
110            }
111        };
112
113        Ok(signed_entity)
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120
121    #[test]
122    fn hydrate_cardano_transactions_signed_entity_type() {
123        let expected = SignedEntityType::CardanoTransactions(Epoch(35), BlockNumber(77));
124        let signed_entity = Hydrator::hydrate_signed_entity_type(
125            SignedEntityTypeDiscriminants::CardanoTransactions.index(),
126            &expected.get_json_beacon().unwrap(),
127        )
128        .unwrap();
129
130        assert_eq!(expected, signed_entity);
131    }
132
133    #[test]
134    fn hydrate_cardano_blocks_transactions_signed_entity_type() {
135        let expected = SignedEntityType::CardanoBlocksTransactions(Epoch(37), BlockNumber(79));
136        let signed_entity = Hydrator::hydrate_signed_entity_type(
137            SignedEntityTypeDiscriminants::CardanoBlocksTransactions.index(),
138            &expected.get_json_beacon().unwrap(),
139        )
140        .unwrap();
141
142        assert_eq!(expected, signed_entity);
143    }
144
145    #[test]
146    fn hydrate_cardano_database_signed_entity_type() {
147        let expected = SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(84, 239));
148        let signed_entity = Hydrator::hydrate_signed_entity_type(
149            SignedEntityTypeDiscriminants::CardanoDatabase.index(),
150            &expected.get_json_beacon().unwrap(),
151        )
152        .unwrap();
153
154        assert_eq!(expected, signed_entity);
155    }
156}