mithril_persistence/database/
hydrator.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Shared hydrator helpers for persistence

use serde::Deserialize;

use mithril_common::entities::{
    BlockNumber, CardanoDbBeacon, Epoch, SignedEntityType, SignedEntityTypeDiscriminants,
};

use crate::sqlite::HydrationError;

/// Helper struct to hydrate common data.
pub struct Hydrator;

impl Hydrator {
    /// Read a signed entity beacon column from the database
    pub fn read_signed_entity_beacon_column<U: sqlite::RowIndex + Clone>(
        row: &sqlite::Row,
        column_index: U,
    ) -> String {
        // We need to check first that the cell can be read as a string first
        // (e.g. when beacon json is '{"epoch": 1, "immutable_file_number": 2}').
        // If it fails, we fallback on reading the cell as an integer (e.g. when beacon json is '5').
        // TODO: Maybe there is a better way of doing this.
        match row.try_read::<&str, _>(column_index.clone()) {
            Ok(value) => value.to_string(),
            Err(_) => (row.read::<i64, _>(column_index)).to_string(),
        }
    }

    /// Try to convert an i64 field from the database to a u64
    pub fn try_to_u64(field: &str, value: i64) -> Result<u64, HydrationError> {
        u64::try_from(value)
            .map_err(|e|
                HydrationError::InvalidData(
                    format!("Integer field {field} (value={value}) is incompatible with u64 representation. Error = {e}")
                )
            )
    }

    /// Create a [SignedEntityType] from data coming from the database
    pub fn hydrate_signed_entity_type(
        signed_entity_type_id: usize,
        beacon_str: &str,
    ) -> Result<SignedEntityType, HydrationError> {
        let signed_entity = match SignedEntityTypeDiscriminants::from_id(signed_entity_type_id)
            .map_err(|e| {
                HydrationError::InvalidData(format!("Unknown signed entity. Error: {e}."))
            })? {
            SignedEntityTypeDiscriminants::MithrilStakeDistribution => {
                let epoch: Epoch = serde_json::from_str(beacon_str).map_err(|e| {
                    HydrationError::InvalidData(format!(
                        "Invalid Epoch JSON representation '{beacon_str}. Error: {e}'."
                    ))
                })?;
                SignedEntityType::MithrilStakeDistribution(epoch)
            }
            SignedEntityTypeDiscriminants::CardanoStakeDistribution => {
                let epoch: Epoch = serde_json::from_str(beacon_str).map_err(|e| {
                    HydrationError::InvalidData(format!(
                        "Invalid Epoch JSON representation '{beacon_str}. Error: {e}'."
                    ))
                })?;
                SignedEntityType::CardanoStakeDistribution(epoch)
            }
            SignedEntityTypeDiscriminants::CardanoImmutableFilesFull => {
                let beacon: CardanoDbBeacon = serde_json::from_str(beacon_str).map_err(|e| {
                    HydrationError::InvalidData(format!(
                        "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
                    ))
                })?;
                SignedEntityType::CardanoImmutableFilesFull(beacon)
            }
            SignedEntityTypeDiscriminants::CardanoTransactions => {
                #[derive(Deserialize)]
                struct CardanoTransactionsBeacon {
                    epoch: Epoch,
                    block_number: BlockNumber,
                }

                let beacon: CardanoTransactionsBeacon =
                    serde_json::from_str(beacon_str).map_err(|e| {
                        HydrationError::InvalidData(format!(
                        "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
                    ))
                    })?;
                SignedEntityType::CardanoTransactions(beacon.epoch, beacon.block_number)
            }
            SignedEntityTypeDiscriminants::CardanoDatabase => {
                let beacon: CardanoDbBeacon = serde_json::from_str(beacon_str).map_err(|e| {
                    HydrationError::InvalidData(format!(
                        "Invalid Beacon JSON in open_message.beacon: '{beacon_str}'. Error: {e}"
                    ))
                })?;
                SignedEntityType::CardanoDatabase(beacon)
            }
        };

        Ok(signed_entity)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hydrate_cardano_transaction_signed_entity_type() {
        let expected = SignedEntityType::CardanoTransactions(Epoch(35), BlockNumber(77));
        let signed_entity = Hydrator::hydrate_signed_entity_type(
            SignedEntityTypeDiscriminants::CardanoTransactions.index(),
            &expected.get_json_beacon().unwrap(),
        )
        .unwrap();

        assert_eq!(expected, signed_entity);
    }

    #[test]
    fn hydrate_cardano_database_signed_entity_type() {
        let expected = SignedEntityType::CardanoDatabase(CardanoDbBeacon::new(84, 239));
        let signed_entity = Hydrator::hydrate_signed_entity_type(
            SignedEntityTypeDiscriminants::CardanoDatabase.index(),
            &expected.get_json_beacon().unwrap(),
        )
        .unwrap();

        assert_eq!(expected, signed_entity);
    }
}