Skip to main content

atuin_scripts/store/
record.rs

1use atuin_domain::record::DecryptedData;
2use eyre::{Result, eyre};
3use uuid::Uuid;
4
5use crate::store::script::SCRIPT_VERSION;
6
7use super::script::Script;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum ScriptRecord {
11    Create(Script),
12    Update(Script),
13    Delete(Uuid),
14}
15
16impl ScriptRecord {
17    pub fn serialize(&self) -> Result<DecryptedData> {
18        use rmp::encode;
19
20        let mut output = vec![];
21
22        match self {
23            ScriptRecord::Create(script) => {
24                // 0 -> a script create
25                encode::write_u8(&mut output, 0)?;
26
27                let bytes = script.serialize()?;
28
29                encode::write_bin(&mut output, &bytes.0)?;
30            }
31
32            ScriptRecord::Delete(id) => {
33                // 1 -> a script delete
34                encode::write_u8(&mut output, 1)?;
35                encode::write_str(&mut output, id.to_string().as_str())?;
36            }
37
38            ScriptRecord::Update(script) => {
39                // 2 -> a script update
40                encode::write_u8(&mut output, 2)?;
41                let bytes = script.serialize()?;
42                encode::write_bin(&mut output, &bytes.0)?;
43            }
44        };
45
46        Ok(DecryptedData(output))
47    }
48
49    pub fn deserialize(data: &DecryptedData, version: &str) -> Result<Self> {
50        use rmp::decode;
51
52        fn error_report<E: std::fmt::Debug>(err: E) -> eyre::Report {
53            eyre!("{err:?}")
54        }
55
56        match version {
57            SCRIPT_VERSION => {
58                let mut bytes = decode::Bytes::new(&data.0);
59
60                let record_type = decode::read_u8(&mut bytes).map_err(error_report)?;
61
62                match record_type {
63                    // create
64                    0 => {
65                        // written by encode::write_bin above
66                        let _ = decode::read_bin_len(&mut bytes).map_err(error_report)?;
67                        let script = Script::deserialize(bytes.remaining_slice())?;
68                        Ok(ScriptRecord::Create(script))
69                    }
70
71                    // delete
72                    1 => {
73                        let bytes = bytes.remaining_slice();
74                        let (id, _) = decode::read_str_from_slice(bytes).map_err(error_report)?;
75                        Ok(ScriptRecord::Delete(Uuid::parse_str(id)?))
76                    }
77
78                    // update
79                    2 => {
80                        // written by encode::write_bin above
81                        let _ = decode::read_bin_len(&mut bytes).map_err(error_report)?;
82                        let script = Script::deserialize(bytes.remaining_slice())?;
83                        Ok(ScriptRecord::Update(script))
84                    }
85
86                    _ => Err(eyre!("unknown script record type {record_type}")),
87                }
88            }
89            _ => Err(eyre!("unknown version {version:?}")),
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97    use rstest::rstest;
98
99    #[test]
100    fn test_serialize_create() {
101        let script = Script::builder()
102            .id(uuid::Uuid::parse_str("0195c825a35f7982bdb016168881cbc6").unwrap())
103            .name("test".to_string())
104            .description("test".to_string())
105            .shebang("test".to_string())
106            .tags(vec!["test".to_string()])
107            .script("test".to_string())
108            .build();
109
110        let record = ScriptRecord::Create(script);
111
112        let serialized = record.serialize().unwrap();
113
114        assert_eq!(
115            serialized.0,
116            vec![
117                204, 0, 196, 65, 150, 217, 36, 48, 49, 57, 53, 99, 56, 50, 53, 45, 97, 51, 53, 102,
118                45, 55, 57, 56, 50, 45, 98, 100, 98, 48, 45, 49, 54, 49, 54, 56, 56, 56, 49, 99,
119                98, 99, 54, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116, 164, 116, 101, 115,
120                116, 145, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116
121            ]
122        );
123    }
124
125    #[test]
126    fn test_serialize_delete() {
127        let record = ScriptRecord::Delete(
128            uuid::Uuid::parse_str("0195c825a35f7982bdb016168881cbc6").unwrap(),
129        );
130
131        let serialized = record.serialize().unwrap();
132
133        assert_eq!(
134            serialized.0,
135            vec![
136                204, 1, 217, 36, 48, 49, 57, 53, 99, 56, 50, 53, 45, 97, 51, 53, 102, 45, 55, 57,
137                56, 50, 45, 98, 100, 98, 48, 45, 49, 54, 49, 54, 56, 56, 56, 49, 99, 98, 99, 54
138            ]
139        );
140    }
141
142    #[test]
143    fn test_serialize_update() {
144        let script = Script::builder()
145            .id(uuid::Uuid::parse_str("0195c825a35f7982bdb016168881cbc6").unwrap())
146            .name(String::from("test"))
147            .description(String::from("test"))
148            .shebang(String::from("test"))
149            .tags(vec![String::from("test"), String::from("test2")])
150            .script(String::from("test"))
151            .build();
152
153        let record = ScriptRecord::Update(script);
154
155        let serialized = record.serialize().unwrap();
156
157        assert_eq!(
158            serialized.0,
159            vec![
160                204, 2, 196, 71, 150, 217, 36, 48, 49, 57, 53, 99, 56, 50, 53, 45, 97, 51, 53, 102,
161                45, 55, 57, 56, 50, 45, 98, 100, 98, 48, 45, 49, 54, 49, 54, 56, 56, 56, 49, 99,
162                98, 99, 54, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116, 164, 116, 101, 115,
163                116, 146, 164, 116, 101, 115, 116, 165, 116, 101, 115, 116, 50, 164, 116, 101, 115,
164                116
165            ],
166        );
167    }
168
169    #[rstest]
170    #[case::create(ScriptRecord::Create(
171        Script::builder()
172            .name("test".to_string())
173            .description("test".to_string())
174            .shebang("test".to_string())
175            .tags(vec!["test".to_string()])
176            .script("test".to_string())
177            .build(),
178    ))]
179    #[case::delete(ScriptRecord::Delete(
180        uuid::Uuid::parse_str("0195c825a35f7982bdb016168881cbc6").unwrap(),
181    ))]
182    #[case::update(ScriptRecord::Update(
183        Script::builder()
184            .name("test".to_string())
185            .description("test".to_string())
186            .shebang("test".to_string())
187            .tags(vec!["test".to_string()])
188            .script("test".to_string())
189            .build(),
190    ))]
191    fn serialize_deserialize(#[case] record: ScriptRecord) {
192        let serialized = record.serialize().unwrap();
193        let deserialized = ScriptRecord::deserialize(&serialized, SCRIPT_VERSION).unwrap();
194
195        assert_eq!(record, deserialized);
196    }
197}