1use atuin_common::record::DecryptedData;
2use eyre::{Result, bail, ensure};
3use uuid::Uuid;
4
5use rmp::{
6 decode::{self, Bytes},
7 encode,
8};
9use typed_builder::TypedBuilder;
10
11pub const SCRIPT_VERSION: &str = "v0";
12pub const SCRIPT_TAG: &str = "script";
13pub const SCRIPT_LEN: usize = 20000; #[derive(Debug, Clone, PartialEq, Eq, TypedBuilder)]
16pub struct Script {
18 #[builder(default = uuid::Uuid::new_v4())]
20 pub id: Uuid,
21
22 pub name: String,
24
25 #[builder(default = String::new())]
27 pub description: String,
28
29 #[builder(default = String::new())]
31 pub shebang: String,
32
33 #[builder(default = Vec::new())]
35 pub tags: Vec<String>,
36
37 pub script: String,
39}
40
41impl Script {
42 pub fn serialize(&self) -> Result<DecryptedData> {
43 let mut tags = self.tags.clone();
45 tags.sort();
46
47 let mut output = vec![];
48
49 encode::write_array_len(&mut output, 6)?;
50 encode::write_str(&mut output, &self.id.to_string())?;
51 encode::write_str(&mut output, &self.name)?;
52 encode::write_str(&mut output, &self.description)?;
53 encode::write_str(&mut output, &self.shebang)?;
54 encode::write_array_len(&mut output, self.tags.len() as u32)?;
55
56 for tag in &tags {
57 encode::write_str(&mut output, tag)?;
58 }
59
60 encode::write_str(&mut output, &self.script)?;
61
62 Ok(DecryptedData(output))
63 }
64
65 pub fn deserialize(bytes: &[u8]) -> Result<Self> {
66 let mut bytes = decode::Bytes::new(bytes);
67 let nfields = decode::read_array_len(&mut bytes).unwrap();
68
69 ensure!(nfields == 6, "too many entries in v0 script record");
70
71 let bytes = bytes.remaining_slice();
72
73 let (id, bytes) = decode::read_str_from_slice(bytes).unwrap();
74 let (name, bytes) = decode::read_str_from_slice(bytes).unwrap();
75 let (description, bytes) = decode::read_str_from_slice(bytes).unwrap();
76 let (shebang, bytes) = decode::read_str_from_slice(bytes).unwrap();
77
78 let mut bytes = Bytes::new(bytes);
79 let tags_len = decode::read_array_len(&mut bytes).unwrap();
80
81 let mut bytes = bytes.remaining_slice();
82
83 let mut tags = Vec::new();
84 for _ in 0..tags_len {
85 let (tag, remaining) = decode::read_str_from_slice(bytes).unwrap();
86 tags.push(tag.to_owned());
87 bytes = remaining;
88 }
89
90 let (script, bytes) = decode::read_str_from_slice(bytes).unwrap();
91
92 if !bytes.is_empty() {
93 bail!("trailing bytes in encoded script record. malformed")
94 }
95
96 Ok(Script {
97 id: Uuid::parse_str(id).unwrap(),
98 name: name.to_owned(),
99 description: description.to_owned(),
100 shebang: shebang.to_owned(),
101 tags,
102 script: script.to_owned(),
103 })
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn test_serialize() {
113 let script = Script {
114 id: uuid::Uuid::parse_str("0195c825a35f7982bdb016168881cbc6").unwrap(),
115 name: "test".to_string(),
116 description: "test".to_string(),
117 shebang: "test".to_string(),
118 tags: vec!["test".to_string()],
119 script: "test".to_string(),
120 };
121
122 let serialized = script.serialize().unwrap();
123 assert_eq!(
124 serialized.0,
125 vec![
126 150, 217, 36, 48, 49, 57, 53, 99, 56, 50, 53, 45, 97, 51, 53, 102, 45, 55, 57, 56,
127 50, 45, 98, 100, 98, 48, 45, 49, 54, 49, 54, 56, 56, 56, 49, 99, 98, 99, 54, 164,
128 116, 101, 115, 116, 164, 116, 101, 115, 116, 164, 116, 101, 115, 116, 145, 164,
129 116, 101, 115, 116, 164, 116, 101, 115, 116
130 ]
131 );
132 }
133
134 #[test]
135 fn test_serialize_deserialize() {
136 let script = Script {
137 id: uuid::Uuid::new_v4(),
138 name: "test".to_string(),
139 description: "test".to_string(),
140 shebang: "test".to_string(),
141 tags: vec!["test".to_string()],
142 script: "test".to_string(),
143 };
144
145 let serialized = script.serialize().unwrap();
146
147 let deserialized = Script::deserialize(&serialized.0).unwrap();
148
149 assert_eq!(script, deserialized);
150 }
151}