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::CardanoDatabase => {
89                let beacon: CardanoDbBeacon = serde_json::from_str(beacon_str).map_err(|e| {
90                    HydrationError::InvalidData(format!(
91                        "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
92                    ))
93                })?;
94                SignedEntityType::CardanoDatabase(beacon)
95            }
96        };
97
98        Ok(signed_entity)
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn hydrate_cardano_transaction_signed_entity_type() {
108        let expected = SignedEntityType::CardanoTransactions(Epoch(35), BlockNumber(77));
109        let signed_entity = Hydrator::hydrate_signed_entity_type(
110            SignedEntityTypeDiscriminants::CardanoTransactions.index(),
111            &expected.get_json_beacon().unwrap(),
112        )
113        .unwrap();
114
115        assert_eq!(expected, signed_entity);
116    }
117
118    #[test]
119    fn hydrate_cardano_database_signed_entity_type() {
120        let expected = SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(84, 239));
121        let signed_entity = Hydrator::hydrate_signed_entity_type(
122            SignedEntityTypeDiscriminants::CardanoDatabase.index(),
123            &expected.get_json_beacon().unwrap(),
124        )
125        .unwrap();
126
127        assert_eq!(expected, signed_entity);
128    }
129}